From 51e66af2fdfe61f8b7b1f736551d86c3a2ce0bf6 Mon Sep 17 00:00:00 2001 From: Stefano Franz Date: Fri, 16 Nov 2018 17:10:53 +0000 Subject: [PATCH 01/19] CORDA-2201 add documentation for hiding passwords in node.conf (#4221) --- docs/source/corda-configuration-file.rst | 50 +++++++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index 532ac838e0..c5db669868 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -47,8 +47,10 @@ Here are the contents of the ``reference.conf`` file: Fields ------ -The available config fields are listed below. ``baseDirectory`` is available as a substitution value and contains the -absolute path to the node's base directory. + +.. note:: All fields can be used with placeholders for environment variables. For example: ``${NODE_TRUST_STORE_PASSWORD}`` would be replaced by the contents of environment variable ``NODE_TRUST_STORE_PASSWORD``. See: `Hiding Sensitive Data`_ + +The available config fields are listed below. :myLegalName: The legal identity of the node. This acts as a human-readable alias to the node's public key and can be used with the network map to look up the node's info. This is the name that is used in the node's certificates (either when requesting them @@ -307,3 +309,47 @@ Together with the above configuration `tlsCertCrlIssuer` option needs to be set This set-up ensures that the TLS-level certificates are embedded with the CRL distribution point referencing the CRL issued by R3. In cases where a proprietary CRL infrastructure is provided those values need to be changed accordingly. + +Hiding Sensitive Data +--------------------- +A frequent requirement is that configuration files must not expose passwords to unauthorised readers. By leveraging environment variables, it is possible to hide passwords and other similar fields. + +Take a simple node config that wishes to protect the node cryptographic stores: + +.. parsed-literal:: + + myLegalName : "O=PasswordProtectedNode,OU=corda,L=London,C=GB" + keyStorePassword : ${KEY_PASS} + trustStorePassword : ${TRUST_PASS} + p2pAddress : "localhost:12345" + devMode : false + compatibilityZoneURL : "https://cz.corda.net" + +By delegating to a password store, and using `command substitution` it is possible to ensure that sensitive passwords never appear in plain text. +The below examples are of loading Corda with the KEY_PASS and TRUST_PASS variables read from a program named ``corporatePasswordStore``. + + +Bash +~~~~ + +.. sourcecode:: shell + + KEY_PASS=$(corporatePasswordStore --cordaKeyStorePassword) TRUST_PASS=$(corporatePasswordStore --cordaTrustStorePassword) java -jar corda.jar + +Windows PowerShell +~~~~~~~~~~~~~~~~~~ + +.. sourcecode:: shell + + $env:KEY_PASS=$(corporatePasswordStore --cordaKeyStorePassword); $env:TRUST_PASS=$(corporatePasswordStore --cordaTrustStorePassword); java -jar corda.jar + + +For launching on Windows without PowerShell, it is not possible to perform command substitution, and so the variables must be specified manually, for example: + +.. sourcecode:: shell + + SET KEY_PASS=mypassword & SET TRUST_PASS=mypassword & java -jar corda.jar + +.. warning:: If this approach is taken, the passwords will appear in the windows command prompt history. + + From 8f463c46a956def70538f7441a5bcd7f2dca4a44 Mon Sep 17 00:00:00 2001 From: Stefano Franz Date: Fri, 16 Nov 2018 17:13:55 +0000 Subject: [PATCH 02/19] Add message to uses of require(...) (#4192) --- .../corda/client/jackson/internal/CordaModule.kt | 13 ++++++++----- .../net/corda/client/jfx/model/NodeMonitorModel.kt | 2 +- .../corda/common/validation/internal/Validated.kt | 2 +- .../kotlin/net/corda/core/crypto/CompositeKey.kt | 6 +++--- .../main/kotlin/net/corda/core/crypto/SecureHash.kt | 2 +- .../net/corda/core/crypto/internal/ProviderMap.kt | 2 +- .../kotlin/net/corda/core/internal/InternalUtils.kt | 9 ++++++--- .../kotlin/net/corda/core/internal/NamedCache.kt | 4 ++-- .../core/internal/errors/AddressBindingException.kt | 2 +- .../kotlin/net/corda/core/utilities/ByteArrays.kt | 10 +++++----- .../kotlin/io/cryptoblk/core/StatusTransitions.kt | 8 ++++---- .../src/main/kotlin/net/corda/kryohook/KryoHook.kt | 0 .../contracts/universal/UniversalContract.kt | 2 +- .../net/corda/finance/contracts/FinanceTypes.kt | 2 +- .../net/corda/finance/contracts/GetBalances.kt | 4 ++-- .../corda/finance/contracts/asset/OnLedgerAsset.kt | 2 +- .../net/corda/finance/flows/TwoPartyDealFlow.kt | 4 ++-- .../net/corda/finance/flows/TwoPartyTradeFlow.kt | 2 +- .../finance/utils/PhysicalLocationStructures.kt | 8 ++++---- .../corda/nodeapi/internal/DevIdentityGenerator.kt | 4 ++-- .../corda/nodeapi/internal/crypto/X509Utilities.kt | 6 +++--- .../nodeapi/internal/network/NetworkBootstrapper.kt | 6 +++--- .../internal/artemis/ReactiveArtemisConsumer.kt | 4 ++-- .../net/corda/node/serialization/kryo/Kryo.kt | 5 +++-- .../corda/node/services/config/NodeConfiguration.kt | 4 ++-- .../node/services/messaging/P2PMessagingClient.kt | 2 +- .../net/corda/node/services/messaging/RPCServer.kt | 4 ++-- .../services/persistence/NodeAttachmentService.kt | 2 +- .../node/services/statemachine/CountUpDownLatch.kt | 4 ++-- .../services/statemachine/FlowStateMachineImpl.kt | 4 ++-- .../SingleThreadedStateMachineManager.kt | 10 +++++----- .../FiberDeserializationCheckingInterceptor.kt | 12 +++--------- .../registration/NetworkRegistrationHelper.kt | 2 +- .../net/corda/attachmentdemo/AttachmentDemo.kt | 6 +++--- .../net/corda/vega/contracts/PortfolioState.kt | 2 +- .../internal/carpenter/ClassCarpenter.kt | 4 +++- .../internal/carpenter/MetaCarpenter.kt | 1 + .../internal/carpenter/SchemaFields.kt | 5 ++--- .../net/corda/testing/contracts/DummyContract.kt | 2 +- 39 files changed, 88 insertions(+), 85 deletions(-) create mode 100644 experimental/kryo-hook/src/main/kotlin/net/corda/kryohook/KryoHook.kt diff --git a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt index 6eef15668a..2a2a11041d 100644 --- a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt +++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt @@ -3,7 +3,7 @@ package net.corda.client.jackson.internal import com.fasterxml.jackson.annotation.* -import com.fasterxml.jackson.annotation.JsonCreator.Mode.* +import com.fasterxml.jackson.annotation.JsonCreator.Mode.DISABLED import com.fasterxml.jackson.annotation.JsonInclude.Include import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.core.JsonParseException @@ -38,7 +38,10 @@ import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.parseAsHex import net.corda.core.utilities.toHexString import net.corda.serialization.internal.AllWhitelist -import net.corda.serialization.internal.amqp.* +import net.corda.serialization.internal.amqp.SerializerFactoryBuilder +import net.corda.serialization.internal.amqp.constructorForDeserialization +import net.corda.serialization.internal.amqp.hasCordaSerializable +import net.corda.serialization.internal.amqp.propertiesForSerialization import java.math.BigDecimal import java.security.PublicKey import java.security.cert.CertPath @@ -327,11 +330,11 @@ private class PartialTreeJson(val includedLeaf: SecureHash? = null, val right: PartialTreeJson? = null) { init { if (includedLeaf != null) { - require(leaf == null && left == null && right == null) + require(leaf == null && left == null && right == null) { "Invalid JSON structure" } } else if (leaf != null) { - require(left == null && right == null) + require(left == null && right == null) { "Invalid JSON structure" } } else { - require(left != null && right != null) + require(left != null && right != null) { "Invalid JSON structure" } } } } 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 b42deddb8c..5bd5f65b5d 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 @@ -202,7 +202,7 @@ class NodeMonitorModel : AutoCloseable { val _connection = client.start(username, password) // Check connection is truly operational before returning it. val nodeInfo = _connection.proxy.nodeInfo() - require(nodeInfo.legalIdentitiesAndCerts.isNotEmpty()) + require(nodeInfo.legalIdentitiesAndCerts.isNotEmpty()){"No identity certificates found"} _connection } catch (exception: Exception) { if (shouldRetry) { diff --git a/common/validation/src/main/kotlin/net/corda/common/validation/internal/Validated.kt b/common/validation/src/main/kotlin/net/corda/common/validation/internal/Validated.kt index a93f4b44fb..9a21285409 100644 --- a/common/validation/src/main/kotlin/net/corda/common/validation/internal/Validated.kt +++ b/common/validation/src/main/kotlin/net/corda/common/validation/internal/Validated.kt @@ -126,7 +126,7 @@ interface Validated { */ class Unsuccessful(override val errors: Set) : Result(), Validated { init { - require(errors.isNotEmpty()) + require(errors.isNotEmpty()) { "No errors encountered during validation" } } override fun value(exceptionOnErrors: (Set) -> Exception) = throw exceptionOnErrors.invoke(errors) diff --git a/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt b/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt index be28292ff1..64f64c3f75 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt @@ -40,14 +40,14 @@ class CompositeKey private constructor(val threshold: Int, children: List - require(childAsn1 is ASN1Sequence) + require(childAsn1 is ASN1Sequence) { "Child key is not in ASN1 format" } val childSeq = childAsn1 as ASN1Sequence val key = Crypto.decodePublicKey((childSeq.getObjectAt(0) as DERBitString).bytes) val weight = ASN1Integer.getInstance(childSeq.getObjectAt(1)) @@ -274,7 +274,7 @@ class CompositeKey private constructor(val threshold: Int, children: List 0) + require(threshold == null || threshold > 0) { "Threshold must not be specified or its value must be greater than zero" } val n = children.size return when { n > 1 -> CompositeKey(threshold ?: children.map { (_, weight) -> weight }.sum(), children) diff --git a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt index 220298587b..330bc766df 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt @@ -19,7 +19,7 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) { /** SHA-256 is part of the SHA-2 hash function family. Generated hash is fixed size, 256-bits (32-bytes). */ class SHA256(bytes: ByteArray) : SecureHash(bytes) { init { - require(bytes.size == 32) + require(bytes.size == 32) { "Invalid hash size, must be 32 bytes" } } } diff --git a/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt b/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt index bd45e7490b..0523b00cba 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt @@ -45,7 +45,7 @@ val cordaBouncyCastleProvider = BouncyCastleProvider().apply { Security.addProvider(it) } val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply { - require(name == "BCPQC") // The constant it comes from is not final. + require(name == "BCPQC") { "Invalid PQCProvider name" } }.also { Security.addProvider(it) } 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 9814502433..ac306fbdbb 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -5,7 +5,10 @@ package net.corda.core.internal import net.corda.core.DeleteForDJVM import net.corda.core.KeepForDJVM import net.corda.core.crypto.* -import net.corda.core.serialization.* +import net.corda.core.serialization.SerializationDefaults +import net.corda.core.serialization.SerializedBytes +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.UntrustworthyData import org.slf4j.Logger @@ -109,7 +112,7 @@ fun List.randomOrNull(): T? { /** Returns the index of the given item or throws [IllegalArgumentException] if not found. */ fun List.indexOfOrThrow(item: T): Int { val i = indexOf(item) - require(i != -1) + require(i != -1){"No such element"} return i } @@ -219,7 +222,7 @@ data class InputStreamAndHash(val inputStream: InputStream, val sha256: SecureHa */ @DeleteForDJVM fun createInMemoryTestZip(numOfExpectedBytes: Int, content: Byte): InputStreamAndHash { - require(numOfExpectedBytes > 0) + require(numOfExpectedBytes > 0){"Expected bytes must be greater than zero"} val baos = ByteArrayOutputStream() ZipOutputStream(baos).use { zos -> val arraySize = 1024 diff --git a/core/src/main/kotlin/net/corda/core/internal/NamedCache.kt b/core/src/main/kotlin/net/corda/core/internal/NamedCache.kt index 632b0c3060..82c1c35b66 100644 --- a/core/src/main/kotlin/net/corda/core/internal/NamedCache.kt +++ b/core/src/main/kotlin/net/corda/core/internal/NamedCache.kt @@ -14,8 +14,8 @@ interface NamedCacheFactory { * the name can be used to create a file name or a metric name. */ fun checkCacheName(name: String) { - require(!name.isBlank()) - require(allowedChars.matches(name)) + require(!name.isBlank()){"Name must not be empty or only whitespace"} + require(allowedChars.matches(name)){"Invalid characters in cache name"} } fun buildNamed(caffeine: Caffeine, name: String): Cache diff --git a/core/src/main/kotlin/net/corda/core/internal/errors/AddressBindingException.kt b/core/src/main/kotlin/net/corda/core/internal/errors/AddressBindingException.kt index 8c597f65e4..59076ef54f 100644 --- a/core/src/main/kotlin/net/corda/core/internal/errors/AddressBindingException.kt +++ b/core/src/main/kotlin/net/corda/core/internal/errors/AddressBindingException.kt @@ -9,7 +9,7 @@ class AddressBindingException(val addresses: Set) : CordaRun private companion object { private fun message(addresses: Set): String { - require(addresses.isNotEmpty()) + require(addresses.isNotEmpty()) { "Address list must not be empty" } return if (addresses.size > 1) { "Failed to bind on an address in ${addresses.joinToString(", ", "[", "]")}." } else { 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 7190e0c770..296929d29b 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt @@ -68,8 +68,8 @@ sealed class ByteSequence(private val _bytes: ByteArray, val offset: Int, val si * This method cannot be used to get bytes before [offset] or after [offset]+[size], and never makes a new array. */ fun slice(start: Int = 0, end: Int = size): ByteBuffer { - require(start >= 0) - require(end >= 0) + require(start >= 0) { "Starting index must be greater than or equal to 0" } + require(end >= 0){"End index must be greater or equal to 0"} val clampedStart = min(start, size) val clampedEnd = min(end, size) return ByteBuffer.wrap(_bytes, offset + clampedStart, max(0, clampedEnd - clampedStart)).asReadOnlyBuffer() @@ -155,7 +155,7 @@ open class OpaqueBytes(bytes: ByteArray) : ByteSequence(bytes, 0, bytes.size) { } init { - require(bytes.isNotEmpty()) + require(bytes.isNotEmpty()) { "Byte Array must not be empty" } } /** @@ -193,7 +193,7 @@ fun String.parseAsHex(): ByteArray = DatatypeConverter.parseHexBinary(this) @KeepForDJVM class OpaqueBytesSubSequence(override val bytes: ByteArray, offset: Int, size: Int) : ByteSequence(bytes, offset, size) { init { - require(offset >= 0 && offset < bytes.size) - require(size >= 0 && offset + size <= bytes.size) + require(offset >= 0 && offset < bytes.size) { "Offset must be greater than or equal to 0, and less than the size of the backing array" } + require(size >= 0 && offset + size <= bytes.size) { "Sub-sequence size must be greater than or equal to 0, and less than the size of the backing array" } } } diff --git a/experimental/corda-utils/src/main/kotlin/io/cryptoblk/core/StatusTransitions.kt b/experimental/corda-utils/src/main/kotlin/io/cryptoblk/core/StatusTransitions.kt index af7db67dfa..0d108850cf 100644 --- a/experimental/corda-utils/src/main/kotlin/io/cryptoblk/core/StatusTransitions.kt +++ b/experimental/corda-utils/src/main/kotlin/io/cryptoblk/core/StatusTransitions.kt @@ -32,14 +32,14 @@ data class PrintedTransitionGraph(val stateClassName: String, val printedPUML: S * Shorthand for defining transitions directly from the command class */ fun CommandData.txDef(signer: R? = null, from: S?, to: List): - TransitionDef = TransitionDef(this::class.java, signer, from, to) + TransitionDef = TransitionDef(this::class.java, signer, from, to) /** * For a given [stateClass] that tracks a status, it holds all possible transitions in [ts]. * This can be used for generic [verify] in contract code as well as for visualizing the state transition graph in PUML ([printGraph]). */ class StatusTransitions>(private val stateClass: KClass, - private vararg val ts: TransitionDef) { + private vararg val ts: TransitionDef) { private val allowedCmds = ts.map { it.cmd }.toSet() @@ -67,11 +67,11 @@ class StatusTransitions>(priv // for each combination of in x out which should normally be at most 1... inputStates.forEach { inp -> outputStates.forEach { outp -> - require(inp != null || outp != null) + require(inp != null || outp != null) { "Input and output states cannot be both left unspecified" } val options = matchingTransitions(inp?.status, outp?.status, cmd.value) val signerGroup = options.groupBy { it.signer }.entries.singleOrNull() - ?: throw IllegalStateException("Cannot have different signers in StatusTransitions for the same command.") + ?: throw IllegalStateException("Cannot have different signers in StatusTransitions for the same command.") val signer = signerGroup.key if (signer != null) { // which state determines who is the signer? by default the input, unless it's the initial transition diff --git a/experimental/kryo-hook/src/main/kotlin/net/corda/kryohook/KryoHook.kt b/experimental/kryo-hook/src/main/kotlin/net/corda/kryohook/KryoHook.kt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/experimental/src/main/kotlin/net/corda/finance/contracts/universal/UniversalContract.kt b/experimental/src/main/kotlin/net/corda/finance/contracts/universal/UniversalContract.kt index f73805b967..b5b1a97c6d 100644 --- a/experimental/src/main/kotlin/net/corda/finance/contracts/universal/UniversalContract.kt +++ b/experimental/src/main/kotlin/net/corda/finance/contracts/universal/UniversalContract.kt @@ -203,7 +203,7 @@ class UniversalContract : Contract { val rest = extractRemainder(arr, action) // for now - let's assume not - require(rest is Zero) + require(rest is Zero) { "Remainder must be zero" } requireThat { "action must have a time-window" using (tx.timeWindow != null) diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/FinanceTypes.kt b/finance/src/main/kotlin/net/corda/finance/contracts/FinanceTypes.kt index 56cd5d497e..849cee24a8 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/FinanceTypes.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/FinanceTypes.kt @@ -321,7 +321,7 @@ open class BusinessCalendar(val holidayDates: List) { * TODO: Make more efficient if necessary */ fun moveBusinessDays(date: LocalDate, direction: DateRollDirection, i: Int): LocalDate { - require(i >= 0) + require(i >= 0){"Days to add/subtract must be positive"} if (i == 0) return date var retDate = date var ctr = 0 diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/GetBalances.kt b/finance/src/main/kotlin/net/corda/finance/contracts/GetBalances.kt index 34fb16b688..f844ac2e42 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/GetBalances.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/GetBalances.kt @@ -39,8 +39,8 @@ private fun rowsToAmount(currency: Currency, rows: Vault.Page>) return if (rows.otherResults.isEmpty()) { Amount(0L, currency) } else { - require(rows.otherResults.size == 2) - require(rows.otherResults[1] == currency.currencyCode) + require(rows.otherResults.size == 2){"Invalid number of rows returned by query"} + require(rows.otherResults[1] == currency.currencyCode){"Currency on rows returned by query does not match expected"} val quantity = rows.otherResults[0] as Long Amount(quantity, currency) } diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/OnLedgerAsset.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/OnLedgerAsset.kt index 046de76ee0..074cf869dd 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/OnLedgerAsset.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/OnLedgerAsset.kt @@ -298,7 +298,7 @@ abstract class OnLedgerAsset> issueCommand: CommandData): Set { check(tx.inputStates().isEmpty()) check(tx.outputStates().map { it.data }.filterIsInstance(transactionState.javaClass).isEmpty()) - require(transactionState.data.amount.quantity > 0) + require(transactionState.data.amount.quantity > 0){"Amount to issue must be greater than zero"} val at = transactionState.data.amount.token.issuer val commandSigner = at.party.owningKey tx.addOutputState(transactionState) diff --git a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyDealFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyDealFlow.kt index 93d7430f5b..413937aaa9 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyDealFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyDealFlow.kt @@ -140,8 +140,8 @@ object TwoPartyDealFlow { // Verify the transaction identities represent the correct parties val wellKnownOtherParty = serviceHub.identityService.wellKnownPartyFromAnonymous(it.primaryIdentity) val wellKnownMe = serviceHub.identityService.wellKnownPartyFromAnonymous(it.secondaryIdentity) - require(wellKnownOtherParty == otherSideSession.counterparty) - require(wellKnownMe == ourIdentity) + require(wellKnownOtherParty == otherSideSession.counterparty){"Well known party for handshake identity ${it.primaryIdentity} does not match counterparty"} + require(wellKnownMe == ourIdentity){"Well known party for handshake identity ${it.secondaryIdentity} does not match ourIdentity"} validateHandshake(it) } } diff --git a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt index 57c79cf191..92ab856e69 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt @@ -203,7 +203,7 @@ object TwoPartyTradeFlow { // The asset must either be owned by the well known identity of the counterparty, or we must be able to // prove the owner is a confidential identity of the counterparty. val assetForSaleIdentity = serviceHub.identityService.wellKnownPartyFromAnonymous(asset.owner) - require(assetForSaleIdentity == sellerSession.counterparty) + require(assetForSaleIdentity == sellerSession.counterparty){"Well known identity lookup returned identity that does not match counterparty"} // Register the identity we're about to send payment to. This shouldn't be the same as the asset owner // identity, so that anonymity is enforced. diff --git a/finance/src/main/kotlin/net/corda/finance/utils/PhysicalLocationStructures.kt b/finance/src/main/kotlin/net/corda/finance/utils/PhysicalLocationStructures.kt index 5277921bd1..0e54a2ffba 100644 --- a/finance/src/main/kotlin/net/corda/finance/utils/PhysicalLocationStructures.kt +++ b/finance/src/main/kotlin/net/corda/finance/utils/PhysicalLocationStructures.kt @@ -9,8 +9,8 @@ data class ScreenCoordinate(val screenX: Double, val screenY: Double) @CordaSerializable data class WorldCoordinate(val latitude: Double, val longitude: Double) { init { - require(latitude in -90..90) - require(longitude in -180..180) + require(latitude in -90..90){"Latitude must be between -90 and +90"} + require(longitude in -180..180){"Longitude must be between -180 and +180"} } /** @@ -24,8 +24,8 @@ data class WorldCoordinate(val latitude: Double, val longitude: Double) { @Suppress("unused") // Used from the visualiser GUI. fun project(screenWidth: Double, screenHeight: Double, topLatitude: Double, bottomLatitude: Double, leftLongitude: Double, rightLongitude: Double): ScreenCoordinate { - require(latitude in bottomLatitude..topLatitude) - require(longitude in leftLongitude..rightLongitude) + require(latitude in bottomLatitude..topLatitude){"Latitude must be between $bottomLatitude and $topLatitude"} + require(longitude in leftLongitude..rightLongitude){"Longitude must be between $leftLongitude and $rightLongitude"} fun deg2rad(deg: Double) = deg * Math.PI / 180.0 val leftLngRad = deg2rad(leftLongitude) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt index d08cd750cb..a4c352fcca 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt @@ -47,7 +47,7 @@ object DevIdentityGenerator { /** Generates a CFT notary identity, where the entire cluster shares a key pair. */ fun generateDistributedNotarySingularIdentity(dirs: List, notaryName: CordaX500Name): Party { - require(dirs.isNotEmpty()) + require(dirs.isNotEmpty()){"At least one directory to generate identity for must be specified"} log.trace { "Generating singular identity \"$notaryName\" for nodes: ${dirs.joinToString()}" } @@ -63,7 +63,7 @@ object DevIdentityGenerator { /** Generates a BFT notary identity: individual key pairs for each cluster member, and a shared composite key. */ fun generateDistributedNotaryCompositeIdentity(dirs: List, notaryName: CordaX500Name, threshold: Int = 1): Party { - require(dirs.isNotEmpty()) + require(dirs.isNotEmpty()){"At least one directory to generate identity for must be specified"} log.trace { "Generating composite identity \"$notaryName\" for nodes: ${dirs.joinToString()}" } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt index 9a21bfac72..87618d4e52 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt @@ -254,7 +254,7 @@ object X509Utilities { crlIssuer: X500Name? = null): X509Certificate { val builder = createPartialCertificate(certificateType, issuer, issuerPublicKey, subject, subjectPublicKey, validityWindow, nameConstraints, crlDistPoint, crlIssuer) return builder.build(issuerSigner).run { - require(isValidOn(Date())) + require(isValidOn(Date())){"Certificate is not valid at instant now"} toJca() } } @@ -292,8 +292,8 @@ object X509Utilities { crlDistPoint, crlIssuer) return builder.build(signer).run { - require(isValidOn(Date())) - require(isSignatureValid(JcaContentVerifierProviderBuilder().build(issuerKeyPair.public))) + require(isValidOn(Date())){"Certificate is not valid at instant now"} + require(isSignatureValid(JcaContentVerifierProviderBuilder().build(issuerKeyPair.public))){"Invalid signature"} toJca() } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt index 52138854a4..36c567107c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt @@ -431,10 +431,10 @@ internal constructor(private val initSerEnv: Boolean, private fun NodeInfo.notaryIdentity(): Party { return when (legalIdentities.size) { - // Single node notaries have just one identity like all other nodes. This identity is the notary identity + // Single node notaries have just one identity like all other nodes. This identity is the notary identity 1 -> legalIdentities[0] - // Nodes which are part of a distributed notary have a second identity which is the composite identity of the - // cluster and is shared by all the other members. This is the notary identity. + // Nodes which are part of a distributed notary have a second identity which is the composite identity of the + // cluster and is shared by all the other members. This is the notary identity. 2 -> legalIdentities[1] else -> throw IllegalArgumentException("Not sure how to get the notary identity in this scenerio: $this") } diff --git a/node/src/main/kotlin/net/corda/node/internal/artemis/ReactiveArtemisConsumer.kt b/node/src/main/kotlin/net/corda/node/internal/artemis/ReactiveArtemisConsumer.kt index f7adb54ca7..0c0eb71f9f 100644 --- a/node/src/main/kotlin/net/corda/node/internal/artemis/ReactiveArtemisConsumer.kt +++ b/node/src/main/kotlin/net/corda/node/internal/artemis/ReactiveArtemisConsumer.kt @@ -39,7 +39,7 @@ private class MultiplexingReactiveArtemisConsumer(private val queueNames: Set createSession().apply { start() diff --git a/node/src/main/kotlin/net/corda/node/serialization/kryo/Kryo.kt b/node/src/main/kotlin/net/corda/node/serialization/kryo/Kryo.kt index 2772419a7c..6d2b2e4988 100644 --- a/node/src/main/kotlin/net/corda/node/serialization/kryo/Kryo.kt +++ b/node/src/main/kotlin/net/corda/node/serialization/kryo/Kryo.kt @@ -72,8 +72,9 @@ class ImmutableClassSerializer(val klass: KClass) : Serializer() val constructor = klass.primaryConstructor!! init { - // Verify that this class is immutable (all properties are final) - require(props.none { it is KMutableProperty<*> }) + props.forEach { + require(it !is KMutableProperty<*>) { "$it mutable property of class: ${klass} is unsupported" } + } } // Just a utility to help us catch cases where nodes are running out of sync versions. diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 28f2fb68e4..c75a7eb268 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -265,8 +265,8 @@ data class SecurityConfiguration(val authService: SecurityConfiguration.AuthServ val users: List? = null) { init { when (type) { - AuthDataSourceType.INMEMORY -> require(users != null && connection == null) - AuthDataSourceType.DB -> require(users == null && connection != null) + AuthDataSourceType.INMEMORY -> require(users != null && connection == null) { "In-memory authentication must specify a user list, and must not configure a database" } + AuthDataSourceType.DB -> require(users == null && connection != null) { "Database-backed authentication must not specify a user list, and must configure a database" } } } diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt index b2f54fc909..ecf512f9df 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt @@ -569,7 +569,7 @@ private class P2PMessagingConsumer( override fun start() { synchronized(this) { - require(!startedFlag) + require(!startedFlag){"Must not already be started"} drainingModeWasChangedEvents.filter { change -> change.switchedOn() }.doOnNext { initialAndExistingConsumer.switchTo(existingOnlyConsumer) }.subscribe() drainingModeWasChangedEvents.filter { change -> change.switchedOff() }.doOnNext { existingOnlyConsumer.switchTo(initialAndExistingConsumer) }.subscribe() subscriptions += existingOnlyConsumer.messages.doOnNext(messages::onNext).subscribe() 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 d5b3b0bc7d..4a745093dd 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 @@ -253,7 +253,7 @@ class RPCServer( private fun bindingRemovalArtemisMessageHandler(artemisMessage: ClientMessage) { lifeCycle.requireState(State.STARTED) val notificationType = artemisMessage.getStringProperty(ManagementHelper.HDR_NOTIFICATION_TYPE) - require(notificationType == CoreNotificationType.BINDING_REMOVED.name) + require(notificationType == CoreNotificationType.BINDING_REMOVED.name){"Message contained notification type of $notificationType instead of expected ${CoreNotificationType.BINDING_REMOVED.name}"} val clientAddress = artemisMessage.getStringProperty(ManagementHelper.HDR_ROUTING_NAME) log.warn("Detected RPC client disconnect on address $clientAddress, scheduling for reaping") invalidateClient(SimpleString(clientAddress)) @@ -262,7 +262,7 @@ class RPCServer( private fun bindingAdditionArtemisMessageHandler(artemisMessage: ClientMessage) { lifeCycle.requireState(State.STARTED) val notificationType = artemisMessage.getStringProperty(ManagementHelper.HDR_NOTIFICATION_TYPE) - require(notificationType == CoreNotificationType.BINDING_ADDED.name) + require(notificationType == CoreNotificationType.BINDING_ADDED.name){"Message contained notification type of $notificationType instead of expected ${CoreNotificationType.BINDING_ADDED.name}"} val clientAddress = SimpleString(artemisMessage.getStringProperty(ManagementHelper.HDR_ROUTING_NAME)) log.debug("RPC client queue created on address $clientAddress") 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 37f5a38d6c..5f2b1a71be 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 @@ -300,7 +300,7 @@ class NodeAttachmentService( private fun import(jar: InputStream, uploader: String?, filename: String?): AttachmentId { return database.transaction { withContractsInJar(jar) { contractClassNames, inputStream -> - require(inputStream !is JarInputStream) + require(inputStream !is JarInputStream){"Input stream must not be a JarInputStream"} // Read the file into RAM and then calculate its hash. The attachment must fit into memory. // TODO: Switch to a two-phase insert so we can handle attachments larger than RAM. diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/CountUpDownLatch.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/CountUpDownLatch.kt index 995ee4b2df..7db19406d6 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/CountUpDownLatch.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/CountUpDownLatch.kt @@ -1,7 +1,7 @@ package net.corda.node.services.statemachine -import co.paralleluniverse.strands.concurrent.AbstractQueuedSynchronizer import co.paralleluniverse.fibers.Suspendable +import co.paralleluniverse.strands.concurrent.AbstractQueuedSynchronizer /** * Quasar-compatible latch that may be incremented. @@ -56,7 +56,7 @@ class CountUpDownLatch(initialValue: Int) { } fun countDown(number: Int = 1) { - require(number > 0) + require(number > 0){"Number to count down by must be greater than 0"} sync.releaseShared(number) } 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 5bb11fa435..a27c0c09b5 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 @@ -193,7 +193,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, "Transaction context is missing. This might happen if a suspendable method is not annotated with @Suspendable annotation." } } else { - require(contextTransactionOrNull == null) + require(contextTransactionOrNull == null){"Transaction is marked as not present, but is not null"} } } @@ -388,7 +388,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, isDbTransactionOpenOnEntry = true, isDbTransactionOpenOnExit = false ) - require(continuation == FlowContinuation.ProcessEvents) + require(continuation == FlowContinuation.ProcessEvents){"Expected a continuation of type ${FlowContinuation.ProcessEvents}, found $continuation "} unpark(SERIALIZER_BLOCKER) } return uncheckedCast(processEventsUntilFlowIsResumed( diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt index 19e1284f83..1facf8d478 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt @@ -172,7 +172,7 @@ class SingleThreadedStateMachineManager( * @param allowedUnsuspendedFiberCount Optional parameter is used in some tests. */ override fun stop(allowedUnsuspendedFiberCount: Int) { - require(allowedUnsuspendedFiberCount >= 0) + require(allowedUnsuspendedFiberCount >= 0){"allowedUnsuspendedFiberCount must be greater than or equal to zero"} mutex.locked { if (stopping) throw IllegalStateException("Already stopping!") stopping = true @@ -775,10 +775,10 @@ class SingleThreadedStateMachineManager( ) { drainFlowEventQueue(flow) // final sanity checks - require(lastState.pendingDeduplicationHandlers.isEmpty()) - require(lastState.isRemoved) - require(lastState.checkpoint.subFlowStack.size == 1) - require(flow.fiber.id !in sessionToFlow.values) + require(lastState.pendingDeduplicationHandlers.isEmpty()) { "Flow cannot be removed until all pending deduplications have completed" } + require(lastState.isRemoved) { "Flow must be in removable state before removal" } + require(lastState.checkpoint.subFlowStack.size == 1) { "Checkpointed stack must be empty" } + require(flow.fiber.id !in sessionToFlow.values) { "Flow fibre must not be needed by an existing session" } flow.resultFuture.set(removalReason.flowReturnValue) lastState.flowLogic.progressTracker?.currentStep = ProgressTracker.DONE changesPublisher.onNext(StateMachineManager.Change.Removed(lastState.flowLogic, Try.Success(removalReason.flowReturnValue))) diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/interceptors/FiberDeserializationCheckingInterceptor.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/interceptors/FiberDeserializationCheckingInterceptor.kt index 463d5bf10c..7e6aaa52bd 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/interceptors/FiberDeserializationCheckingInterceptor.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/interceptors/FiberDeserializationCheckingInterceptor.kt @@ -2,17 +2,11 @@ package net.corda.node.services.statemachine.interceptors import co.paralleluniverse.fibers.Suspendable import net.corda.core.flows.StateMachineRunId -import net.corda.core.serialization.* +import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.internal.CheckpointSerializationContext import net.corda.core.serialization.internal.checkpointDeserialize import net.corda.core.utilities.contextLogger -import net.corda.node.services.statemachine.ActionExecutor -import net.corda.node.services.statemachine.Event -import net.corda.node.services.statemachine.FlowFiber -import net.corda.node.services.statemachine.FlowState -import net.corda.node.services.statemachine.FlowStateMachineImpl -import net.corda.node.services.statemachine.StateMachineState -import net.corda.node.services.statemachine.TransitionExecutor +import net.corda.node.services.statemachine.* import net.corda.node.services.statemachine.transitions.FlowContinuation import net.corda.node.services.statemachine.transitions.TransitionResult import java.util.concurrent.LinkedBlockingQueue @@ -69,7 +63,7 @@ class FiberDeserializationChecker { private var foundUnrestorableFibers: Boolean = false fun start(checkpointSerializationContext: CheckpointSerializationContext) { - require(checkerThread == null) + require(checkerThread == null){"Checking thread must not already be started"} checkerThread = thread(name = "FiberDeserializationChecker") { while (true) { val job = jobQueue.take() 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 6c636d9e74..24562519dc 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 @@ -372,7 +372,7 @@ class NodeRegistrationHelper( private class FixedPeriodLimitedRetrialStrategy(times: Int, private val period: Duration) : (Duration?) -> Duration? { init { - require(times > 0) + require(times > 0){"Retry attempts must be larger than zero"} } private var counter = times 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 08a4fa3873..c32217c429 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 @@ -94,7 +94,7 @@ private fun sender(rpc: CordaRPCOps, inputStream: InputStream, hash: SecureHash. val id = rpc.uploadAttachment(it) require(hash == id) { "Id was '$id' instead of '$hash'" } } - require(rpc.attachmentExists(hash)) + require(rpc.attachmentExists(hash)){"Attachment matching hash: $hash does not exist"} } val flowHandle = rpc.startTrackedFlow(::AttachmentDemoFlow, otherSideFuture.get(), notaryFuture.get(), hash) @@ -159,7 +159,7 @@ fun recipient(rpc: CordaRPCOps, webPort: Int) { if (wtx.attachments.isNotEmpty()) { if (wtx.outputs.isNotEmpty()) { val state = wtx.outputsOfType().single() - require(rpc.attachmentExists(state.hash)) + require(rpc.attachmentExists(state.hash)) {"attachment matching hash: ${state.hash} does not exist"} // Download the attachment via the Web endpoint. val connection = URL("http://localhost:$webPort/attachments/${state.hash}").openConnection() as HttpURLConnection @@ -207,7 +207,7 @@ class AttachmentContract : Contract { override fun verify(tx: LedgerTransaction) { val state = tx.outputsOfType().single() // we check that at least one has the matching hash, the other will be the contract - require(tx.attachments.any { it.id == state.hash }) + require(tx.attachments.any { it.id == state.hash }) {"At least one attachment in transaction must match hash ${state.hash}"} } object Command : TypeOnlyCommandData() diff --git a/samples/simm-valuation-demo/contracts-states/src/main/kotlin/net/corda/vega/contracts/PortfolioState.kt b/samples/simm-valuation-demo/contracts-states/src/main/kotlin/net/corda/vega/contracts/PortfolioState.kt index ac96e95d38..119bc683e4 100644 --- a/samples/simm-valuation-demo/contracts-states/src/main/kotlin/net/corda/vega/contracts/PortfolioState.kt +++ b/samples/simm-valuation-demo/contracts-states/src/main/kotlin/net/corda/vega/contracts/PortfolioState.kt @@ -40,7 +40,7 @@ data class PortfolioState(val portfolio: List, } override fun generateRevision(notary: Party, oldState: StateAndRef<*>, updatedValue: Update): TransactionBuilder { - require(oldState.state.data == this) + require(oldState.state.data == this){"Old state data does not match current state data"} val portfolio = updatedValue.portfolio ?: portfolio val valuation = updatedValue.valuation ?: valuation diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenter.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenter.kt index f1a422f22e..fb05ce1695 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenter.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenter.kt @@ -148,7 +148,9 @@ class ClassCarpenterImpl @JvmOverloads constructor (override val whitelist: Clas } } - require(schema.name in _loaded) + if (schema.name !in _loaded){ + throw ClassNotFoundException(schema.name) + } return _loaded[schema.name]!! } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/MetaCarpenter.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/MetaCarpenter.kt index b5e6593286..445c3ce7da 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/MetaCarpenter.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/MetaCarpenter.kt @@ -83,6 +83,7 @@ abstract class MetaCarpenterBase(val schemas: CarpenterMetaSchema, val cc: Class // 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 -> + require(newObject.name in schemas.dependencies[dependent]!!.second) schemas.dependencies[dependent]?.second?.remove(newObject.name) diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/SchemaFields.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/SchemaFields.kt index ba40dc20f8..8e639ce810 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/SchemaFields.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/SchemaFields.kt @@ -72,8 +72,7 @@ open class NonNullableField(field: Class) : ClassField(field) { } override fun nullTest(mv: MethodVisitor, slot: Int) { - require(name != unsetName) - + check(name != unsetName) {"Property this.name cannot be $unsetName"} if (!field.isPrimitive) { with(mv) { visitVarInsn(ALOAD, 0) // load this @@ -109,7 +108,7 @@ class NullableField(field: Class) : ClassField(field) { } override fun nullTest(mv: MethodVisitor, slot: Int) { - require(name != unsetName) + require(name != unsetName){"Property this.name cannot be $unsetName"} } } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContract.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContract.kt index 7a335b9968..fa38493fce 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContract.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContract.kt @@ -79,7 +79,7 @@ data class DummyContract(val blank: Any? = null) : Contract { */ @JvmStatic fun move(priors: List>, newOwner: AbstractParty): TransactionBuilder { - require(priors.isNotEmpty()) + require(priors.isNotEmpty()){"States to move to new owner must not be empty"} val priorState = priors[0].state.data val (cmd, state) = priorState.withNewOwner(newOwner) return TransactionBuilder(notary = priors[0].state.notary).withItems( From 4c88e26a4644ae86796af8723515c4ab72a1d9fb Mon Sep 17 00:00:00 2001 From: Roger Willis Date: Sun, 18 Nov 2018 11:35:56 +0000 Subject: [PATCH 03/19] Fixed incorrect diagram. (#4212) --- docs/source/resources/state-hierarchy.png | Bin 174750 -> 135818 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/source/resources/state-hierarchy.png b/docs/source/resources/state-hierarchy.png index 232ac7d1f59530091bc77047725d9753376305e0..2799433431348790b74650c5e72ee6fabb1e32b8 100644 GIT binary patch literal 135818 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 JDz)&r_8&NpL{|U+ literal 174750 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 rEJRGmXPZ)}&-SD%#7IlTTp-*_)h*NSh9Iy87 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 #dT^CfQ%;@cgOs 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 Date: Sun, 18 Nov 2018 12:36:21 +0000 Subject: [PATCH 04/19] [CORDA-2004]: Update CLI UX guidelines for backwards compatibility (#4248) * Update CLI UX guidelines for backwards compatibility * De-lousing * Spelling mistake --- docs/source/cli-ux-guidelines.rst | 90 +++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 4 deletions(-) diff --git a/docs/source/cli-ux-guidelines.rst b/docs/source/cli-ux-guidelines.rst index 41b3ac81df..d22dd8cc26 100644 --- a/docs/source/cli-ux-guidelines.rst +++ b/docs/source/cli-ux-guidelines.rst @@ -73,14 +73,18 @@ Adding a new option Parameter stability ~~~~~~~~~~~~~~~~~~~ -* Avoid removing parameters. If, for some reason, a parameter needs to be renamed, add a new parameter with the new name and deprecate the old parameter, or alternatively keep both versions of the parameter. +* Avoid removing parameters. If, for some reason, a parameter needs to be renamed, add a new parameter with the new name and deprecate the old parameter, or alternatively +keep both versions of the parameter. See :ref:`cli-ux-backwards-compatibility` for more information. +Notes for adding a new a command line application +------------------------------------------------- + The ``CordaCliWrapper`` base class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The ``CordaCliWrapper`` base class from the ``cliutils`` module should be used as a base where practicable, this will provide a set of default options out of the box. -In order to use it, create a class containing your command line options using the syntax provided at (see the `picocli`_ website for more information) +In order to use it, create a class containing your command line options using the syntax provided at (see the `picocli `_ website for more information) .. container:: codeset @@ -133,7 +137,7 @@ Then in your ``main()`` method: Application behavior --------------------- +~~~~~~~~~~~~~~~~~~~~ * Set exit codes using exitProcess. @@ -144,4 +148,82 @@ Application behavior * Make sure all exit codes are documented with recommended remedies where applicable. * Your ``--help`` text or other docs should ideally include examples. Writing examples is also a good way to find out if your program requires a dozen flags to do anything. * Don’t print logging output to the console unless the user requested it via a ``-–verbose`` flag (conventionally shortened to ``-v``). Logs should be either suppressed or saved to a text file during normal usage, except for errors, which are always OK to print. -* Don't print stack traces to the console. Stack traces can be added to logging files, but the user should see as meaningful error description as possible. \ No newline at end of file +* Don't print stack traces to the console. Stack traces can be added to logging files, but the user should see as meaningful error description as possible. + +.. _cli-ux-backwards-compatibility: + +Backwards Compatibility +----------------------- + +Our commitment to API stability (See :doc:`api-scanner` for more information) extends to new versions of our CLI tools. Removing and renaming +parameters may cause existing scripts users may have written to fail, and should be avoided unless absolutely necessary. + +Deprecating command line parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Command line parameters that are no longer necessary should be deprecated rather than removed. Picocli allows parameters to be hidden by use +of the ``hidden`` option, for example: + +.. container:: codeset + + .. sourcecode:: kotlin + + import net.corda.cliutils.CordaCliWrapper + + class UsefulUtility : CordaCliWrapper("useful-utility", "A command line utility that is super useful!") { + @Option(names = ["--no-longer-useful", "-u"], + hidden = true, + description = ["The option is no longer useful. Don't show it in the help output."] + ) + private var noLongerUseful: Boolean = false + + override fun runProgram(): Int { + if (noLongerUseful) // print a warning to the log to let the user know the option has been deprecated + logger.warn("The --no-longer-useful option is deprecated, please use the --alternatively-useful option instead") + // do some stuff + return UsefulUtilityExitCodes.SUCCESS + } + } + +This will cause the option to still be usable, but means it won't be shown when ``--help`` is called. As a result, existing scripts dependent +on the parameter will still run, but new users will be directed to the replacement. + +Changing the type of existing command line parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Don't change the type of an existing command line parameter if that change would not be backwards compatible. For example, adding a +value to an enumeration based parameter would be fine, but removing one would not. Instead of changing the type, consider adding a new parameter, +deprecating the old parameter as described above, and redirecting inputs from the old parameter to the new parameter internally. + +Testing backwards compatibility +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When adding a new command line tool, a backwards compatibility test should be created by adding the ``test-cli`` as a test dependency of your project +and then creating a test class that extends ``CliBackwardsCompatibleTest`` for the class, like so: + +.. container:: codeset + + .. sourcecode:: kotlin + + import net.corda.testing.CliBackwardsCompatibleTest + + class UsefulUtilityBackwardsCompatibleTest : CliBackwardsCompatibleTest(UsefulUtility::class.java) + +The test will search for a YAML file on the class path named ``.yml`` which details the names, types and possible +options of parameters, and compares it to the options of the current class to make sure they are compatible. + +In order to generate the file, create and run the test for your application. The test will fail, but the test output +will contain the YAML for the current state of the tool. This can be copied and then pasted into a correctly named ``.yml`` +file in the resources directory of the project. + +Release process +~~~~~~~~~~~~~~~ + +As part of the release process, the release manager should regenerate the YAML files for each command line tool by following the following steps: + +* Check out the release branch +* Delete the ``.yml`` file for the tool +* Re-run the backwards compatibility test for the tool +* Copy the resulting YAML from the test output +* Check out the master branch +* Replace the text in ``.yml`` with the text generated on the release branch From f66944cac5ac039b519c4981e4b228424e95b66c Mon Sep 17 00:00:00 2001 From: Dominic Fox <40790090+distributedleetravis@users.noreply.github.com> Date: Mon, 19 Nov 2018 11:03:32 +0000 Subject: [PATCH 05/19] CORDA-2099 remote type model (#4179) * CORDA-2099 create remote type model * Comments * Test the class-carpenting type loader * Comment on cache usage * Pull changes from main serialisation branch * Add missing file * Minor tweaks --- .../internal/amqp/AMQPRemoteTypeModel.kt | 209 ++++++++++++++++++ .../internal/amqp/AMQPTypeIdentifierParser.kt | 190 ++++++++++++++++ .../internal/amqp/AMQPTypeIdentifiers.kt | 63 ++++++ .../model/CarpentryDependencyGraph.kt | 124 +++++++++++ .../internal/model/RemoteTypeCarpenter.kt | 84 +++++++ .../internal/model/RemoteTypeInformation.kt | 196 ++++++++++++++++ .../internal/model/TypeIdentifier.kt | 149 +++++++++++-- .../internal/model/TypeLoader.kt | 61 +++++ .../internal/amqp/AMQPRemoteTypeModelTests.kt | 84 +++++++ .../amqp/AMQPTypeIdentifierParserTests.kt | 197 +++++++++++++++++ .../model/ClassCarpentingTypeLoaderTests.kt | 112 ++++++++++ 11 files changed, 1449 insertions(+), 20 deletions(-) create mode 100644 serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPRemoteTypeModel.kt create mode 100644 serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPTypeIdentifierParser.kt create mode 100644 serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPTypeIdentifiers.kt create mode 100644 serialization/src/main/kotlin/net/corda/serialization/internal/model/CarpentryDependencyGraph.kt create mode 100644 serialization/src/main/kotlin/net/corda/serialization/internal/model/RemoteTypeCarpenter.kt create mode 100644 serialization/src/main/kotlin/net/corda/serialization/internal/model/RemoteTypeInformation.kt create mode 100644 serialization/src/main/kotlin/net/corda/serialization/internal/model/TypeLoader.kt create mode 100644 serialization/src/test/kotlin/net/corda/serialization/internal/amqp/AMQPRemoteTypeModelTests.kt create mode 100644 serialization/src/test/kotlin/net/corda/serialization/internal/amqp/AMQPTypeIdentifierParserTests.kt create mode 100644 serialization/src/test/kotlin/net/corda/serialization/internal/model/ClassCarpentingTypeLoaderTests.kt diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPRemoteTypeModel.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPRemoteTypeModel.kt new file mode 100644 index 0000000000..f717f87f3b --- /dev/null +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPRemoteTypeModel.kt @@ -0,0 +1,209 @@ +package net.corda.serialization.internal.amqp + +import net.corda.serialization.internal.model.* +import java.io.NotSerializableException +import java.util.* + +/** + * Interprets AMQP [Schema] information to obtain [RemoteTypeInformation], caching by [TypeDescriptor]. + */ +class AMQPRemoteTypeModel { + + private val cache: MutableMap = DefaultCacheProvider.createCache() + + /** + * Interpret a [Schema] to obtain a [Map] of all of the [RemoteTypeInformation] contained therein, indexed by + * [TypeDescriptor]. + * + * A [Schema] contains a set of [TypeNotation]s, which we recursively convert into [RemoteTypeInformation], + * associating each new piece of [RemoteTypeInformation] with the [TypeDescriptor] attached to it in the schema. + * + * We start by building a [Map] of [TypeNotation] by [TypeIdentifier], using [AMQPTypeIdentifierParser] to convert + * AMQP type names into [TypeIdentifier]s. This is used as a lookup for resolving notations that are referred to by + * type name from other notations, e.g. the types of properties. + * + * We also build a [Map] of [TypeNotation] by [TypeDescriptor], which we then convert into [RemoteTypeInformation] + * while merging with the cache. + */ + fun interpret(serializationSchemas: SerializationSchemas): Map { + val (schema, transforms) = serializationSchemas + val notationLookup = schema.types.associateBy { it.name.typeIdentifier } + val byTypeDescriptor = schema.types.associateBy { it.typeDescriptor } + val enumTransformsLookup = transforms.types.asSequence().map { (name, transformSet) -> + name.typeIdentifier to interpretTransformSet(transformSet) + }.toMap() + + val interpretationState = InterpretationState(notationLookup, enumTransformsLookup, cache, emptySet()) + + return byTypeDescriptor.mapValues { (typeDescriptor, typeNotation) -> + cache.getOrPut(typeDescriptor) { interpretationState.run { typeNotation.name.typeIdentifier.interpretIdentifier() } } + } + } + + data class InterpretationState(val notationLookup: Map, + val enumTransformsLookup: Map, + val cache: MutableMap, + val seen: Set) { + + private inline fun forgetSeen(block: InterpretationState.() -> T): T = + withSeen(emptySet(), block) + + private inline fun withSeen(typeIdentifier: TypeIdentifier, block: InterpretationState.() -> T): T = + withSeen(seen + typeIdentifier, block) + + private inline fun withSeen(seen: Set, block: InterpretationState.() -> T): T = + copy(seen = seen).run(block) + + /** + * Follow a [TypeIdentifier] to the [TypeNotation] associated with it in the lookup, and interpret that notation. + * If there is no such notation, interpret the [TypeIdentifier] directly into [RemoteTypeInformation]. + * + * If we have visited this [TypeIdentifier] before while traversing the graph of related [TypeNotation]s, then we + * know we have hit a cycle and respond accordingly. + */ + fun TypeIdentifier.interpretIdentifier(): RemoteTypeInformation = + if (this in seen) RemoteTypeInformation.Cycle(this) { forgetSeen { interpretIdentifier() } } + else withSeen(this) { + val identifier = this@interpretIdentifier + notationLookup[identifier]?.interpretNotation(identifier) ?: interpretNoNotation() + } + + /** + * Either fetch from the cache, or interpret, cache, and return, the [RemoteTypeInformation] corresponding to this + * [TypeNotation]. + */ + private fun TypeNotation.interpretNotation(identifier: TypeIdentifier): RemoteTypeInformation = + cache.getOrPut(typeDescriptor) { + when (this) { + is CompositeType -> interpretComposite(identifier) + is RestrictedType -> interpretRestricted(identifier) + } + } + + /** + * Interpret the properties, interfaces and type parameters in this [TypeNotation], and return suitable + * [RemoteTypeInformation]. + */ + private fun CompositeType.interpretComposite(identifier: TypeIdentifier): RemoteTypeInformation { + val properties = fields.asSequence().map { it.interpret() }.toMap() + val typeParameters = identifier.interpretTypeParameters() + val interfaceIdentifiers = provides.map { name -> name.typeIdentifier } + val isInterface = identifier in interfaceIdentifiers + val interfaces = interfaceIdentifiers.mapNotNull { interfaceIdentifier -> + if (interfaceIdentifier == identifier) null + else interfaceIdentifier.interpretIdentifier() + } + + return if (isInterface) RemoteTypeInformation.AnInterface(typeDescriptor, identifier, properties, interfaces, typeParameters) + else RemoteTypeInformation.Composable(typeDescriptor, identifier, properties, interfaces, typeParameters) + } + + /** + * Type parameters are read off from the [TypeIdentifier] we translated the AMQP type name into. + */ + private fun TypeIdentifier.interpretTypeParameters(): List = when (this) { + is TypeIdentifier.Parameterised -> parameters.map { it.interpretIdentifier() } + else -> emptyList() + } + + /** + * Interpret a [RestrictedType] into suitable [RemoteTypeInformation]. + */ + private fun RestrictedType.interpretRestricted(identifier: TypeIdentifier): RemoteTypeInformation = when (identifier) { + is TypeIdentifier.Parameterised -> + RemoteTypeInformation.Parameterised( + typeDescriptor, + identifier, + identifier.interpretTypeParameters()) + is TypeIdentifier.ArrayOf -> + RemoteTypeInformation.AnArray( + typeDescriptor, + identifier, + identifier.componentType.interpretIdentifier()) + is TypeIdentifier.Unparameterised -> + if (choices.isEmpty()) { + RemoteTypeInformation.Unparameterised( + typeDescriptor, + identifier) + } else RemoteTypeInformation.AnEnum( + typeDescriptor, + identifier, + choices.map { it.name }, + enumTransformsLookup[identifier] ?: EnumTransforms.empty) + else -> throw NotSerializableException("Cannot interpret restricted type $this") + } + + /** + * Interpret a [Field] into a name/[RemotePropertyInformation] pair. + */ + private fun Field.interpret(): Pair { + val identifier = type.typeIdentifier + + // A type of "*" is replaced with the value of the "requires" field + val fieldTypeIdentifier = if (identifier == TypeIdentifier.TopType && !requires.isEmpty()) { + requires[0].typeIdentifier + } else identifier + + // We convert Java Object types to Java primitive types if the field is mandatory. + val fieldType = fieldTypeIdentifier.forcePrimitive(mandatory).interpretIdentifier() + + return name to RemotePropertyInformation( + fieldType, + mandatory) + } + + /** + * If there is no [TypeNotation] in the [Schema] matching a given [TypeIdentifier], we interpret the [TypeIdentifier] + * directly. + */ + private fun TypeIdentifier.interpretNoNotation(): RemoteTypeInformation = + when (this) { + is TypeIdentifier.TopType -> RemoteTypeInformation.Top + is TypeIdentifier.UnknownType -> RemoteTypeInformation.Unknown + is TypeIdentifier.ArrayOf -> + RemoteTypeInformation.AnArray( + name, + this, + componentType.interpretIdentifier()) + is TypeIdentifier.Parameterised -> + RemoteTypeInformation.Parameterised( + name, + this, + parameters.map { it.interpretIdentifier() }) + else -> RemoteTypeInformation.Unparameterised(name, this) + } + } +} + +private fun interpretTransformSet(transformSet: EnumMap>): EnumTransforms { + val defaultTransforms = transformSet[TransformTypes.EnumDefault]?.toList() ?: emptyList() + val defaults = defaultTransforms.associate { transform -> (transform as EnumDefaultSchemaTransform).new to transform.old } + val renameTransforms = transformSet[TransformTypes.Rename]?.toList() ?: emptyList() + val renames = renameTransforms.associate { transform -> (transform as RenameSchemaTransform).to to transform.from } + + return EnumTransforms(defaults, renames) +} + +private val TypeNotation.typeDescriptor: String get() = descriptor.name?.toString() ?: + throw NotSerializableException("Type notation has no type descriptor: $this") + +private val String.typeIdentifier get(): TypeIdentifier = AMQPTypeIdentifierParser.parse(this) + +/** + * Force e.g. [java.lang.Integer] to `int`, if it is the type of a mandatory field. + */ +private fun TypeIdentifier.forcePrimitive(mandatory: Boolean) = + if (mandatory) primitives[this] ?: this + else this + +private val primitives = sequenceOf( + Boolean::class, + Byte::class, + Char::class, + Int::class, + Short::class, + Long::class, + Float::class, + Double::class).associate { + TypeIdentifier.forClass(it.javaObjectType) to TypeIdentifier.forClass(it.javaPrimitiveType!!) +} \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPTypeIdentifierParser.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPTypeIdentifierParser.kt new file mode 100644 index 0000000000..4309b03bbe --- /dev/null +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPTypeIdentifierParser.kt @@ -0,0 +1,190 @@ +package net.corda.serialization.internal.amqp + +import com.google.common.primitives.Primitives +import net.corda.serialization.internal.model.TypeIdentifier +import org.apache.qpid.proton.amqp.* +import java.io.NotSerializableException +import java.lang.StringBuilder +import java.util.* + +/** + * Thrown if the type string parser enters an illegal state. + */ +class IllegalTypeNameParserStateException(message: String): NotSerializableException(message) + +/** + * Provides a state machine which knows how to parse AMQP type strings into [TypeIdentifier]s. + */ +object AMQPTypeIdentifierParser { + + internal const val MAX_TYPE_PARAM_DEPTH = 32 + private const val MAX_ARRAY_DEPTH = 32 + + /** + * Given a string representing a serialized AMQP type, construct a TypeIdentifier for that string. + * + * @param typeString The AMQP type string to parse + * @return A [TypeIdentifier] representing the type represented by the input string. + */ + fun parse(typeString: String): TypeIdentifier { + validate(typeString) + return typeString.fold(ParseState.ParsingRawType(null)) { state, c -> + state.accept(c) + }.getTypeIdentifier() + } + + // Make sure our inputs aren't designed to blow things up. + private fun validate(typeString: String) { + var maxTypeParamDepth = 0 + var typeParamdepth = 0 + + var maxArrayDepth = 0 + var wasArray = false + var arrayDepth = 0 + + for (c in typeString) { + if (c.isWhitespace() || c.isJavaIdentifierPart() || c.isJavaIdentifierStart() || + c == '.' || c == ',' || c == '?' || c == '*') continue + + when(c) { + '<' -> maxTypeParamDepth = Math.max(++typeParamdepth, typeParamdepth) + '>' -> typeParamdepth-- + '[' -> { + arrayDepth = if (wasArray) arrayDepth + 2 else 1 + maxArrayDepth = Math.max(maxArrayDepth,arrayDepth) + } + ']' -> arrayDepth-- + else -> throw IllegalTypeNameParserStateException("Type name '$typeString' contains illegal character '$c'") + } + wasArray = c == ']' + } + if (maxTypeParamDepth >= MAX_TYPE_PARAM_DEPTH) + throw IllegalTypeNameParserStateException("Nested depth of type parameters exceeds maximum of $MAX_TYPE_PARAM_DEPTH") + + if (maxArrayDepth >= MAX_ARRAY_DEPTH) + throw IllegalTypeNameParserStateException("Nested depth of arrays exceeds maximum of $MAX_ARRAY_DEPTH") + } + + private sealed class ParseState { + abstract val parent: ParseState.ParsingParameterList? + abstract fun accept(c: Char): ParseState + abstract fun getTypeIdentifier(): TypeIdentifier + + fun unexpected(c: Char): ParseState = throw IllegalTypeNameParserStateException("Unexpected character: '$c'") + fun notInParameterList(c: Char): ParseState = + throw IllegalTypeNameParserStateException("'$c' encountered, but not parsing type parameter list") + + /** + * We are parsing a raw type name, either at the top level or as part of a list of type parameters. + */ + data class ParsingRawType(override val parent: ParseState.ParsingParameterList?, val buffer: StringBuilder = StringBuilder()) : ParseState() { + override fun accept(c: Char) = when (c) { + ',' -> + if (parent == null) notInParameterList(c) + else ParsingRawType(parent.addParameter(getTypeIdentifier())) + '[' -> ParsingArray(getTypeIdentifier(), parent) + ']' -> unexpected(c) + '<' -> ParsingRawType(ParsingParameterList(getTypeName(), parent)) + '>' -> parent?.addParameter(getTypeIdentifier())?.accept(c) ?: notInParameterList(c) + else -> apply { buffer.append(c) } + } + + private fun getTypeName(): String { + val typeName = buffer.toString().trim() + if (typeName.contains(' ')) + throw IllegalTypeNameParserStateException("Illegal whitespace in type name $typeName") + return typeName + } + + override fun getTypeIdentifier(): TypeIdentifier { + val typeName = getTypeName() + return when (typeName) { + "*" -> TypeIdentifier.TopType + "?" -> TypeIdentifier.UnknownType + in simplified -> simplified[typeName]!! + else -> TypeIdentifier.Unparameterised(typeName) + } + } + } + + /** + * We are parsing a parameter list, and expect either to start a new parameter, add array-ness to the last + * parameter we have, or end the list. + */ + data class ParsingParameterList(val typeName: String, override val parent: ParsingParameterList?, val parameters: List = emptyList()) : ParseState() { + override fun accept(c: Char) = when (c) { + ' ' -> this + ',' -> ParsingRawType(this) + '[' -> + if (parameters.isEmpty()) unexpected(c) + else ParsingArray( + // Start adding array-ness to the last parameter we have. + parameters[parameters.lastIndex], + // Take a copy of this state, dropping the last parameter which will be added back on + // when array parsing completes. + copy(parameters = parameters.subList(0, parameters.lastIndex))) + '>' -> parent?.addParameter(getTypeIdentifier()) ?: Complete(getTypeIdentifier()) + else -> unexpected(c) + } + + fun addParameter(parameter: TypeIdentifier) = copy(parameters = parameters + parameter) + + override fun getTypeIdentifier() = TypeIdentifier.Parameterised(typeName, null, parameters) + } + + /** + * We are adding array-ness to some type identifier. + */ + data class ParsingArray(val componentType: TypeIdentifier, override val parent: ParseState.ParsingParameterList?) : ParseState() { + override fun accept(c: Char) = when (c) { + ' ' -> this + 'p' -> ParsingArray(forcePrimitive(componentType), parent) + ']' -> parent?.addParameter(getTypeIdentifier()) ?: Complete(getTypeIdentifier()) + else -> unexpected(c) + } + + override fun getTypeIdentifier() = TypeIdentifier.ArrayOf(componentType) + + private fun forcePrimitive(componentType: TypeIdentifier): TypeIdentifier = + TypeIdentifier.forClass(Primitives.unwrap(componentType.getLocalType().asClass())) + } + + /** + * We have a complete type identifier, and all we can do to it is add array-ness. + */ + data class Complete(val identifier: TypeIdentifier) : ParseState() { + override val parent: ParseState.ParsingParameterList? get() = null + override fun accept(c: Char): ParseState = when (c) { + ' ' -> this + '[' -> ParsingArray(identifier, null) + else -> unexpected(c) + } + + override fun getTypeIdentifier() = identifier + } + } + + private val simplified = mapOf( + "string" to String::class, + "boolean" to Boolean::class, + "byte" to Byte::class, + "char" to Char::class, + "int" to Int::class, + "short" to Short::class, + "long" to Long::class, + "double" to Double::class, + "float" to Float::class, + "ubyte" to UnsignedByte::class, + "uint" to UnsignedInteger::class, + "ushort" to UnsignedShort::class, + "ulong" to UnsignedLong::class, + "decimal32" to Decimal32::class, + "decimal64" to Decimal64::class, + "decimal128" to Decimal128::class, + "binary" to ByteArray::class, + "timestamp" to Date::class, + "uuid" to UUID::class, + "symbol" to Symbol::class).mapValues { (_, v) -> + TypeIdentifier.forClass(v.javaObjectType) + } +} \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPTypeIdentifiers.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPTypeIdentifiers.kt new file mode 100644 index 0000000000..9b227843b3 --- /dev/null +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPTypeIdentifiers.kt @@ -0,0 +1,63 @@ +package net.corda.serialization.internal.amqp + +import net.corda.serialization.internal.model.TypeIdentifier +import org.apache.qpid.proton.amqp.* +import java.lang.reflect.Type +import java.util.* + +object AMQPTypeIdentifiers { + fun isPrimitive(type: Type): Boolean = isPrimitive(TypeIdentifier.forGenericType(type)) + fun isPrimitive(typeIdentifier: TypeIdentifier) = typeIdentifier in primitiveTypeNamesByName + + fun primitiveTypeName(type: Type): String? = + primitiveTypeNamesByName[TypeIdentifier.forGenericType(type)] + + private val primitiveTypeNamesByName = sequenceOf( + Character::class to "char", + Char::class to "char", + Boolean::class to "boolean", + Byte::class to "byte", + UnsignedByte::class to "ubyte", + Short::class to "short", + UnsignedShort::class to "ushort", + Int::class to "int", + UnsignedInteger::class to "uint", + Long::class to "long", + UnsignedLong::class to "ulong", + Float::class to "float", + Double::class to "double", + Decimal32::class to "decimal32", + Decimal64::class to "decimal62", + Decimal128::class to "decimal128", + Date::class to "timestamp", + UUID::class to "uuid", + ByteArray::class to "binary", + String::class to "string", + Symbol::class to "symbol") + .flatMap { (klass, name) -> + val typeIdentifier = TypeIdentifier.forClass(klass.javaObjectType) + val primitiveTypeIdentifier = klass.javaPrimitiveType?.let { TypeIdentifier.forClass(it) } + if (primitiveTypeIdentifier == null) sequenceOf(typeIdentifier to name) + else sequenceOf(typeIdentifier to name, primitiveTypeIdentifier to name) + }.toMap() + + fun nameForType(typeIdentifier: TypeIdentifier): String = when(typeIdentifier) { + is TypeIdentifier.Erased -> typeIdentifier.name + is TypeIdentifier.Unparameterised -> primitiveTypeNamesByName[typeIdentifier] ?: typeIdentifier.name + is TypeIdentifier.UnknownType, + is TypeIdentifier.TopType -> "?" + is TypeIdentifier.ArrayOf -> + if (typeIdentifier == primitiveByteArrayType) "binary" + else nameForType(typeIdentifier.componentType) + + if (typeIdentifier.componentType is TypeIdentifier.Unparameterised && + typeIdentifier.componentType.isPrimitive) "[p]" + else "[]" + is TypeIdentifier.Parameterised -> typeIdentifier.name + typeIdentifier.parameters.joinToString(", ", "<", ">") { + nameForType(it) + } + } + + private val primitiveByteArrayType = TypeIdentifier.ArrayOf(TypeIdentifier.forClass(Byte::class.javaPrimitiveType!!)) + + fun nameForType(type: Type): String = nameForType(TypeIdentifier.forGenericType(type)) +} \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/model/CarpentryDependencyGraph.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/model/CarpentryDependencyGraph.kt new file mode 100644 index 0000000000..6b6edfac88 --- /dev/null +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/model/CarpentryDependencyGraph.kt @@ -0,0 +1,124 @@ +package net.corda.serialization.internal.model + +import java.io.NotSerializableException +import java.lang.reflect.Type + +/** + * Once we have the complete graph of types requiring carpentry to hand, we can use it to sort those types in reverse- + * dependency order, i.e. beginning with those types that have no dependencies on other types, then the types that + * depended on those types, and so on. This means we can feed types directly to the [RemoteTypeCarpenter], and don't + * have to use the [CarpenterMetaSchema]. + * + * @param typesRequiringCarpentry The set of [RemoteTypeInformation] for types that are not reachable by the current + * classloader. + */ +class CarpentryDependencyGraph private constructor(private val typesRequiringCarpentry: Set) { + + companion object { + /** + * Sort the [typesRequiringCarpentry] into reverse-dependency order, then pass them to the provided + * [Type]-builder, collating the results into a [Map] of [Type] by [TypeIdentifier] + */ + fun buildInReverseDependencyOrder( + typesRequiringCarpentry: Set, + getOrBuild: (RemoteTypeInformation) -> Type): Map = + CarpentryDependencyGraph(typesRequiringCarpentry).buildInOrder(getOrBuild) + } + + /** + * A map of inbound edges by node. + * + * A [RemoteTypeInformation] map key is a type that requires other types to have been constructed before it can be + * constructed. + * + * Each [RemoteTypeInformation] in the corresponding [Set] map value is one of the types that the key-type depends on. + * + * No key ever maps to an empty set: types with no dependencies are not included in this map. + */ + private val dependencies = mutableMapOf>() + + /** + * If it is in [typesRequiringCarpentry], then add an edge from [dependee] to this type to the [dependencies] graph. + */ + private fun RemoteTypeInformation.dependsOn(dependee: RemoteTypeInformation) = dependsOn(listOf(dependee)) + + /** + * Add an edge from each of these [dependees] that are in [typesRequiringCarpentry] to this type to the + * [dependencies] graph. + */ + private fun RemoteTypeInformation.dependsOn(dependees: Collection) { + val dependeesInTypesRequiringCarpentry = dependees.filter { it in typesRequiringCarpentry } + if (dependeesInTypesRequiringCarpentry.isEmpty()) return // we don't want to put empty sets into the map. + dependencies.compute(this) { _, dependees -> + dependees?.apply { addAll(dependeesInTypesRequiringCarpentry) } ?: + dependeesInTypesRequiringCarpentry.toMutableSet() + } + } + + /** + * Traverses each of the [typesRequiringCarpentry], building (or obtaining from a cache) the corresponding [Type] + * and populating them into a [Map] of [Type] by [TypeIdentifier]. + */ + private fun buildInOrder(getOrBuild: (RemoteTypeInformation) -> Type): Map { + typesRequiringCarpentry.forEach { it.recordDependencies() } + + return topologicalSort(typesRequiringCarpentry).associate { information -> + information.typeIdentifier to getOrBuild(information) + } + } + + /** + * Record appropriate dependencies for each type of [RemoteTypeInformation] + */ + private fun RemoteTypeInformation.recordDependencies() = when (this) { + is RemoteTypeInformation.Composable -> { + dependsOn(typeParameters) + dependsOn(interfaces) + dependsOn(properties.values.map { it.type }) + } + is RemoteTypeInformation.AnInterface -> { + dependsOn(typeParameters) + dependsOn(interfaces) + dependsOn(properties.values.map { it.type }) + } + is RemoteTypeInformation.AnArray -> dependsOn(componentType) + is RemoteTypeInformation.Parameterised -> dependsOn(typeParameters) + else -> {} + } + + /** + * Separate out those [types] which have [noDependencies] from those which still have dependencies. + * + * Remove the types with no dependencies from the graph, identifying which types are left with no inbound dependees + * as a result, then return the types with no dependencies concatenated with the [topologicalSort] of the remaining + * types, minus the newly-independent types. + */ + private fun topologicalSort( + types: Set, + noDependencies: Set = types - dependencies.keys): Sequence { + // Types which still have dependencies. + val remaining = dependencies.keys.toSet() + + // Remove the types which have no dependencies from the dependencies of the remaining types, and identify + // those types which have no dependencies left after we've done this. + val newlyIndependent = dependencies.asSequence().mapNotNull { (dependent, dependees) -> + dependees.removeAll(noDependencies) + if (dependees.isEmpty()) dependent else null + }.toSet() + + // If there are still types with dependencies, and we have no dependencies we can remove, then we can't continue. + if (newlyIndependent.isEmpty() && dependencies.isNotEmpty()) { + throw NotSerializableException( + "Cannot build dependencies for " + + dependencies.keys.map { it.typeIdentifier.prettyPrint(false) }) + } + + // Remove the types which have no dependencies remaining, maintaining the invariant that no key maps to an + // empty set. + dependencies.keys.removeAll(newlyIndependent) + + // Return the types that had no dependencies, then recurse to process the remainder. + return noDependencies.asSequence() + + if (dependencies.isEmpty()) newlyIndependent.asSequence() else topologicalSort(remaining, newlyIndependent) + } +} \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/model/RemoteTypeCarpenter.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/model/RemoteTypeCarpenter.kt new file mode 100644 index 0000000000..f9e1341a23 --- /dev/null +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/model/RemoteTypeCarpenter.kt @@ -0,0 +1,84 @@ +package net.corda.serialization.internal.model + +import net.corda.serialization.internal.amqp.asClass +import net.corda.serialization.internal.carpenter.* +import java.io.NotSerializableException +import java.lang.reflect.Type + +typealias PropertyName = String + +/** + * Constructs [Type]s using [RemoteTypeInformation]. + */ +interface RemoteTypeCarpenter { + fun carpent(typeInformation: RemoteTypeInformation): Type +} + +/** + * A [RemoteTypeCarpenter] that converts [RemoteTypeInformation] into [Schema] objects for the [ClassCarpenter] to use. + */ +class SchemaBuildingRemoteTypeCarpenter(private val carpenter: ClassCarpenter): RemoteTypeCarpenter { + + private val classLoader: ClassLoader get() = carpenter.classloader + + override fun carpent(typeInformation: RemoteTypeInformation): Type { + try { + when (typeInformation) { + is RemoteTypeInformation.AnInterface -> typeInformation.carpentInterface() + is RemoteTypeInformation.Composable -> typeInformation.carpentComposable() + is RemoteTypeInformation.AnEnum -> typeInformation.carpentEnum() + else -> { + } // Anything else, such as arrays, will be taken care of by the above + } + } catch (e: ClassCarpenterException) { + throw NotSerializableException("${typeInformation.typeIdentifier.name}: ${e.message}") + } + return typeInformation.typeIdentifier.getLocalType(classLoader) + } + + private val RemoteTypeInformation.erasedLocalClass get() = typeIdentifier.getLocalType(classLoader).asClass() + + private fun RemoteTypeInformation.AnInterface.carpentInterface() { + val fields = getFields(typeIdentifier.name, properties) + + val schema = CarpenterSchemaFactory.newInstance( + name = typeIdentifier.name, + fields = fields, + interfaces = getInterfaces(typeIdentifier.name, interfaces), + isInterface = true) + carpenter.build(schema) + } + + private fun RemoteTypeInformation.Composable.carpentComposable() { + val fields = getFields(typeIdentifier.name, properties) + + val schema = CarpenterSchemaFactory.newInstance( + name = typeIdentifier.name, + fields = fields, + interfaces = getInterfaces(typeIdentifier.name, interfaces), + isInterface = false) + carpenter.build(schema) + } + + private fun getFields(ownerName: String, properties: Map) = + properties.mapValues { (name, property) -> + try { + FieldFactory.newInstance(property.isMandatory, name, property.type.erasedLocalClass) + } catch (e: ClassNotFoundException) { + throw UncarpentableException(ownerName, name, property.type.typeIdentifier.name) + } + } + + private fun getInterfaces(ownerName: String, interfaces: List): List> = + interfaces.map { + try { + it.erasedLocalClass + } catch (e: ClassNotFoundException) { + throw UncarpentableException(ownerName, "[interface]", it.typeIdentifier.name) + } + } + + private fun RemoteTypeInformation.AnEnum.carpentEnum() { + carpenter.build(EnumSchema(name = typeIdentifier.name, fields = members.associate { it to EnumField() })) + } +} \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/model/RemoteTypeInformation.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/model/RemoteTypeInformation.kt new file mode 100644 index 0000000000..93c0e72307 --- /dev/null +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/model/RemoteTypeInformation.kt @@ -0,0 +1,196 @@ +package net.corda.serialization.internal.model + +import net.corda.serialization.internal.amqp.Transform +import net.corda.serialization.internal.amqp.TransformTypes +import java.util.* + +typealias TypeDescriptor = String + +/** + * Represents a property of a remotely-defined type. + * + * @param type The type of the property. + * @param isMandatory Whether the property is mandatory (i.e. non-nullable). + */ +data class RemotePropertyInformation(val type: RemoteTypeInformation, val isMandatory: Boolean) + +/** + * The [RemoteTypeInformation] extracted from a remote data source's description of its type schema captures the + * information contained in that schema in a form similar to that of [LocalTypeInformation], but stripped of any + * reference to local type information such as [Type]s, [Method]s, constructors and so on. + * + * It has two main uses: + * + * 1) Comparison with [LocalTypeInformation] to determine compatibility and whether type evolution should be attempted. + * 2) Providing a specification to a [ClassCarpenter] that will synthesize a [Type] at runtime. + * + * A [TypeLoader] knows how to load types described by [RemoteTypeInformation], using a [ClassCarpenter] to build + * synthetic types where needed, so that every piece of [RemoteTypeInformation] is matched to a corresponding local + * [Type] for which [LocalTypeInformation] can be generated. Once we have both [RemoteTypeInformation] and + * [LocalTypeInformation] in hand, we can make decisions about the compatibility between the remote and local type + * schemas. + * + * In the future, it may make sense to generate type schema information by reflecting [LocalTypeInformation] into + * [RemoteTypeInformation]. + * + * Each piece of [RemoteTypeInformation] has both a [TypeIdentifier], which is not guaranteed to be globally uniquely + * identifying, and a [TypeDescriptor], which is. + * + * [TypeIdentifier]s are not globally uniquely identifying because + * multiple remote sources may define their own versions of the same type, with potentially different properties. However, + * they are unique to a given message-exchange session, and are used as unique references for types within the type + * schema associated with a given message. + * + * [TypeDescriptor]s are obtained by "fingerprinting" [LocalTypeInformation], and represent a hashed digest of all of + * the information locally available about a type. If a remote [TypeDescriptor] matches that of a local type, then we + * know that they are fully schema-compatible. However, it is possible for two types to diverge due to inconsistent + * erasure, so that they will have different [TypeDescriptor]s, and yet represent the "same" type for purposes of + * serialisation. In this case, we will determine compatibility based on comparison of the [RemoteTypeInformation]'s + * type graph with that of the [LocalTypeInformation] which reflects it. + */ +sealed class RemoteTypeInformation { + + /** + * The globally-unique [TypeDescriptor] of the represented type. + */ + abstract val typeDescriptor: TypeDescriptor + + /** + * The [TypeIdentifier] of the represented type. + */ + abstract val typeIdentifier: TypeIdentifier + + /** + * Obtain a multi-line, recursively-indented representation of this type information. + * + * @param simplifyClassNames By default, class names are printed as their "simple" class names, i.e. "String" instead + * of "java.lang.String". If this is set to `false`, then the full class name will be printed instead. + */ + fun prettyPrint(simplifyClassNames: Boolean = true): String = + RemoteTypeInformationPrettyPrinter(simplifyClassNames).prettyPrint(this) + + /** + * The [RemoteTypeInformation] corresponding to an unbounded wildcard ([TypeIdentifier.UnknownType]) + */ + object Unknown : RemoteTypeInformation() { + override val typeDescriptor = "?" + override val typeIdentifier = TypeIdentifier.UnknownType + } + + /** + * The [RemoteTypeInformation] corresponding to [java.lang.Object] / [Any] ([TypeIdentifier.TopType]) + */ + object Top : RemoteTypeInformation() { + override val typeDescriptor = "*" + override val typeIdentifier = TypeIdentifier.TopType + } + + /** + * The [RemoteTypeInformation] emitted if we hit a cycle while traversing the graph of related types. + */ + data class Cycle(override val typeIdentifier: TypeIdentifier, private val _follow: () -> RemoteTypeInformation) : RemoteTypeInformation() { + override val typeDescriptor = typeIdentifier.name + val follow: RemoteTypeInformation get() = _follow() + + override fun equals(other: Any?): Boolean = other is Cycle && other.typeIdentifier == typeIdentifier + override fun hashCode(): Int = typeIdentifier.hashCode() + override fun toString(): String = "Cycle($typeIdentifier)" + } + + /** + * Representation of a simple unparameterised type. + */ + data class Unparameterised(override val typeDescriptor: TypeDescriptor, override val typeIdentifier: TypeIdentifier) : RemoteTypeInformation() + + /** + * Representation of a type with type parameters. + * + * @param typeParameters The type parameters of the type. + */ + data class Parameterised(override val typeDescriptor: TypeDescriptor, override val typeIdentifier: TypeIdentifier, val typeParameters: List) : RemoteTypeInformation() + + /** + * Representation of an array of some other type. + * + * @param componentType The component type of the array. + */ + data class AnArray(override val typeDescriptor: TypeDescriptor, override val typeIdentifier: TypeIdentifier, val componentType: RemoteTypeInformation) : RemoteTypeInformation() + + /** + * Representation of an Enum type. + * + * @param members The members of the enum. + */ + data class AnEnum(override val typeDescriptor: TypeDescriptor, + override val typeIdentifier: TypeIdentifier, + val members: List, + val transforms: EnumTransforms) : RemoteTypeInformation() + + /** + * Representation of an interface. + * + * @param properties The properties (i.e. "getter" methods) of the interface. + * @param interfaces The interfaces extended by the interface. + * @param typeParameters The type parameters of the interface. + */ + data class AnInterface(override val typeDescriptor: TypeDescriptor, override val typeIdentifier: TypeIdentifier, val properties: Map, val interfaces: List, val typeParameters: List) : RemoteTypeInformation() + + /** + * Representation of a concrete POJO-like class. + * + * @param properties The properties of the class. + * @param interfaces The interfaces extended by the class. + * @param typeParameters The type parameters of the class. + */ + data class Composable( + override val typeDescriptor: TypeDescriptor, + override val typeIdentifier: TypeIdentifier, + val properties: Map, + val interfaces: List, + val typeParameters: List) : RemoteTypeInformation() +} + +private data class RemoteTypeInformationPrettyPrinter(private val simplifyClassNames: Boolean = true, private val indent: Int = 0) { + + fun prettyPrint(remoteTypeInformation: RemoteTypeInformation): String = with(remoteTypeInformation){ + when (this) { + is RemoteTypeInformation.AnInterface -> typeIdentifier.prettyPrint(simplifyClassNames) + + printInterfaces(interfaces) + + indentAnd { printProperties(properties) } + is RemoteTypeInformation.Composable -> typeIdentifier.prettyPrint(simplifyClassNames) + + printInterfaces(interfaces) + + indentAnd { printProperties(properties) } + is RemoteTypeInformation.AnEnum -> typeIdentifier.prettyPrint(simplifyClassNames) + + members.joinToString("|", "(", ")") + else -> typeIdentifier.prettyPrint(simplifyClassNames) + } + } + + private inline fun indentAnd(block: RemoteTypeInformationPrettyPrinter.() -> String) = + copy(indent = indent + 1).block() + + private fun printInterfaces(interfaces: List) = + if (interfaces.isEmpty()) "" + else interfaces.joinToString(", ", ": ", "") { + it.typeIdentifier.prettyPrint(simplifyClassNames) + } + + private fun printProperties(properties: Map) = + properties.entries.sortedBy { it.key }.joinToString("\n", "\n", "") { + it.prettyPrint() + } + + private fun Map.Entry.prettyPrint(): String = + " ".repeat(indent) + key + + (if(!value.isMandatory) " (optional)" else "") + + ": " + value.type.prettyPrint(simplifyClassNames) +} + +data class EnumTransforms(val defaults: Map, val renames: Map) { + + val size: Int get() = defaults.size + renames.size + + companion object { + val empty = EnumTransforms(emptyMap(), emptyMap()) + } +} \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/model/TypeIdentifier.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/model/TypeIdentifier.kt index 3e4816b0c6..95e4a6132a 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/model/TypeIdentifier.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/model/TypeIdentifier.kt @@ -1,7 +1,16 @@ package net.corda.serialization.internal.model import com.google.common.reflect.TypeToken +import net.corda.serialization.internal.amqp.asClass +import java.io.NotSerializableException import java.lang.reflect.* +import java.util.* + +/** + * Thrown if a [TypeIdentifier] is incompatible with the local [Type] to which it refers, + * i.e. if the number of type parameters does not match. + */ +class IncompatibleTypeIdentifierException(message: String) : NotSerializableException(message) /** * Used as a key for retrieving cached type information. We need slightly more information than the bare classname, @@ -21,18 +30,30 @@ sealed class TypeIdentifier { */ abstract val name: String + /** + * Obtain the local type matching this identifier + * + * @param classLoader The classloader to use to load the type. + * @throws ClassNotFoundException if the type or any of its parameters cannot be loaded. + * @throws IncompatibleTypeIdentifierException if the type identifier is incompatible with the locally-defined type + * to which it refers. + */ + abstract fun getLocalType(classLoader: ClassLoader = ClassLoader.getSystemClassLoader()): Type + + open val erased: TypeIdentifier get() = this + /** * Obtain a nicely-formatted representation of the identified type, for help with debugging. */ fun prettyPrint(simplifyClassNames: Boolean = true): String = when(this) { - is TypeIdentifier.Unknown -> "?" - is TypeIdentifier.Top -> "*" + is TypeIdentifier.UnknownType -> "?" + is TypeIdentifier.TopType -> "*" is TypeIdentifier.Unparameterised -> name.simplifyClassNameIfRequired(simplifyClassNames) is TypeIdentifier.Erased -> "${name.simplifyClassNameIfRequired(simplifyClassNames)} (erased)" - is TypeIdentifier.ArrayOf -> "${componentType.prettyPrint()}[]" + is TypeIdentifier.ArrayOf -> "${componentType.prettyPrint(simplifyClassNames)}[]" is TypeIdentifier.Parameterised -> name.simplifyClassNameIfRequired(simplifyClassNames) + parameters.joinToString(", ", "<", ">") { - it.prettyPrint() + it.prettyPrint(simplifyClassNames) } } @@ -46,10 +67,10 @@ sealed class TypeIdentifier { * @param type The class to get a [TypeIdentifier] for. */ fun forClass(type: Class<*>): TypeIdentifier = when { - type.name == "java.lang.Object" -> Top + type.name == "java.lang.Object" -> TopType type.isArray -> ArrayOf(forClass(type.componentType)) type.typeParameters.isEmpty() -> Unparameterised(type.name) - else -> Erased(type.name) + else -> Erased(type.name, type.typeParameters.size) } /** @@ -63,45 +84,92 @@ sealed class TypeIdentifier { * class implementing a parameterised interface and specifying values for type variables which are referred to * by methods defined in the interface. */ - fun forGenericType(type: Type, resolutionContext: Type = type): TypeIdentifier = when(type) { - is ParameterizedType -> Parameterised((type.rawType as Class<*>).name, type.actualTypeArguments.map { - forGenericType(it.resolveAgainst(resolutionContext)) - }) - is Class<*> -> forClass(type) - is GenericArrayType -> ArrayOf(forGenericType(type.genericComponentType.resolveAgainst(resolutionContext))) - else -> Unknown - } + fun forGenericType(type: Type, resolutionContext: Type = type): TypeIdentifier = + when(type) { + is ParameterizedType -> Parameterised( + (type.rawType as Class<*>).name, + type.ownerType?.let { forGenericType(it) }, + type.actualTypeArguments.map { + forGenericType(it.resolveAgainst(resolutionContext)) + }) + is Class<*> -> forClass(type) + is GenericArrayType -> ArrayOf(forGenericType(type.genericComponentType.resolveAgainst(resolutionContext))) + is WildcardType -> type.upperBound.let { if (it == type) UnknownType else forGenericType(it) } + else -> UnknownType + } } /** * The [TypeIdentifier] of [Any] / [java.lang.Object]. */ - object Top : TypeIdentifier() { + object TopType : TypeIdentifier() { override val name get() = "*" - override fun toString() = "Top" + override fun getLocalType(classLoader: ClassLoader): Type = Any::class.java + override fun toString() = "TopType" + } + + private object UnboundedWildcardType : WildcardType { + override fun getLowerBounds(): Array = emptyArray() + override fun getUpperBounds(): Array = arrayOf(Any::class.java) + override fun toString() = "?" } /** * The [TypeIdentifier] of an unbounded wildcard. */ - object Unknown : TypeIdentifier() { + object UnknownType : TypeIdentifier() { override val name get() = "?" - override fun toString() = "Unknown" + override fun getLocalType(classLoader: ClassLoader): Type = UnboundedWildcardType + override fun toString() = "UnknownType" } /** * Identifies a class with no type parameters. */ data class Unparameterised(override val name: String) : TypeIdentifier() { + + companion object { + private val primitives = listOf( + Byte::class, + Boolean:: class, + Char::class, + Int::class, + Short::class, + Long::class, + Float::class, + Double::class).associate { + it.javaPrimitiveType!!.name to it.javaPrimitiveType + } + } override fun toString() = "Unparameterised($name)" + override fun getLocalType(classLoader: ClassLoader): Type = primitives[name] ?: classLoader.loadClass(name) + + val isPrimitive get() = name in primitives } /** * Identifies a parameterised class such as List, for which we cannot obtain the type parameters at runtime * because they have been erased. */ - data class Erased(override val name: String) : TypeIdentifier() { + data class Erased(override val name: String, val erasedParameterCount: Int) : TypeIdentifier() { + fun toParameterized(parameters: List): TypeIdentifier { + if (parameters.size != erasedParameterCount) throw IncompatibleTypeIdentifierException( + "Erased type $name takes $erasedParameterCount parameters, but ${parameters.size} supplied" + ) + return Parameterised(name, null, parameters) + } + override fun toString() = "Erased($name)" + + override fun getLocalType(classLoader: ClassLoader): Type = classLoader.loadClass(name) + } + + private class ReconstitutedGenericArrayType(private val componentType: Type) : GenericArrayType { + override fun getGenericComponentType(): Type = componentType + override fun toString() = "$componentType[]" + override fun equals(other: Any?): Boolean = + other is GenericArrayType && componentType == other.genericComponentType + override fun hashCode(): Int = Objects.hashCode(componentType) } /** @@ -112,6 +180,30 @@ sealed class TypeIdentifier { data class ArrayOf(val componentType: TypeIdentifier) : TypeIdentifier() { override val name get() = componentType.name + "[]" override fun toString() = "ArrayOf(${componentType.prettyPrint()})" + override fun getLocalType(classLoader: ClassLoader): Type { + val component = componentType.getLocalType(classLoader) + return when (componentType) { + is Parameterised -> ReconstitutedGenericArrayType(component) + else -> java.lang.reflect.Array.newInstance(component.asClass(), 0).javaClass + } + } + } + + private class ReconstitutedParameterizedType( + private val _rawType: Type, + private val _ownerType: Type?, + private val _actualTypeArguments: Array) : ParameterizedType { + override fun getRawType(): Type = _rawType + override fun getOwnerType(): Type? = _ownerType + override fun getActualTypeArguments(): Array = _actualTypeArguments + override fun toString(): String = TypeIdentifier.forGenericType(this).prettyPrint(false) + override fun equals(other: Any?): Boolean = + other is ParameterizedType && + other.rawType == rawType && + other.ownerType == ownerType && + Arrays.equals(other.actualTypeArguments, actualTypeArguments) + override fun hashCode(): Int = + Arrays.hashCode(actualTypeArguments) xor Objects.hashCode(ownerType) xor Objects.hashCode(rawType) } /** @@ -119,8 +211,25 @@ sealed class TypeIdentifier { * * @param parameters [TypeIdentifier]s for each of the resolved type parameter values of this type. */ - data class Parameterised(override val name: String, val parameters: List) : TypeIdentifier() { + data class Parameterised(override val name: String, val owner: TypeIdentifier?, val parameters: List) : TypeIdentifier() { + /** + * Get the type-erased equivalent of this type. + */ + override val erased: TypeIdentifier get() = Erased(name, parameters.size) + override fun toString() = "Parameterised(${prettyPrint()})" + override fun getLocalType(classLoader: ClassLoader): Type { + val rawType = classLoader.loadClass(name) + if (rawType.typeParameters.size != parameters.size) { + throw IncompatibleTypeIdentifierException( + "Class $rawType expects ${rawType.typeParameters.size} type arguments, " + + "but type ${this.prettyPrint(false)} has ${parameters.size}") + } + return ReconstitutedParameterizedType( + rawType, + owner?.getLocalType(classLoader), + parameters.map { it.getLocalType(classLoader) }.toTypedArray()) + } } } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/model/TypeLoader.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/model/TypeLoader.kt new file mode 100644 index 0000000000..ca541f6102 --- /dev/null +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/model/TypeLoader.kt @@ -0,0 +1,61 @@ +package net.corda.serialization.internal.model + +import net.corda.serialization.internal.carpenter.* +import java.io.NotSerializableException +import java.lang.ClassCastException +import java.lang.reflect.Type + +/** + * A [TypeLoader] obtains local types whose [TypeIdentifier]s will reflect those of remote types. + */ +interface TypeLoader { + /** + * Obtains local types which will have the same [TypeIdentifier]s as the remote types. + * + * @param remoteTypeInformation The type information for the remote types. + */ + fun load(remoteTypeInformation: Collection): Map +} + +/** + * A [TypeLoader] that uses the [ClassCarpenter] to build a class matching the supplied [RemoteTypeInformation] if none + * is visible from the current classloader. + */ +class ClassCarpentingTypeLoader(private val carpenter: RemoteTypeCarpenter, private val classLoader: ClassLoader): TypeLoader { + + val cache = DefaultCacheProvider.createCache() + + override fun load(remoteTypeInformation: Collection): Map { + val remoteInformationByIdentifier = remoteTypeInformation.associateBy { it.typeIdentifier } + + // Grab all the types we can from the cache, or the classloader. + val noCarpentryRequired = remoteInformationByIdentifier.asSequence().mapNotNull { (identifier, _) -> + try { + identifier to cache.computeIfAbsent(identifier) { identifier.getLocalType(classLoader) } + } catch (e: ClassNotFoundException) { + null + } + }.toMap() + + // If we have everything we need, return immediately. + if (noCarpentryRequired.size == remoteTypeInformation.size) return noCarpentryRequired + + // Identify the types which need carpenting up. + val requiringCarpentry = remoteInformationByIdentifier.asSequence().mapNotNull { (identifier, information) -> + if (identifier in noCarpentryRequired) null else information + }.toSet() + + // Build the types requiring carpentry in reverse-dependency order. + // Something else might be trying to carpent these types at the same time as us, so we always consult + // (and populate) the cache. + val carpented = CarpentryDependencyGraph.buildInReverseDependencyOrder(requiringCarpentry) { typeToCarpent -> + cache.computeIfAbsent(typeToCarpent.typeIdentifier) { + carpenter.carpent(typeToCarpent) + } + } + + // Return the complete map of types. + return noCarpentryRequired + carpented + } +} + diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/AMQPRemoteTypeModelTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/AMQPRemoteTypeModelTests.kt new file mode 100644 index 0000000000..b1bca647c5 --- /dev/null +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/AMQPRemoteTypeModelTests.kt @@ -0,0 +1,84 @@ +package net.corda.serialization.internal.amqp + +import net.corda.serialization.internal.amqp.testutils.serializeAndReturnSchema +import net.corda.serialization.internal.amqp.testutils.testDefaultFactory +import net.corda.serialization.internal.model.* +import net.corda.testing.core.SerializationEnvironmentRule +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test +import java.lang.IllegalArgumentException +import java.util.* + +class AMQPRemoteTypeModelTests { + + @Rule + @JvmField + val serializationEnvRule = SerializationEnvironmentRule() + + private val factory = testDefaultFactory() + private val typeModel = AMQPRemoteTypeModel() + + interface Interface { + val array: Array + val list: List + val map: Map + } + + enum class Enum : Interface { + FOO, BAR, BAZ; + + override val array: Array get() = emptyArray() + override val list: List get() = emptyList() + override val map: Map get() = emptyMap() + } + + open class Superclass(override val array: Array, override val list: List, override val map: Map) + : Interface + + class C(array: Array, list: List, map: Map, val enum: Enum): Superclass(array, list, map) + + class SimpleClass(val a: Int, val b: Double, val c: Short?, val d: ByteArray, val e: ByteArray?) + + @Test + fun `round-trip some types through AMQP serialisations`() { + arrayOf("").assertRemoteType("String[]") + listOf(1).assertRemoteType("List") + arrayOf(listOf(1)).assertRemoteType("List[]") + Enum.BAZ.assertRemoteType("Enum(FOO|BAR|BAZ)") + mapOf("string" to 1).assertRemoteType("Map") + arrayOf(byteArrayOf(1, 2, 3)).assertRemoteType("byte[][]") + + SimpleClass(1, 2.0, null, byteArrayOf(1, 2, 3), byteArrayOf(4, 5, 6)) + .assertRemoteType(""" + SimpleClass + a: int + b: double + c (optional): Short + d: byte[] + e (optional): byte[] + """) + + C(arrayOf("a", "b"), listOf(UUID.randomUUID()), mapOf(UUID.randomUUID() to intArrayOf(1, 2, 3)), Enum.BAZ) + .assertRemoteType(""" + C: Interface + array: String[] + enum: Enum(FOO|BAR|BAZ) + list: List + map: Map + """) + } + + private fun getRemoteType(obj: Any): RemoteTypeInformation { + val output = SerializationOutput(factory) + val schema = output.serializeAndReturnSchema(obj) + val values = typeModel.interpret(SerializationSchemas(schema.schema, schema.transformsSchema)).values + return values.find { it.typeIdentifier.getLocalType().asClass().isAssignableFrom(obj::class.java) } ?: + throw IllegalArgumentException( + "Can't find ${obj::class.java.name} in ${values.map { it.typeIdentifier.name}}") + } + + private fun Any.assertRemoteType(prettyPrinted: String) { + assertEquals(prettyPrinted.trimIndent(), getRemoteType(this).prettyPrint()) + } +} \ No newline at end of file diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/AMQPTypeIdentifierParserTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/AMQPTypeIdentifierParserTests.kt new file mode 100644 index 0000000000..6883a7ac68 --- /dev/null +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/AMQPTypeIdentifierParserTests.kt @@ -0,0 +1,197 @@ +package net.corda.serialization.internal.amqp + +import com.google.common.reflect.TypeToken +import net.corda.serialization.internal.model.TypeIdentifier +import org.apache.qpid.proton.amqp.UnsignedShort +import org.junit.Test +import java.io.NotSerializableException +import java.lang.reflect.Type +import java.time.LocalDateTime +import java.util.* +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class AMQPTypeIdentifierParserTests { + + @Test + fun `primitives and arrays`() { + assertParseResult("int") + assertParseResult("int[p]") + assertParseResult>("int[]") + assertParseResult>("int[p][]") + assertParseResult>>("int[][]") + assertParseResult("binary") + assertParseResult>("binary[]") + assertParseResult>("ushort[]") + assertParseResult>>("string[][]") + assertParseResult("uuid") + assertParseResult("timestamp") + + // We set a limit to the depth of arrays-of-arrays-of-arrays... + assertFailsWith { + AMQPTypeIdentifierParser.parse("string" + "[]".repeat(33)) + } + } + + @Test + fun `unparameterised types`() { + assertParseResult("java.time.LocalDateTime") + assertParseResult>("java.time.LocalDateTime[]") + assertParseResult>>("java.time.LocalDateTime[][]") + } + + interface WithParameter { + val value: T + } + + interface WithParameters { + val p: Array + val q: WithParameter> + } + + @Test + fun `parameterised types, nested, with arrays`() { + assertParsesTo>, UUID>>>>>( + "WithParameters[]>>" + ) + + // We set a limit to the maximum depth of nested type parameters. + assertFailsWith { + AMQPTypeIdentifierParser.parse("WithParameter<".repeat(33) + ">".repeat(33)) + } + } + + @Test + fun `compatibility test`() { + assertParsesCompatibly() + assertParsesCompatibly() + assertParsesCompatibly>() + assertParsesCompatibly>() + assertParsesTo>("WithParameter") + assertParsesCompatibly>() + assertParsesCompatibly>>() + assertParsesCompatibly>, UUID>>>>>() + } + + // Old tests for DeserializedParameterizedType + @Test + fun `test nested`() { + verify(" java.util.Map < java.util.Map< java.lang.String, java.lang.Integer >, java.util.Map < java.lang.Long , java.lang.String > >") + } + + @Test + fun `test simple`() { + verify("java.util.List") + } + + @Test + fun `test multiple args`() { + verify("java.util.Map") + } + + @Test + fun `test trailing whitespace`() { + verify("java.util.Map ") + } + + @Test + fun `test list of commands`() { + verify("java.util.List>>") + } + + @Test(expected = NotSerializableException::class) + fun `test trailing text`() { + verify("java.util.Mapfoo") + } + + @Test(expected = NotSerializableException::class) + fun `test trailing comma`() { + verify("java.util.Map") + } + + @Test(expected = NotSerializableException::class) + fun `test leading comma`() { + verify("java.util.Map<,java.lang.String, java.lang.Integer>") + } + + @Test(expected = NotSerializableException::class) + fun `test middle comma`() { + verify("java.util.Map<,java.lang.String,, java.lang.Integer>") + } + + @Test(expected = NotSerializableException::class) + fun `test trailing close`() { + verify("java.util.Map>") + } + + @Test(expected = NotSerializableException::class) + fun `test empty params`() { + verify("java.util.Map<>") + } + + @Test(expected = NotSerializableException::class) + fun `test mid whitespace`() { + verify("java.u til.List") + } + + @Test(expected = NotSerializableException::class) + fun `test mid whitespace2`() { + verify("java.util.List") + } + + @Test(expected = NotSerializableException::class) + fun `test wrong number of parameters`() { + verify("java.util.List") + } + + @Test + fun `test no parameters`() { + verify("java.lang.String") + } + + @Test(expected = NotSerializableException::class) + fun `test parameters on non-generic type`() { + verify("java.lang.String") + } + + @Test(expected = NotSerializableException::class) + fun `test excessive nesting`() { + var nested = "java.lang.Integer" + for (i in 1..AMQPTypeIdentifierParser.MAX_TYPE_PARAM_DEPTH) { + nested = "java.util.List<$nested>" + } + verify(nested) + } + + private inline fun assertParseResult(typeString: String) { + assertEquals(TypeIdentifier.forGenericType(typeOf()), AMQPTypeIdentifierParser.parse(typeString)) + } + + private inline fun typeOf() = object : TypeToken() {}.type + + private inline fun assertParsesCompatibly() = assertParsesCompatibly(typeOf()) + + private fun assertParsesCompatibly(type: Type) { + assertParsesTo(type, TypeIdentifier.forGenericType(type).prettyPrint()) + } + + private inline fun assertParsesTo(expectedIdentifierPrettyPrint: String) { + assertParsesTo(typeOf(), expectedIdentifierPrettyPrint) + } + + private fun assertParsesTo(type: Type, expectedIdentifierPrettyPrint: String) { + val nameForType = AMQPTypeIdentifiers.nameForType(type) + val parsedIdentifier = AMQPTypeIdentifierParser.parse(nameForType) + assertEquals(expectedIdentifierPrettyPrint, parsedIdentifier.prettyPrint()) + } + + + private fun normalise(string: String): String { + return string.replace(" ", "") + } + + private fun verify(typeName: String) { + val type = AMQPTypeIdentifierParser.parse(typeName).getLocalType() + assertEquals(normalise(typeName), normalise(type.typeName)) + } +} \ No newline at end of file diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/model/ClassCarpentingTypeLoaderTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/model/ClassCarpentingTypeLoaderTests.kt new file mode 100644 index 0000000000..a2a2cc280d --- /dev/null +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/model/ClassCarpentingTypeLoaderTests.kt @@ -0,0 +1,112 @@ +package net.corda.serialization.internal.model + +import com.fasterxml.jackson.databind.ObjectMapper +import com.google.common.reflect.TypeToken +import net.corda.serialization.internal.AllWhitelist +import net.corda.serialization.internal.amqp.asClass +import net.corda.serialization.internal.carpenter.ClassCarpenterImpl +import org.junit.Test +import java.lang.reflect.Type +import kotlin.test.assertEquals + +class ClassCarpentingTypeLoaderTests { + + val carpenter = ClassCarpenterImpl(AllWhitelist) + val remoteTypeCarpenter = SchemaBuildingRemoteTypeCarpenter(carpenter) + val typeLoader = ClassCarpentingTypeLoader(remoteTypeCarpenter, carpenter.classloader) + + @Test + fun `carpent some related classes`() { + val addressInformation = RemoteTypeInformation.Composable( + "address", + typeIdentifierOf("net.corda.test.Address"), + mapOf( + "addressLines" to remoteType>().mandatory, + "postcode" to remoteType().optional + ), emptyList(), emptyList() + ) + + val listOfAddresses = RemoteTypeInformation.Parameterised( + "list

", + TypeIdentifier.Parameterised( + "java.util.List", + null, + listOf(addressInformation.typeIdentifier)), + listOf(addressInformation)) + + val personInformation = RemoteTypeInformation.Composable( + "person", + typeIdentifierOf("net.corda.test.Person"), + mapOf( + "name" to remoteType().mandatory, + "age" to remoteType(TypeIdentifier.forClass(Int::class.javaPrimitiveType!!)).mandatory, + "address" to addressInformation.mandatory, + "previousAddresses" to listOfAddresses.mandatory + ), emptyList(), emptyList()) + + val types = typeLoader.load(listOf(personInformation, addressInformation, listOfAddresses)) + val addressType = types[addressInformation.typeIdentifier]!! + val personType = types[personInformation.typeIdentifier]!! + + val address = addressType.make(arrayOf("23 Acacia Avenue", "Surbiton"), "VB6 5UX") + val previousAddress = addressType.make(arrayOf("99 Penguin Lane", "Doncaster"), "RA8 81T") + + val person = personType.make("Arthur Putey", 42, address, listOf(previousAddress)) + val personJson = ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(person) + assertEquals(""" + { + "name" : "Arthur Putey", + "age" : 42, + "address" : { + "addressLines" : [ "23 Acacia Avenue", "Surbiton" ], + "postcode" : "VB6 5UX" + }, + "previousAddresses" : [ { + "addressLines" : [ "99 Penguin Lane", "Doncaster" ], + "postcode" : "RA8 81T" + } ] + } + """.trimIndent(), personJson) + } + + private fun Type.make(vararg params: Any): Any { + val cls = this.asClass() + val paramTypes = params.map { it::class.javaPrimitiveType ?: it::class.javaObjectType }.toTypedArray() + val constructor = cls.constructors.find { it.parameterTypes.zip(paramTypes).all { + (expected, actual) -> expected.isAssignableFrom(actual) + } }!! + return constructor.newInstance(*params) + } + + private fun typeIdentifierOf(typeName: String, vararg parameters: TypeIdentifier) = + if (parameters.isEmpty()) TypeIdentifier.Unparameterised(typeName) + else TypeIdentifier.Parameterised(typeName, null, parameters.toList()) + + private inline fun typeOf(): Type = object : TypeToken() {}.type + private inline fun typeIdentifierOf(): TypeIdentifier = TypeIdentifier.forGenericType(typeOf()) + private inline fun remoteType(): RemoteTypeInformation = remoteType(typeIdentifierOf()) + + private fun remoteType(typeIdentifier: TypeIdentifier): RemoteTypeInformation = + when (typeIdentifier) { + is TypeIdentifier.Unparameterised -> RemoteTypeInformation.Unparameterised(typeIdentifier.prettyPrint(), typeIdentifier) + is TypeIdentifier.Parameterised -> RemoteTypeInformation.Parameterised( + typeIdentifier.prettyPrint(), + typeIdentifier, + typeIdentifier.parameters.map { remoteType(it) }) + is TypeIdentifier.ArrayOf -> RemoteTypeInformation.AnArray( + typeIdentifier.prettyPrint(), + typeIdentifier, + remoteType(typeIdentifier.componentType)) + is TypeIdentifier.Erased -> RemoteTypeInformation.Unparameterised( + typeIdentifier.prettyPrint(), + TypeIdentifier.Unparameterised(typeIdentifier.name)) + is TypeIdentifier.TopType -> RemoteTypeInformation.Top + is TypeIdentifier.UnknownType -> RemoteTypeInformation.Unknown + } + + private val RemoteTypeInformation.optional: RemotePropertyInformation get() = + RemotePropertyInformation(this, false) + + private val RemoteTypeInformation.mandatory: RemotePropertyInformation get() = + RemotePropertyInformation(this, true) +} \ No newline at end of file From 0c323bce5c220d88b72fcb5251de1d2e82e83411 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Mon, 19 Nov 2018 11:09:33 +0000 Subject: [PATCH 06/19] Documents Corda dependencies. Clean-up. (#4196) * Documents Corda dependencies. Clean-up. * Review feedback. * Addresses review feedback. --- docs/source/cordapp-build-systems.rst | 98 +++++++++++++++++++-------- 1 file changed, 70 insertions(+), 28 deletions(-) diff --git a/docs/source/cordapp-build-systems.rst b/docs/source/cordapp-build-systems.rst index 10d620376d..113a8d3637 100644 --- a/docs/source/cordapp-build-systems.rst +++ b/docs/source/cordapp-build-systems.rst @@ -46,9 +46,13 @@ Several ``ext`` variables are used in a CorDapp's ``build.gradle`` file to defin ``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/cordapp. If in doubt, you should base yourself on the version numbers used in the ``build.gradle`` file of the `Kotlin CorDapp Template `_ and the `Java CorDapp Template `_. +versions can be found here: https://bintray.com/r3/corda/cordapp. If in doubt, you should base yourself on the version +numbers used in the ``build.gradle`` file of the +`Kotlin CorDapp Template `_ and the +`Java CorDapp Template `_. -For example, to use version 3.0 of Corda, version 3.0.8 of the Corda gradle plugins, version 0.7.9 of Quasar, and version 1.1.60 of Kotlin, you'd write: +For example, to use version 3.0 of Corda, version 3.0.8 of the Corda gradle plugins, version 0.7.9 of Quasar, and +version 1.1.60 of Kotlin, you'd write: .. sourcecode:: groovy @@ -70,19 +74,55 @@ The ``cordformation`` plugin adds two new gradle configurations: configurations should be used for any Corda dependency (e.g. ``corda-core``, ``corda-node``) in order to prevent a dependency from being included twice (once in the CorDapp JAR and once in the Corda JARs). -To build against Corda, you must add the following to your ``build.gradle`` file: +Here are some guidelines for Corda dependencies: -* ``net.corda:corda:$corda_release_version`` as a ``cordaRuntime`` dependency -* Each Corda compile dependency (eg ``net.corda:corda-core:$corda_release_version``) as a ``cordaCompile`` dependency +* When building a CorDapp, you should always include ``net.corda:corda-core:$corda_release_version`` as a + ``cordaCompile`` dependency, and ``net.corda:corda:$corda_release_version`` as a ``cordaRuntime`` dependency -You may also want to add: +* When building an RPC client that communicates with a node (e.g. a webserver), you should include + ``net.corda:corda-rpc:$corda_release_version`` as a ``cordaCompile`` dependency -* ``net.corda:corda-test-utils:$corda_release_version`` as a ``testCompile`` dependency, in order to use Corda's test +* When you need to use the network bootstrapper to bootstrap a local network (e.g. when using ``Cordformation``), you + should include ``net.corda:corda-node-api:$corda_release_version`` as a ``cordaCompile`` dependency + +* To use Corda's test frameworks, add ``net.corda:corda-test-utils:$corda_release_version`` as a ``testCompile`` + dependency. Never include ``corda-test-utils`` as a ``compile`` or ``cordaCompile`` dependency + +* Any other Corda dependencies you need should be included as ``cordaCompile`` dependencies + +Here is an overview of the various Corda dependencies: + +* ``corda`` - The Corda fat JAR. Do not use as a compile dependency. Required as a ``cordaRuntime`` dependency when + using ``Cordformation`` +* ``corda-confidential-identities`` - A part of the core Corda libraries. Automatically pulled in by other libraries +* ``corda-core`` - Usually automatically included by another dependency, contains core Corda utilities, model, and + functionality. Include manually if the utilities are useful or you are writing a library for Corda +* ``corda-core-deterministic`` - Used by the Corda node for deterministic contracts. Not likely to be used externally +* ``corda-djvm`` - Used by the Corda node for deterministic contracts. Not likely to be used externally +* ``corda-finance`` - The Corda finance CorDapp. Only include as a ``cordaCompile`` dependency if using as a dependent + Cordapp or if you need access to the Corda finance types. Use as a ``cordapp`` dependency if using as a CorDapp + dependency (see below) +* ``corda-jackson`` - Corda Jackson support. Use if you plan to serialise Corda objects to and/or from JSON +* ``corda-jfx`` - JavaFX utilities with some Corda-specific models and utilities. Only use with JavaFX apps +* ``corda-mock`` - A small library of useful mocks. Use if the classes are useful to you +* ``corda-node`` - The Corda node. Do not depend on. Used only by the Corda fat JAR and indirectly in testing frameworks -* ``net.corda:corda-webserver:$corda_release_version`` as a ``cordaRuntime`` dependency, in order to use Corda's - built-in development webserver - -.. warning:: Never include ``corda-test-utils`` as a ``compile`` or ``cordaCompile`` dependency. +* ``corda-node-api`` - The node API. Required to bootstrap a local network +* ``corda-node-driver`` - Testing utility for programmatically starting nodes from JVM languages. Use for tests +* ``corda-notary-bft-smart`` - A Corda notary implementation +* ``corda-notary-raft`` - A Corda notary implementation +* ``corda-rpc`` - The Corda RPC client library. Used when writing an RPC client +* ``corda-serialization`` - The Corda core serialization library. Automatically included by other dependencies +* ``corda-serialization-deterministic`` - The Corda core serialization library. Automatically included by other + dependencies +* ``corda-shell`` - Used by the Corda node. Never depend on directly +* ``corda-test-common`` - A common test library. Automatically included by other test libraries +* ``corda-test-utils`` - Used when writing tests against Corda/Cordapps +* ``corda-tools-explorer`` - The Node Explorer tool. Do not depend on +* ``corda-tools-network-bootstrapper`` - The Network Builder tool. Useful in build scripts +* ``corda-tools-shell-cli`` - The Shell CLI tool. Useful in build scripts +* ``corda-webserver-impl`` - The Corda webserver fat JAR. Deprecated. Usually only used by build scripts +* ``corda-websever`` - The Corda webserver library. Deprecated. Use a standard webserver library such as Spring instead Dependencies on other CorDapps ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -157,19 +197,19 @@ The example ``cordapp`` plugin with plugin ``signing`` configuration: } //... -CorDapp auto-signing allows to use signature constraints for contracts from the CorDapp -without need to create a keystore and configure the ``cordapp`` plugin. -For production deployment ensure to sign the CorDapp using your own certificate e.g. by setting system properties to point to an external keystore -or by disabling signing in ``cordapp`` plugin and signing the CordDapp JAR downstream in your build pipeline. -CorDapp signed by Corda development certificate is accepted by Corda node only when running in the development mode. -In case CordDapp signed by the (default) development key is run on node in the production mode (e.g. for testing), -the node may be set to accept the development key by adding the ``cordappSignerKeyFingerprintBlacklist = []`` property set to empty list -(see :ref:`Configuring a node `). +CorDapp auto-signing allows to use signature constraints for contracts from the CorDapp without need to create a +keystore and configure the ``cordapp`` plugin. For production deployment ensure to sign the CorDapp using your own +certificate e.g. by setting system properties to point to an external keystore or by disabling signing in ``cordapp`` +plugin and signing the CordDapp JAR downstream in your build pipeline. CorDapp signed by Corda development certificate +is accepted by Corda node only when running in the development mode. In case CordDapp signed by the (default) +development key is run on node in the production mode (e.g. for testing), the node may be set to accept the development +key by adding the ``cordappSignerKeyFingerprintBlacklist = []`` property set to empty list (see +:ref:`Configuring a node `). -Signing options can be contextually overwritten by the relevant system properties as described above. -This allows the single ``build.gradle`` file to be used for a development build (defaulting to the Corda development keystore) -and for a production build (using an external keystore). -The example system properties setup for the build process which overrides signing options: +Signing options can be contextually overwritten by the relevant system properties as described above. This allows the +single ``build.gradle`` file to be used for a development build (defaulting to the Corda development keystore) and for +a production build (using an external keystore). The example system properties setup for the build process which +overrides signing options: .. sourcecode:: shell @@ -187,8 +227,9 @@ CorDapp signing can be disabled for a build: ./gradlew -Dsigning.enabled=false -Other system properties can be explicitly assigned to options by calling ``System.getProperty`` in ``cordapp`` plugin configuration. -For example the below configuration sets the specific signing algorithm when a system property is available otherwise defaults to an empty string: +Other system properties can be explicitly assigned to options by calling ``System.getProperty`` in ``cordapp`` plugin +configuration. For example the below configuration sets the specific signing algorithm when a system property is +available otherwise defaults to an empty string: .. sourcecode:: groovy @@ -200,7 +241,8 @@ For example the below configuration sets the specific signing algorithm when a s } //... -Then the build process can set the value for *custom.sigalg* system property and other system properties recognized by ``cordapp`` plugin: +Then the build process can set the value for *custom.sigalg* system property and other system properties recognized by +``cordapp`` plugin: .. sourcecode:: shell @@ -216,8 +258,8 @@ Cordformation plugin can also sign CorDapps JARs, when deploying set of nodes, s Example ^^^^^^^ -Below is a sample of what a CorDapp's Gradle dependencies block might look like. When building your own CorDapp, you should -base yourself on the ``build.gradle`` file of the +Below is a sample of what a CorDapp's Gradle dependencies block might look like. When building your own CorDapp, you +should base yourself on the ``build.gradle`` file of the `Kotlin CorDapp Template `_ or the `Java CorDapp Template `_. From 828892a3d95861ee868047cec875ed0c628b6f1f Mon Sep 17 00:00:00 2001 From: Stefano Franz Date: Mon, 19 Nov 2018 11:26:42 +0000 Subject: [PATCH 07/19] write nodeInfo to additional-node-info folder as well as baseDirectory (#4247) * write nodeInfo to additional-node-info folder as well as baseDirectory * fix broken network map tests --- .../net/corda/core}/internal/NodeInfoConstants.kt | 2 +- .../internal/network/NodeInfoFilesCopier.kt | 2 +- .../internal/network/NetworkBootstrapperTest.kt | 2 +- .../internal/network/NodeInfoFilesCopierTest.kt | 2 +- .../corda/node/services/network/NetworkMapTest.kt | 14 ++++++++------ .../kotlin/net/corda/node/internal/AbstractNode.kt | 1 + .../corda/node/services/network/NodeInfoWatcher.kt | 2 +- .../node/services/network/NetworkMapUpdaterTest.kt | 2 +- .../node/services/network/NodeInfoWatcherTest.kt | 2 +- 9 files changed, 16 insertions(+), 13 deletions(-) rename {node-api/src/main/kotlin/net/corda/nodeapi => core/src/main/kotlin/net/corda/core}/internal/NodeInfoConstants.kt (76%) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NodeInfoConstants.kt b/core/src/main/kotlin/net/corda/core/internal/NodeInfoConstants.kt similarity index 76% rename from node-api/src/main/kotlin/net/corda/nodeapi/internal/NodeInfoConstants.kt rename to core/src/main/kotlin/net/corda/core/internal/NodeInfoConstants.kt index 371ed2cd57..921a33fab5 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NodeInfoConstants.kt +++ b/core/src/main/kotlin/net/corda/core/internal/NodeInfoConstants.kt @@ -1,4 +1,4 @@ -package net.corda.nodeapi.internal +package net.corda.core.internal // TODO: Add to Corda node.conf to allow customisation const val NODE_INFO_DIRECTORY = "additional-node-infos" diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NodeInfoFilesCopier.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NodeInfoFilesCopier.kt index 86c3266bb6..520c9cabfe 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NodeInfoFilesCopier.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NodeInfoFilesCopier.kt @@ -2,7 +2,7 @@ package net.corda.nodeapi.internal.network import net.corda.core.internal.* import net.corda.core.utilities.contextLogger -import net.corda.nodeapi.internal.NODE_INFO_DIRECTORY +import net.corda.core.internal.NODE_INFO_DIRECTORY import rx.Observable import rx.Scheduler import rx.Subscription diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapperTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapperTest.kt index 0c1b439d99..0c2345db05 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapperTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapperTest.kt @@ -11,7 +11,7 @@ import net.corda.core.node.NodeInfo import net.corda.core.serialization.serialize import net.corda.node.services.config.NotaryConfig import net.corda.nodeapi.internal.DEV_ROOT_CA -import net.corda.nodeapi.internal.NODE_INFO_DIRECTORY +import net.corda.core.internal.NODE_INFO_DIRECTORY import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.config.parseAs import net.corda.nodeapi.internal.config.toConfig diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/NodeInfoFilesCopierTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/NodeInfoFilesCopierTest.kt index abacb8a0e3..97e21787bb 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/NodeInfoFilesCopierTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/NodeInfoFilesCopierTest.kt @@ -4,7 +4,7 @@ import net.corda.core.internal.div import net.corda.core.internal.list import net.corda.core.internal.write import net.corda.nodeapi.eventually -import net.corda.nodeapi.internal.NODE_INFO_DIRECTORY +import net.corda.core.internal.NODE_INFO_DIRECTORY import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Rule diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt index 943b2c0707..747827cea7 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt @@ -5,10 +5,11 @@ import net.corda.core.internal.* import net.corda.core.internal.concurrent.transpose import net.corda.core.messaging.ParametersUpdateInfo import net.corda.core.node.NodeInfo +import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.seconds -import net.corda.nodeapi.internal.NODE_INFO_DIRECTORY +import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME import net.corda.nodeapi.internal.network.SignedNetworkParameters @@ -21,14 +22,13 @@ import net.corda.testing.node.internal.* import net.corda.testing.node.internal.network.NetworkMapServer import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.After +import org.hamcrest.CoreMatchers.`is` +import org.junit.* import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Rule -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized import java.net.URL +import java.nio.file.Files import java.time.Instant @RunWith(Parameterized::class) @@ -237,7 +237,9 @@ class NetworkMapTest(var initFunc: (URL, NetworkMapServer) -> CompatibilityZoneP // Make sure the nodes aren't getting the node infos from their additional directories val nodeInfosDir = baseDirectory / NODE_INFO_DIRECTORY if (nodeInfosDir.exists()) { - assertThat(nodeInfosDir.list()).isEmpty() + Assert.assertThat(nodeInfosDir.list().size, `is`(1)) + Assert.assertThat(Files.readAllBytes(nodeInfosDir.list().single()).deserialize().verified().legalIdentities.first(), `is`( this.nodeInfo.legalIdentities.first())) + } assertThat(rpc.networkMapSnapshot()).containsOnly(*nodes) } 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 65e3bce05b..e6669e14ec 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -452,6 +452,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, // Write the node-info file even if nothing's changed, just in case the file has been deleted. NodeInfoWatcher.saveToFile(configuration.baseDirectory, nodeInfoAndSigned) + NodeInfoWatcher.saveToFile(configuration.baseDirectory / NODE_INFO_DIRECTORY, nodeInfoAndSigned) // Always republish on startup, it's treated by network map server as a heartbeat. if (publish && networkMapClient != null) { diff --git a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt index 814e193b25..ee0b3b068f 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt @@ -7,7 +7,7 @@ import net.corda.core.serialization.serialize import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug import net.corda.core.utilities.seconds -import net.corda.nodeapi.internal.NODE_INFO_DIRECTORY +import net.corda.core.internal.NODE_INFO_DIRECTORY import net.corda.nodeapi.internal.NodeInfoAndSigned import net.corda.nodeapi.internal.network.NodeInfoFilesCopier import rx.Observable diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt index 5cf1aa2b02..bbe4812c8d 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt @@ -16,7 +16,7 @@ import net.corda.core.serialization.serialize import net.corda.core.utilities.millis import net.corda.node.VersionInfo import net.corda.node.services.api.NetworkMapCacheInternal -import net.corda.nodeapi.internal.NODE_INFO_DIRECTORY +import net.corda.core.internal.NODE_INFO_DIRECTORY import net.corda.nodeapi.internal.NodeInfoAndSigned import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME import net.corda.nodeapi.internal.network.NodeInfoFilesCopier diff --git a/node/src/test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt index 034afa9121..8a02e9e86f 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt @@ -6,7 +6,7 @@ import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.internal.size import net.corda.core.node.services.KeyManagementService -import net.corda.nodeapi.internal.NODE_INFO_DIRECTORY +import net.corda.core.internal.NODE_INFO_DIRECTORY import net.corda.nodeapi.internal.NodeInfoAndSigned import net.corda.nodeapi.internal.network.NodeInfoFilesCopier import net.corda.testing.core.ALICE_NAME From 1658cb026965e3068f26fc7a2f485f31a2c0900a Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Mon, 19 Nov 2018 12:19:05 +0000 Subject: [PATCH 08/19] Updates running-a-cordapp to point to new samples page. (#4251) --- docs/source/tutorial-cordapp.rst | 68 +++++++++++++++----------------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/docs/source/tutorial-cordapp.rst b/docs/source/tutorial-cordapp.rst index 805808b761..b9ccef55c4 100644 --- a/docs/source/tutorial-cordapp.rst +++ b/docs/source/tutorial-cordapp.rst @@ -16,7 +16,7 @@ The example CorDapp allows nodes to agree IOUs with each other, as long as they We will deploy and run the CorDapp on four test nodes: -* **Notary**, which hosts a validating notary service +* **Notary**, which runs a notary service * **PartyA** * **PartyB** * **PartyC** @@ -30,10 +30,9 @@ Start by downloading the example CorDapp from GitHub: * Set up your machine by following the :doc:`quickstart guide ` -* Clone the example CorDapp from the `cordapp-example repository `_ using - the following command: ``git clone https://github.com/corda/cordapp-example`` +* Clone the samples repository from using the following command: ``git clone https://github.com/corda/samples`` -* Change directories to the freshly cloned repo: ``cd cordapp-example`` +* Change directories to the ``cordapp-example`` folder: ``cd samples/cordapp-example`` Opening the example CorDapp in IntelliJ --------------------------------------- @@ -41,7 +40,7 @@ Let's open the example CorDapp in IntelliJ IDEA: * Open IntelliJ -* A splash screen will appear. Click ``open``, select the cloned ``cordapp-example`` folder, and click ``OK`` +* A splash screen will appear. Click ``open``, navigate to and select the ``cordapp-example`` folder, and click ``OK`` * Once the project is open, click ``File``, then ``Project Structure``. Under ``Project SDK:``, set the project SDK by clicking ``New...``, clicking ``JDK``, and navigating to ``C:\Program Files\Java\jdk1.8.0_XXX`` on Windows or ``Library/Java/JavaVirtualMachines/jdk1.8.XXX`` on MacOSX (where ``XXX`` is the @@ -65,61 +64,59 @@ The example CorDapp has the following structure: │   │   └── log4j2.xml │   └── test │   └── log4j2.xml - ├── doc - │   └── example_flow.plantuml ├── gradle │   └── wrapper │   ├── gradle-wrapper.jar │   └── gradle-wrapper.properties - ├── lib - │   ├── README.txt - │   └── quasar.jar ├── java-source - │   └── ... - ├── kotlin-source │   ├── build.gradle │   └── src + │   ├── integrationTest + │   │   └── java + │   │   └── com + │   │   └── example + │   │   └── DriverBasedTests.java │   ├── main - │   │   ├── kotlin + │   │   ├── java │   │   │   └── com │   │   │   └── example │   │   │   ├── api - │   │   │   │   └── ExampleApi.kt + │   │   │   │   └── ExampleApi.java │   │   │   ├── client - │   │   │   │   └── ExampleClientRPC.kt + │   │   │   │   └── ExampleClientRPC.java │   │   │   ├── contract - │   │   │   │   └── IOUContract.kt + │   │   │   │   └── IOUContract.java │   │   │   ├── flow - │   │   │   │   └── ExampleFlow.kt - │   │   │   ├── model - │   │   │   │   └── IOU.kt + │   │   │   │   └── ExampleFlow.java │   │   │   ├── plugin - │   │   │   │   └── ExamplePlugin.kt + │   │   │   │   └── ExamplePlugin.java │   │   │   ├── schema - │   │   │   │   └── IOUSchema.kt + │   │   │   │   ├── IOUSchema.java + │   │   │   │   └── IOUSchemaV1.java │   │   │   └── state - │   │   │   └── IOUState.kt + │   │   │   └── IOUState.java │   │   └── 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 + │   └── java │   └── com │   └── example - │   ├── Main.kt + │   ├── NodeDriver.java │   ├── contract - │   │   └── IOUContractTests.kt + │   │   └── IOUContractTests.java │   └── flow - │   └── IOUFlowTests.kt + │   └── IOUFlowTests.java + ├── kotlin-source + │   ├── ... + ├── lib + │   ├── README.txt + │   └── quasar.jar ├── .gitignore ├── LICENCE ├── README.md @@ -137,16 +134,15 @@ The key files and directories are as follows: * **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 rewrites our CorDapp's flows to be checkpointable -* **kotlin-source** contains the source code for the example CorDapp written in Kotlin +* **java-source** contains the source code for the example CorDapp written in Java - * **kotlin-source/src/main/kotlin** contains the source code for the example CorDapp - * **kotlin-source/src/main/resources** contains the certificate store, some static web content to be served by the + * **java-source/src/main/java** contains the source code for the example CorDapp + * **java-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 + * **java-source/src/test/java** 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. CorDapps can be developed in any language - targeting the JVM +* **kotlin-source** contains the same source code, but written in Kotlin. CorDapps can be developed in either Java and Kotlin Running the example CorDapp --------------------------- From a867e7cb8b553be82042187e81c026848546c6b2 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 16 Nov 2018 17:14:17 +0000 Subject: [PATCH 09/19] Bugfix: make decimal64 amqp type work (nothing uses it today) --- .../net/corda/serialization/internal/amqp/SerializerFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt index 1cfa23f1eb..8e2e1e1330 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt @@ -118,7 +118,7 @@ interface SerializerFactory { Float::class.java to "float", Double::class.java to "double", Decimal32::class.java to "decimal32", - Decimal64::class.java to "decimal62", + Decimal64::class.java to "decimal64", Decimal128::class.java to "decimal128", Date::class.java to "timestamp", UUID::class.java to "uuid", From 38a473776442890a1be1f50a06a78b1d8719479e Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 16 Nov 2018 17:26:27 +0000 Subject: [PATCH 10/19] Small tweaks useful for the experimental C++ support. --- .../serialization/internal/amqp/AMQPSerializationScheme.kt | 2 +- .../corda/serialization/internal/amqp/PropertySerializers.kt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationScheme.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationScheme.kt index 5be95c4306..1f530f5434 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationScheme.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationScheme.kt @@ -172,7 +172,7 @@ abstract class AbstractAMQPSerializationScheme( // Not used as a simple direct import to facilitate testing open val publicKeySerializer: CustomSerializer<*> = net.corda.serialization.internal.amqp.custom.PublicKeySerializer - private fun getSerializerFactory(context: SerializationContext): SerializerFactory { + fun getSerializerFactory(context: SerializationContext): SerializerFactory { val key = Pair(context.whitelist, context.deserializationClassLoader) // ConcurrentHashMap.get() is lock free, but computeIfAbsent is not, even if the key is in the map already. return serializerFactoriesForContexts[key] ?: serializerFactoriesForContexts.computeIfAbsent(key) { diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializers.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializers.kt index 65a8998a14..517af0406a 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializers.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializers.kt @@ -52,6 +52,8 @@ class PublicPropertyReader(private val readMethod: Method) : PropertyReader() { } override fun isNullable(): Boolean = readMethod.returnsNullable() + + val genericReturnType get() = readMethod.genericReturnType } /** From 2d043828a0de2903102dd2a17776d82d8b39f933 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Mon, 19 Nov 2018 13:42:12 +0000 Subject: [PATCH 11/19] CORDA-2083 verify transaction in AttachmentsClassloader (#4188) CORDA-2083 fix tests CORDA-2083 fix tests CORDA-2083 fix tests CORDA-2083 fix tests CORDA-2083 fix tests CORDA-2083 fix tests CORDA-2083 fix tests CORDA-2083 add support for explicit upgrade transactions CORDA-2083 cleanup CORDA-2083 cleanup CORDA-2083 More cleanup CORDA-2083 More cleanup CORDA-2083 Clean up tests CORDA-2083 Address code review comments CORDA-2083 Fix merge CORDA-2083 Fix merge CORDA-2083 Address code review comments revert file CORDA-2083 Fix test CORDA-2083 Add test CORDA-2083 cleanup CORDA-2083 Fix test CORDA-2083 Address code review comments. CORDA-2083 Remove unused functions. CORDA-2083 Address code review comments. CORDA-2083 Address code review comments. CORDA-2083 Address code review comments. CORDA-2083 Address code review comments. CORDA-2083 Address code review comments. --- .../corda/core/internal/AbstractAttachment.kt | 5 + .../net/corda/core/internal/CordaUtils.kt | 15 + .../net/corda/core/internal/InternalUtils.kt | 20 +- .../corda/core/internal/TransactionUtils.kt | 85 +++- .../core/serialization/SerializationAPI.kt | 4 +- .../internal/AttachmentsClassLoader.kt | 161 ++++++++ .../internal/CheckpointSerializationAPI.kt | 7 - .../ContractUpgradeTransactions.kt | 76 +++- .../core/transactions/LedgerTransaction.kt | 192 ++++++--- .../core/transactions/MerkleTransaction.kt | 83 +--- .../transactions/NotaryChangeTransactions.kt | 16 + .../core/transactions/TransactionBuilder.kt | 2 +- .../core/transactions/WireTransaction.kt | 70 +++- .../net/corda/core/utilities/KotlinUtils.kt | 7 +- .../net/corda/core/flows/AttachmentTests.kt | 25 +- ...ttachmentsClassLoaderSerializationTests.kt | 98 +++++ .../AttachmentsClassLoaderTests.kt | 102 +++++ .../core/transactions/TransactionTests.kt | 4 +- .../corda/nodeapi/DummyContractBackdoor.kt | 0 .../net/corda/core/transactions/isolated.jar | Bin 0 -> 12262 bytes docs/source/changelog.rst | 5 + .../contracts/asset/ObligationTests.kt | 5 +- .../internal/persistence/CordaPersistence.kt | 5 +- .../persistence/HibernateConfiguration.kt | 10 +- .../statemachine/LargeTransactionsTest.kt | 8 +- node/src/main/java/CordaCaplet.java | 2 - .../net/corda/node/internal/AbstractNode.kt | 14 +- .../serialization/kryo/CordaClassResolver.kt | 2 +- .../persistence/NodeAttachmentService.kt | 2 +- .../transactions/MaxTransactionSizeTests.kt | 16 +- .../internal/AttachmentsClassLoaderBuilder.kt | 12 - .../internal/AttachmentsClassLoader.kt | 115 ------ .../internal/CheckpointSerializationScheme.kt | 9 - .../internal/SerializationScheme.kt | 41 +- .../internal/AttachmentsClassLoaderTests.kt | 382 ------------------ .../internal/CordaClassResolverTests.kt | 5 +- .../net/corda/testing/core/TestUtils.kt | 1 + .../testing/internal/InternalTestUtils.kt | 18 +- .../testing/internal/MockCordappProvider.kt | 7 +- 39 files changed, 839 insertions(+), 792 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt create mode 100644 core/src/test/kotlin/net/corda/core/transactions/AttachmentsClassLoaderSerializationTests.kt create mode 100644 core/src/test/kotlin/net/corda/core/transactions/AttachmentsClassLoaderTests.kt rename {serialization => core}/src/test/kotlin/net/corda/nodeapi/DummyContractBackdoor.kt (100%) create mode 100644 core/src/test/resources/net/corda/core/transactions/isolated.jar delete mode 100644 serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderBuilder.kt delete mode 100644 serialization/src/main/kotlin/net/corda/serialization/internal/AttachmentsClassLoader.kt delete mode 100644 serialization/src/test/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderTests.kt diff --git a/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt index c509abdd4f..5b27d28ef5 100644 --- a/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt +++ b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt @@ -27,6 +27,11 @@ fun isUploaderTrusted(uploader: String?): Boolean = uploader in TRUSTED_UPLOADER @KeepForDJVM abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment { companion object { + /** + * Returns a function that knows how to load an attachment. + * + * TODO - this code together with the rest of the Attachment handling (including [FetchedAttachment]) needs some refactoring as it is really hard to follow. + */ @DeleteForDJVM fun SerializeAsTokenContext.attachmentDataLoader(id: SecureHash): () -> ByteArray { return { diff --git a/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt b/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt index 6595851d22..5c07f82117 100644 --- a/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt @@ -1,6 +1,10 @@ package net.corda.core.internal import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM +import net.corda.core.contracts.ContractState +import net.corda.core.contracts.StateRef +import net.corda.core.contracts.TransactionState import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.CordappConfig import net.corda.core.cordapp.CordappContext @@ -8,11 +12,14 @@ import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowLogic import net.corda.core.node.ServicesForResolution import net.corda.core.node.ZoneVersionTooLowException +import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.SerializedBytes 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.OpaqueBytes import org.slf4j.MDC // *Internal* Corda-specific utilities @@ -73,3 +80,11 @@ class LazyMappedList(val originalList: List, val transform: (T, Int) -> override fun get(index: Int) = partialResolvedList[index] ?: transform(originalList[index], index).also { computed -> partialResolvedList[index] = computed } } + +/** + * A SerializedStateAndRef is a pair (BinaryStateRepresentation, StateRef). + * The [serializedState] is the actual component from the original transaction. + */ +@KeepForDJVM +@CordaSerializable +data class SerializedStateAndRef(val serializedState: SerializedBytes>, val ref: StateRef) \ 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 ac306fbdbb..1a0a1d5143 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -221,15 +221,16 @@ data class InputStreamAndHash(val inputStream: InputStream, val sha256: SecureHa * Note that a slightly bigger than numOfExpectedBytes size is expected. */ @DeleteForDJVM - fun createInMemoryTestZip(numOfExpectedBytes: Int, content: Byte): InputStreamAndHash { + fun createInMemoryTestZip(numOfExpectedBytes: Int, content: Byte, entryName: String = "z"): InputStreamAndHash { require(numOfExpectedBytes > 0){"Expected bytes must be greater than zero"} + 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")) + zos.putNextEntry(ZipEntry(entryName)) for (i in 0 until n) { zos.write(bytes, 0, arraySize) } @@ -501,3 +502,18 @@ fun SerializedBytes.checkPayloadIs(type: Class): Untrustworthy return type.castIfPossible(payloadData)?.let { UntrustworthyData(it) } ?: throw IllegalArgumentException("We were expecting a ${type.name} but we instead got a ${payloadData.javaClass.name} ($payloadData)") } + +/** + * Simple Map structure that can be used as a cache in the DJVM. + */ +fun createSimpleCache(maxSize: Int, onEject: (MutableMap.MutableEntry) -> Unit = {}): MutableMap { + return object : LinkedHashMap() { + override fun removeEldestEntry(eldest: MutableMap.MutableEntry?): Boolean { + val eject = size > maxSize + if (eject) onEject(eldest!!) + return eject + } + } +} + +fun MutableMap.toSynchronised(): MutableMap = Collections.synchronizedMap(this) \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/internal/TransactionUtils.kt b/core/src/main/kotlin/net/corda/core/internal/TransactionUtils.kt index 3e774b4b6e..b8d574803e 100644 --- a/core/src/main/kotlin/net/corda/core/internal/TransactionUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/TransactionUtils.kt @@ -1,15 +1,23 @@ package net.corda.core.internal -import net.corda.core.contracts.ContractClassName -import net.corda.core.contracts.PrivacySalt -import net.corda.core.contracts.StateRef +import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.componentHash import net.corda.core.crypto.sha256 import net.corda.core.identity.Party +import net.corda.core.serialization.MissingAttachmentsException +import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.SerializationFactory import net.corda.core.serialization.serialize +import net.corda.core.transactions.ComponentGroup import net.corda.core.transactions.ContractUpgradeWireTransaction +import net.corda.core.transactions.FilteredComponentGroup import net.corda.core.transactions.NotaryChangeWireTransaction +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.lazyMapped import java.io.ByteArrayOutputStream +import java.security.PublicKey +import kotlin.reflect.KClass /** Constructs a [NotaryChangeWireTransaction]. */ class NotaryChangeTransactionBuilder(val inputs: List, @@ -42,4 +50,75 @@ fun combinedHash(components: Iterable): SecureHash { stream.write(it.bytes) } return stream.toByteArray().sha256() +} + +/** + * This function knows how to deserialize a transaction component group. + * + * In case the [componentGroups] is an instance of [LazyMappedList], this function will just use the original deserialized version, and avoid an unnecessary deserialization. + * The [forceDeserialize] will force deserialization. In can be used in case the SerializationContext changes. + */ +fun deserialiseComponentGroup(componentGroups: List, + clazz: KClass, + groupEnum: ComponentGroupEnum, + forceDeserialize: Boolean = false, + factory: SerializationFactory = SerializationFactory.defaultFactory, + context: SerializationContext = factory.defaultContext): List { + val group = componentGroups.firstOrNull { it.groupIndex == groupEnum.ordinal } + + if (group == null || group.components.isEmpty()) { + return emptyList() + } + + // If the componentGroup is a [LazyMappedList] it means that the original deserialized version is already available. + val components = group.components + if (!forceDeserialize && components is LazyMappedList<*, OpaqueBytes>) { + return components.originalList as List + } + + return components.lazyMapped { component, internalIndex -> + try { + factory.deserialize(component, clazz.java, context) + } catch (e: MissingAttachmentsException) { + throw e + } catch (e: Exception) { + throw Exception("Malformed transaction, $groupEnum at index $internalIndex cannot be deserialised", e) + } + } +} + +/** + * Method to deserialise Commands from its two groups: + * * COMMANDS_GROUP which contains the CommandData part + * * and SIGNERS_GROUP which contains the Signers part. + * + * This method used the [deserialiseComponentGroup] method. + */ +fun deserialiseCommands(componentGroups: List, + forceDeserialize: Boolean = false, + factory: SerializationFactory = SerializationFactory.defaultFactory, + context: SerializationContext = factory.defaultContext): List> { + // TODO: we could avoid deserialising unrelated signers. + // However, current approach ensures the transaction is not malformed + // and it will throw if any of the signers objects is not List of public keys). + val signersList: List> = uncheckedCast(deserialiseComponentGroup(componentGroups, List::class, ComponentGroupEnum.SIGNERS_GROUP, forceDeserialize)) + val commandDataList: List = deserialiseComponentGroup(componentGroups, CommandData::class, ComponentGroupEnum.COMMANDS_GROUP, forceDeserialize) + val group = componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal } + return if (group is FilteredComponentGroup) { + check(commandDataList.size <= signersList.size) { + "Invalid Transaction. Less Signers (${signersList.size}) than CommandData (${commandDataList.size}) objects" + } + val componentHashes = group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) } + val leafIndices = componentHashes.map { group.partialMerkleTree.leafIndex(it) } + if (leafIndices.isNotEmpty()) + check(leafIndices.max()!! < signersList.size) { "Invalid Transaction. A command with no corresponding signer detected" } + commandDataList.lazyMapped { commandData, index -> Command(commandData, signersList[leafIndices[index]]) } + } else { + // It is a WireTransaction + // or a FilteredTransaction with no Commands (in which case group is null). + check(commandDataList.size == signersList.size) { + "Invalid Transaction. Sizes of CommandData (${commandDataList.size}) and Signers (${signersList.size}) do not match" + } + commandDataList.lazyMapped { commandData, index -> Command(commandData, signersList[index]) } + } } \ No newline at end of file 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 3a0ee16ce0..941617ba04 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt @@ -177,10 +177,10 @@ interface SerializationContext { fun withClassLoader(classLoader: ClassLoader): SerializationContext /** - * Helper method to return a new context based on this context with the appropriate class loader constructed from the passed attachment identifiers. - * (Requires the attachment storage to have been enabled). + * Does not do anything. */ @Throws(MissingAttachmentsException::class) + @Deprecated("There is no reason to call this. This method does not actually do anything.") fun withAttachmentsClassLoader(attachmentHashes: List): SerializationContext /** diff --git a/core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt b/core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt new file mode 100644 index 0000000000..1a8d25437c --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt @@ -0,0 +1,161 @@ +package net.corda.core.serialization.internal + +import net.corda.core.contracts.Attachment +import net.corda.core.contracts.ContractAttachment +import net.corda.core.crypto.SecureHash +import net.corda.core.internal.VisibleForTesting +import net.corda.core.internal.isUploaderTrusted +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.SerializationFactory +import net.corda.core.serialization.internal.AttachmentURLStreamHandlerFactory.toUrl +import net.corda.core.internal.createSimpleCache +import net.corda.core.internal.toSynchronised +import java.io.IOException +import java.io.InputStream +import java.net.* + +/** + * A custom ClassLoader that knows how to load classes from a set of attachments. The attachments themselves only + * need to provide JAR streams, and so could be fetched from a database, local disk, etc. Constructing an + * AttachmentsClassLoader is somewhat expensive, as every attachment is scanned to ensure that there are no overlapping + * file paths. + */ +class AttachmentsClassLoader(attachments: List, parent: ClassLoader = ClassLoader.getSystemClassLoader()) : + URLClassLoader(attachments.map(::toUrl).toTypedArray(), parent) { + + companion object { + + init { + // This is required to register the AttachmentURLStreamHandlerFactory. + URL.setURLStreamHandlerFactory(AttachmentURLStreamHandlerFactory) + } + + private const val `META-INF` = "meta-inf" + private val excludeFromNoOverlapCheck = setOf( + "manifest.mf", + "license", + "license.txt", + "notice", + "notice.txt", + "index.list" + ) + + private fun shouldCheckForNoOverlap(path: String): Boolean { + if (!path.startsWith(`META-INF`)) return true + val p = path.substring(`META-INF`.length + 1) + if (p in excludeFromNoOverlapCheck) return false + if (p.endsWith(".sf") || p.endsWith(".dsa")) return false + return true + } + + @CordaSerializable + class OverlappingAttachments(val path: String) : Exception() { + override fun toString() = "Multiple attachments define a file at path $path" + } + + private fun requireNoDuplicates(attachments: List) { + val classLoaderEntries = mutableSetOf() + for (attachment in attachments) { + attachment.openAsJAR().use { jar -> + while (true) { + val entry = jar.nextJarEntry ?: break + + // We already verified that paths are not strange/game playing when we inserted the attachment + // into the storage service. So we don't need to repeat it here. + // + // We forbid files that differ only in case, or path separator to avoid issues for Windows/Mac developers where the + // filesystem tries to be case insensitive. This may break developers who attempt to use ProGuard. + // + // Also convert to Unix path separators as all resource/class lookups will expect this. + // If 2 entries have the same CRC, it means the same file is present in both attachments, so that is ok. TODO - Mike, wdyt? + val path = entry.name.toLowerCase().replace('\\', '/') + if (shouldCheckForNoOverlap(path)) { + if (path in classLoaderEntries) throw OverlappingAttachments(path) + classLoaderEntries.add(path) + } + } + } + } + } + } + + init { + require(attachments.mapNotNull { it as? ContractAttachment }.all { isUploaderTrusted(it.uploader) }) { + "Attempting to load Contract Attachments downloaded from the network" + } + + requireNoDuplicates(attachments) + } +} + +/** + * This is just a factory that provides a cache to avoid constructing expensive [AttachmentsClassLoader]s. + */ +@VisibleForTesting +internal object AttachmentsClassLoaderBuilder { + + private const val ATTACHMENT_CLASSLOADER_CACHE_SIZE = 1000 + + // This runs in the DJVM so it can't use caffeine. + private val cache: MutableMap, AttachmentsClassLoader> = createSimpleCache, AttachmentsClassLoader>(ATTACHMENT_CLASSLOADER_CACHE_SIZE) + .toSynchronised() + + fun build(attachments: List): AttachmentsClassLoader { + return cache.computeIfAbsent(attachments.map { it.id }.sorted()) { + AttachmentsClassLoader(attachments) + } + } + + fun withAttachmentsClassloaderContext(attachments: List, block: (ClassLoader) -> T): T { + + // Create classloader from the attachments. + val transactionClassLoader = AttachmentsClassLoaderBuilder.build(attachments) + + // Create a new serializationContext for the current Transaction. + val transactionSerializationContext = SerializationFactory.defaultFactory.defaultContext.withClassLoader(transactionClassLoader) + + // Deserialize all relevant classes in the transaction classloader. + return SerializationFactory.defaultFactory.withCurrentContext(transactionSerializationContext) { + block(transactionClassLoader) + } + } +} + +/** + * Registers a new internal "attachment" protocol. + * This will not be exposed as an API. + */ +object AttachmentURLStreamHandlerFactory : URLStreamHandlerFactory { + private const val attachmentScheme = "attachment" + + // TODO - what happens if this grows too large? + private val loadedAttachments = mutableMapOf().toSynchronised() + + override fun createURLStreamHandler(protocol: String): URLStreamHandler? { + return if (attachmentScheme == protocol) { + AttachmentURLStreamHandler + } else null + } + + fun toUrl(attachment: Attachment): URL { + val id = attachment.id.toString() + loadedAttachments[id] = attachment + return URL(attachmentScheme, "", -1, id, AttachmentURLStreamHandler) + } + + private object AttachmentURLStreamHandler : URLStreamHandler() { + override fun openConnection(url: URL): URLConnection { + if (url.protocol != attachmentScheme) throw IOException("Cannot handle protocol: ${url.protocol}") + val attachment = loadedAttachments[url.path] ?: throw IOException("Could not load url: $url .") + return AttachmentURLConnection(url, attachment) + } + } + + private class AttachmentURLConnection(url: URL, private val attachment: Attachment) : URLConnection(url) { + override fun getContentLengthLong(): Long = attachment.size.toLong() + override fun getInputStream(): InputStream = attachment.open() + override fun connect() { + connected = true + } + } +} diff --git a/core/src/main/kotlin/net/corda/core/serialization/internal/CheckpointSerializationAPI.kt b/core/src/main/kotlin/net/corda/core/serialization/internal/CheckpointSerializationAPI.kt index 6769b73b03..f870e9aa0e 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/internal/CheckpointSerializationAPI.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/internal/CheckpointSerializationAPI.kt @@ -73,13 +73,6 @@ interface CheckpointSerializationContext { */ fun withClassLoader(classLoader: ClassLoader): CheckpointSerializationContext - /** - * Helper method to return a new context based on this context with the appropriate class loader constructed from the passed attachment identifiers. - * (Requires the attachment storage to have been enabled). - */ - @Throws(MissingAttachmentsException::class) - fun withAttachmentsClassLoader(attachmentHashes: List): CheckpointSerializationContext - /** * Helper method to return a new context based on this context with the given class specifically whitelisted. */ diff --git a/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt b/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt index ddadaefc0f..0ea2c661aa 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt @@ -1,5 +1,6 @@ package net.corda.core.transactions +import net.corda.core.CordaInternal import net.corda.core.KeepForDJVM import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash @@ -11,10 +12,12 @@ import net.corda.core.internal.AttachmentWithContext import net.corda.core.internal.combinedHash import net.corda.core.node.NetworkParameters import net.corda.core.node.ServicesForResolution -import net.corda.core.serialization.CordaSerializable -import net.corda.core.serialization.deserialize +import net.corda.core.serialization.* +import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder import net.corda.core.transactions.ContractUpgradeFilteredTransaction.FilteredComponent +import net.corda.core.transactions.ContractUpgradeWireTransaction.Companion.calculateUpgradedState import net.corda.core.transactions.ContractUpgradeWireTransaction.Component.* +import net.corda.core.transactions.WireTransaction.Companion.resolveStateRefBinaryComponent import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.toBase58String import java.security.PublicKey @@ -35,6 +38,32 @@ data class ContractUpgradeWireTransaction( /** Required for hiding components in [ContractUpgradeFilteredTransaction]. */ val privacySalt: PrivacySalt = PrivacySalt() ) : CoreTransaction() { + + companion object { + /** + * Runs the explicit upgrade logic. + */ + @CordaInternal + internal fun calculateUpgradedState(state: TransactionState, upgradedContract: UpgradedContract, upgradedContractAttachment: Attachment): TransactionState { + // TODO: if there are encumbrance states in the inputs, just copy them across without modifying + val upgradedState: S = upgradedContract.upgrade(state.data) + val inputConstraint = state.constraint + val outputConstraint = when (inputConstraint) { + is HashAttachmentConstraint -> HashAttachmentConstraint(upgradedContractAttachment.id) + WhitelistedByZoneAttachmentConstraint -> WhitelistedByZoneAttachmentConstraint + else -> throw IllegalArgumentException("Unsupported input contract constraint $inputConstraint") + } + // TODO: re-map encumbrance pointers + return TransactionState( + data = upgradedState, + contract = upgradedContract::class.java.name, + constraint = outputConstraint, + notary = state.notary, + encumbrance = state.encumbrance + ) + } + } + override val inputs: List = serializedComponents[INPUTS.ordinal].deserialize() override val notary: Party by lazy { serializedComponents[NOTARY.ordinal].deserialize() } val legacyContractAttachmentId: SecureHash by lazy { serializedComponents[LEGACY_ATTACHMENT.ordinal].deserialize() } @@ -90,6 +119,32 @@ data class ContractUpgradeWireTransaction( ) } + private fun upgradedContract(className: ContractClassName, classLoader: ClassLoader): UpgradedContract = try { + classLoader.loadClass(className).asSubclass(UpgradedContract::class.java as Class>) + .newInstance() + } catch (e: Exception) { + throw TransactionVerificationException.ContractCreationError(id, className, e) + } + + /** + * Creates a binary serialized component for a virtual output state serialised and executed with the attachments from the transaction. + */ + @CordaInternal + internal fun resolveOutputComponent(services: ServicesForResolution, stateRef: StateRef): SerializedBytes> { + val binaryInput = resolveStateRefBinaryComponent(inputs[stateRef.index], services)!! + val legacyAttachment = services.attachments.openAttachment(legacyContractAttachmentId) + ?: throw MissingContractAttachments(emptyList()) + val upgradedAttachment = services.attachments.openAttachment(upgradedContractAttachmentId) + ?: throw MissingContractAttachments(emptyList()) + + return AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(listOf(legacyAttachment, upgradedAttachment)) { transactionClassLoader -> + val resolvedInput = binaryInput.deserialize>() + val upgradedContract = upgradedContract(upgradedContractClassName, transactionClassLoader) + val outputState = calculateUpgradedState(resolvedInput, upgradedContract, upgradedAttachment) + outputState.serialize() + } + } + /** Constructs a filtered transaction: the inputs and the notary party are always visible, while the rest are hidden. */ fun buildFilteredTransaction(): ContractUpgradeFilteredTransaction { val totalComponents = (0 until serializedComponents.size).toSet() @@ -222,22 +277,7 @@ data class ContractUpgradeLedgerTransaction( * Outputs are computed by running the contract upgrade logic on input states. This is done eagerly so that the * transaction is verified during construction. */ - override val outputs: List> = inputs.map { (state) -> - // TODO: if there are encumbrance states in the inputs, just copy them across without modifying - val upgradedState = upgradedContract.upgrade(state.data) - val inputConstraint = state.constraint - val outputConstraint = when (inputConstraint) { - is HashAttachmentConstraint -> HashAttachmentConstraint(upgradedContractAttachment.id) - WhitelistedByZoneAttachmentConstraint -> WhitelistedByZoneAttachmentConstraint - else -> throw IllegalArgumentException("Unsupported input contract constraint $inputConstraint") - } - // TODO: re-map encumbrance pointers - state.copy( - data = upgradedState, - contract = upgradedContractClassName, - constraint = outputConstraint - ) - } + override val outputs: List> = inputs.map { calculateUpgradedState(it.state, upgradedContract, upgradedContractAttachment) } /** The required signers are the set of all input states' participants. */ override val requiredSigningKeys: Set 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 c72221a204..222a04897c 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt @@ -1,22 +1,21 @@ package net.corda.core.transactions +import net.corda.core.CordaInternal import net.corda.core.KeepForDJVM import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.crypto.isFulfilledBy import net.corda.core.identity.Party -import net.corda.core.internal.AttachmentWithContext -import net.corda.core.internal.castIfPossible -import net.corda.core.internal.checkMinimumPlatformVersion -import net.corda.core.internal.uncheckedCast +import net.corda.core.internal.* import net.corda.core.node.NetworkParameters import net.corda.core.serialization.CordaSerializable -import net.corda.core.utilities.Try +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.warnOnce import java.util.* import java.util.function.Predicate import kotlin.collections.HashSet -import net.corda.core.utilities.warnOnce /** * A LedgerTransaction is derived from a [WireTransaction]. It is the result of doing the following operations: @@ -34,7 +33,7 @@ import net.corda.core.utilities.warnOnce // DOCSTART 1 @KeepForDJVM @CordaSerializable -data class LedgerTransaction @JvmOverloads constructor( +data class LedgerTransaction private constructor( /** The resolved input states which will be consumed/invalidated by the execution of this transaction. */ override val inputs: List>, override val outputs: List>, @@ -47,9 +46,38 @@ data class LedgerTransaction @JvmOverloads constructor( override val notary: Party?, val timeWindow: TimeWindow?, val privacySalt: PrivacySalt, - private val networkParameters: NetworkParameters? = null, - override val references: List> = emptyList() + private val networkParameters: NetworkParameters?, + override val references: List>, + val componentGroups: List?, + val resolvedInputBytes: List?, + val resolvedReferenceBytes: List? ) : FullTransaction() { + + @Deprecated("Client code should not instantiate LedgerTransaction.") + constructor( + inputs: List>, + outputs: List>, + commands: List>, + attachments: List, + id: SecureHash, + notary: Party?, + timeWindow: TimeWindow?, + privacySalt: PrivacySalt + ) : this(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, null, emptyList(), null, null, null) + + @Deprecated("Client code should not instantiate LedgerTransaction.") + constructor( + inputs: List>, + outputs: List>, + commands: List>, + attachments: List, + id: SecureHash, + notary: Party?, + timeWindow: TimeWindow?, + privacySalt: PrivacySalt, + networkParameters: NetworkParameters? + ) : this(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, emptyList(), null, null, null) + //DOCEND 1 init { checkBaseInvariants() @@ -58,19 +86,25 @@ data class LedgerTransaction @JvmOverloads constructor( checkEncumbrancesValid() } - private companion object { - val logger = loggerFor() - private fun contractClassFor(className: ContractClassName, classLoader: ClassLoader?): Try> { - return Try.on { - (classLoader ?: this::class.java.classLoader) - .loadClass(className) - .asSubclass(Contract::class.java) - } - } + companion object { + private val logger = loggerFor() - private fun stateToContractClass(state: TransactionState): Try> { - return contractClassFor(state.contract, state.data::class.java.classLoader) - } + @CordaInternal + internal fun makeLedgerTransaction( + inputs: List>, + outputs: List>, + commands: List>, + attachments: List, + id: SecureHash, + notary: Party?, + timeWindow: TimeWindow?, + privacySalt: PrivacySalt, + networkParameters: NetworkParameters?, + references: List>, + componentGroups: List, + resolvedInputBytes: List, + resolvedReferenceBytes: List + ) = LedgerTransaction(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, componentGroups, resolvedInputBytes, resolvedReferenceBytes) } val inputStates: List get() = inputs.map { it.state.data } @@ -88,6 +122,12 @@ data class LedgerTransaction @JvmOverloads constructor( /** * Verifies this transaction and runs contract code. At this stage it is assumed that signatures have already been verified. + + * The contract verification logic is run in a custom [AttachmentsClassLoader] created for the current transaction. + * This classloader is only used during verification and does not leak to the client code. + * + * The reason for this is that classes (contract states) deserialized in this classloader would actually be a different type from what + * the calling code would expect. * * @throws TransactionVerificationException if anything goes wrong. */ @@ -95,12 +135,17 @@ data class LedgerTransaction @JvmOverloads constructor( fun verify() { val contractAttachmentsByContract: Map = getUniqueContractAttachmentsByContract() - // TODO - verify for version downgrade - validatePackageOwnership(contractAttachmentsByContract) - validateStatesAgainstContract() - verifyConstraintsValidity(contractAttachmentsByContract) - verifyConstraints(contractAttachmentsByContract) - verifyContracts() + AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(this.attachments) { transactionClassLoader -> + + val internalTx = createInternalLedgerTransaction() + + // TODO - verify for version downgrade + validatePackageOwnership(contractAttachmentsByContract) + validateStatesAgainstContract(internalTx) + verifyConstraintsValidity(internalTx, contractAttachmentsByContract, transactionClassLoader) + verifyConstraints(internalTx, contractAttachmentsByContract) + verifyContracts(internalTx) + } } /** @@ -133,7 +178,7 @@ data class LedgerTransaction @JvmOverloads constructor( * * A warning will be written to the log if any mismatch is detected. */ - private fun validateStatesAgainstContract() = allStates.forEach(::validateStateAgainstContract) + private fun validateStatesAgainstContract(internalTx: LedgerTransaction) = internalTx.allStates.forEach { validateStateAgainstContract(it) } private fun validateStateAgainstContract(state: TransactionState) { state.data.requiredContractClassName?.let { requiredContractClassName -> @@ -150,25 +195,25 @@ data class LedgerTransaction @JvmOverloads constructor( * * Constraints should be one of the valid supported ones. * * Constraints should propagate correctly if not marked otherwise. */ - private fun verifyConstraintsValidity(contractAttachmentsByContract: Map) { + private fun verifyConstraintsValidity(internalTx: LedgerTransaction, contractAttachmentsByContract: Map, transactionClassLoader: ClassLoader) { // First check that the constraints are valid. - for (state in allStates) { + for (state in internalTx.allStates) { checkConstraintValidity(state) } // Group the inputs and outputs by contract, and for each contract verify the constraints propagation logic. // This is not required for reference states as there is nothing to propagate. - val inputContractGroups = inputs.groupBy { it.state.contract } - val outputContractGroups = outputs.groupBy { it.contract } + val inputContractGroups = internalTx.inputs.groupBy { it.state.contract } + val outputContractGroups = internalTx.outputs.groupBy { it.contract } for (contractClassName in (inputContractGroups.keys + outputContractGroups.keys)) { - if (contractClassName.contractHasAutomaticConstraintPropagation()) { + if (contractClassName.contractHasAutomaticConstraintPropagation(transactionClassLoader)) { // Verify that the constraints of output states have at least the same level of restriction as the constraints of the corresponding input states. val inputConstraints = inputContractGroups[contractClassName]?.map { it.state.constraint }?.toSet() val outputConstraints = outputContractGroups[contractClassName]?.map { it.constraint }?.toSet() outputConstraints?.forEach { outputConstraint -> inputConstraints?.forEach { inputConstraint -> - if (!(outputConstraint.canBeTransitionedFrom(inputConstraint, contractAttachmentsByContract[contractClassName]!! ))) { + if (!(outputConstraint.canBeTransitionedFrom(inputConstraint, contractAttachmentsByContract[contractClassName]!!))) { throw TransactionVerificationException.ConstraintPropagationRejection(id, contractClassName, inputConstraint, outputConstraint) } } @@ -186,8 +231,8 @@ data class LedgerTransaction @JvmOverloads constructor( * * @throws TransactionVerificationException if the constraints fail to verify */ - private fun verifyConstraints(contractAttachmentsByContract: Map) { - for (state in allStates) { + private fun verifyConstraints(internalTx: LedgerTransaction, contractAttachmentsByContract: Map) { + for (state in internalTx.allStates) { val contractAttachment = contractAttachmentsByContract[state.contract] ?: throw TransactionVerificationException.MissingAttachmentRejection(id, state.contract) @@ -226,38 +271,64 @@ data class LedgerTransaction @JvmOverloads constructor( return result } + private fun contractClassFor(className: ContractClassName, classLoader: ClassLoader): Class = try { + classLoader.loadClass(className).asSubclass(Contract::class.java) + } catch (e: Exception) { + throw TransactionVerificationException.ContractCreationError(id, className, e) + } + + private fun createInternalLedgerTransaction(): LedgerTransaction { + return if (resolvedInputBytes != null && resolvedReferenceBytes != null && componentGroups != null) { + + // Deserialize all relevant classes in the transaction classloader. + val resolvedDeserializedInputs = resolvedInputBytes.map { StateAndRef(it.serializedState.deserialize(), it.ref) } + val resolvedDeserializedReferences = resolvedReferenceBytes.map { StateAndRef(it.serializedState.deserialize(), it.ref) } + val deserializedOutputs = deserialiseComponentGroup(componentGroups, TransactionState::class, ComponentGroupEnum.OUTPUTS_GROUP, forceDeserialize = true) + val deserializedCommands = deserialiseCommands(this.componentGroups, forceDeserialize = true) + val authenticatedArgs = deserializedCommands.map { cmd -> + val parties = commands.find { it.value.javaClass.name == cmd.value.javaClass.name }!!.signingParties + CommandWithParties(cmd.signers, parties, cmd.value) + } + + val ledgerTransactionToVerify = this.copy( + inputs = resolvedDeserializedInputs, + outputs = deserializedOutputs, + commands = authenticatedArgs, + references = resolvedDeserializedReferences) + + ledgerTransactionToVerify + } else { + // This branch is only present for backwards compatibility. + // TODO - it should be removed once the constructor of LedgerTransaction is no longer public api. + logger.warn("The LedgerTransaction should not be instantiated directly from client code. Please use WireTransaction.toLedgerTransaction. The result of the verify method might not be accurate.") + this + } + } + /** * Check the transaction is contract-valid by running the verify() for each input and output state contract. * If any contract fails to verify, the whole transaction is considered to be invalid. */ - private fun verifyContracts() = inputAndOutputStates.forEach { ts -> - val contractClass = getContractClass(ts) - val contract = createContractInstance(contractClass) + private fun verifyContracts(internalTx: LedgerTransaction) { + val contractClasses = (internalTx.inputs.map { it.state } + internalTx.outputs).toSet() + .map { it.contract to contractClassFor(it.contract, it.data.javaClass.classLoader) } - try { - contract.verify(this) - } catch (e: Exception) { - throw TransactionVerificationException.ContractRejection(id, contract, e) - } - } - - // Obtain the contract class from the class name, wrapping any exception as a [ContractCreationError] - private fun getContractClass(ts: TransactionState): Class = - try { - (ts.data::class.java.classLoader ?: this::class.java.classLoader) - .loadClass(ts.contract) - .asSubclass(Contract::class.java) - } catch (e: Exception) { - throw TransactionVerificationException.ContractCreationError(id, ts.contract, e) - } - - // Obtain an instance of the contract class, wrapping any exception as a [ContractCreationError] - private fun createContractInstance(contractClass: Class): Contract = + val contractInstances = contractClasses.map { (contractClassName, contractClass) -> try { contractClass.newInstance() } catch (e: Exception) { - throw TransactionVerificationException.ContractCreationError(id, contractClass.name, e) + throw TransactionVerificationException.ContractCreationError(id, contractClassName, e) } + } + + contractInstances.forEach { contract -> + try { + contract.verify(internalTx) + } catch (e: Exception) { + throw TransactionVerificationException.ContractRejection(id, contract, e) + } + } + } /** * Make sure the notary has stayed the same. As we can't tell how inputs and outputs connect, if there @@ -286,7 +357,8 @@ data class LedgerTransaction @JvmOverloads constructor( // b) the number of outputs can contain the encumbrance // c) the bi-directionality (full cycle) property is satisfied // d) encumbered output states are assigned to the same notary. - val statesAndEncumbrance = outputs.withIndex().filter { it.value.encumbrance != null }.map { Pair(it.index, it.value.encumbrance!!) } + val statesAndEncumbrance = outputs.withIndex().filter { it.value.encumbrance != null } + .map { Pair(it.index, it.value.encumbrance!!) } if (!statesAndEncumbrance.isEmpty()) { checkBidirectionalOutputEncumbrances(statesAndEncumbrance) checkNotariesOutputEncumbrance(statesAndEncumbrance) 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 811b3dfab6..e91f867067 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt @@ -6,14 +6,14 @@ import net.corda.core.contracts.* import net.corda.core.contracts.ComponentGroupEnum.* import net.corda.core.crypto.* import net.corda.core.identity.Party -import net.corda.core.internal.LazyMappedList -import net.corda.core.internal.uncheckedCast -import net.corda.core.serialization.* +import net.corda.core.internal.deserialiseCommands +import net.corda.core.internal.deserialiseComponentGroup +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.SerializedBytes +import net.corda.core.serialization.deserialize import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.lazyMapped import java.security.PublicKey import java.util.function.Predicate -import kotlin.reflect.KClass /** * Implemented by [WireTransaction] and [FilteredTransaction]. A TraversableTransaction allows you to iterate @@ -23,27 +23,27 @@ import kotlin.reflect.KClass */ abstract class TraversableTransaction(open val componentGroups: List) : CoreTransaction() { /** Hashes of the ZIP/JAR files that are needed to interpret the contents of this wire transaction. */ - val attachments: List = deserialiseComponentGroup(SecureHash::class, ATTACHMENTS_GROUP) + val attachments: List = deserialiseComponentGroup(componentGroups, SecureHash::class, ATTACHMENTS_GROUP) /** Pointers to the input states on the ledger, identified by (tx identity hash, output index). */ - override val inputs: List = deserialiseComponentGroup(StateRef::class, INPUTS_GROUP) + override val inputs: List = deserialiseComponentGroup(componentGroups, StateRef::class, INPUTS_GROUP) /** Pointers to reference states, identified by (tx identity hash, output index). */ - override val references: List = deserialiseComponentGroup(StateRef::class, REFERENCES_GROUP) + override val references: List = deserialiseComponentGroup(componentGroups, StateRef::class, REFERENCES_GROUP) - override val outputs: List> = deserialiseComponentGroup(TransactionState::class, OUTPUTS_GROUP, attachmentsContext = true) + override val outputs: List> = deserialiseComponentGroup(componentGroups, TransactionState::class, OUTPUTS_GROUP) /** Ordered list of ([CommandData], [PublicKey]) pairs that instruct the contracts what to do. */ - val commands: List> = deserialiseCommands() + val commands: List> = deserialiseCommands(componentGroups) override val notary: Party? = let { - val notaries: List = deserialiseComponentGroup(Party::class, NOTARY_GROUP) + val notaries: List = deserialiseComponentGroup(componentGroups, Party::class, NOTARY_GROUP) check(notaries.size <= 1) { "Invalid Transaction. More than 1 notary party detected." } notaries.firstOrNull() } val timeWindow: TimeWindow? = let { - val timeWindows: List = deserialiseComponentGroup(TimeWindow::class, TIMEWINDOW_GROUP) + val timeWindows: List = deserialiseComponentGroup(componentGroups, TimeWindow::class, TIMEWINDOW_GROUP) check(timeWindows.size <= 1) { "Invalid Transaction. More than 1 time-window detected." } timeWindows.firstOrNull() } @@ -66,65 +66,6 @@ abstract class TraversableTransaction(open val componentGroups: List deserialiseComponentGroup(clazz: KClass, - groupEnum: ComponentGroupEnum, - attachmentsContext: Boolean = false): List { - val group = componentGroups.firstOrNull { it.groupIndex == groupEnum.ordinal } - - if (group == null || group.components.isEmpty()) { - return emptyList() - } - - // If the componentGroup is a [LazyMappedList] it means that the original deserialized version is already available. - val components = group.components - if (components is LazyMappedList<*, OpaqueBytes>) { - return components.originalList as List - } - - val factory = SerializationFactory.defaultFactory - val context = factory.defaultContext.let { if (attachmentsContext) it.withAttachmentsClassLoader(attachments) else it } - - return components.lazyMapped { component, internalIndex -> - try { - factory.deserialize(component, clazz.java , context) - } catch (e: MissingAttachmentsException) { - throw e - } catch (e: Exception) { - throw Exception("Malformed transaction, $groupEnum at index $internalIndex cannot be deserialised", e) - } - } - } - - // Method to deserialise Commands from its two groups: - // COMMANDS_GROUP which contains the CommandData part - // and SIGNERS_GROUP which contains the Signers part. - private fun deserialiseCommands(): List> { - // TODO: we could avoid deserialising unrelated signers. - // However, current approach ensures the transaction is not malformed - // and it will throw if any of the signers objects is not List of public keys). - val signersList: List> = uncheckedCast(deserialiseComponentGroup(List::class, SIGNERS_GROUP)) - val commandDataList: List = deserialiseComponentGroup(CommandData::class, COMMANDS_GROUP, attachmentsContext = true) - val group = componentGroups.firstOrNull { it.groupIndex == COMMANDS_GROUP.ordinal } - return if (group is FilteredComponentGroup) { - check(commandDataList.size <= signersList.size) { - "Invalid Transaction. Less Signers (${signersList.size}) than CommandData (${commandDataList.size}) objects" - } - val componentHashes = group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) } - val leafIndices = componentHashes.map { group.partialMerkleTree.leafIndex(it) } - if (leafIndices.isNotEmpty()) - check(leafIndices.max()!! < signersList.size) { "Invalid Transaction. A command with no corresponding signer detected" } - commandDataList.lazyMapped { commandData, index -> Command(commandData, signersList[leafIndices[index]]) } - } else { - // It is a WireTransaction - // or a FilteredTransaction with no Commands (in which case group is null). - check(commandDataList.size == signersList.size) { - "Invalid Transaction. Sizes of CommandData (${commandDataList.size}) and Signers (${signersList.size}) do not match" - } - commandDataList.lazyMapped { commandData, index -> Command(commandData, signersList[index]) } - } - } } /** diff --git a/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt b/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt index 55f329540d..98c0ae4678 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt @@ -1,5 +1,6 @@ package net.corda.core.transactions +import net.corda.core.CordaInternal import net.corda.core.DeleteForDJVM import net.corda.core.KeepForDJVM import net.corda.core.contracts.* @@ -10,6 +11,7 @@ import net.corda.core.identity.Party import net.corda.core.node.ServiceHub import net.corda.core.node.ServicesForResolution 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.transactions.NotaryChangeWireTransaction.Component.* @@ -75,6 +77,20 @@ data class NotaryChangeWireTransaction( @DeleteForDJVM fun resolve(services: ServiceHub, sigs: List) = resolve(services as ServicesForResolution, sigs) + /** + * This should return a serialized virtual output state, that will be used to verify spending transactions. + * The binary output should not depend on the classpath of the node that is verifying the transaction. + * + * Ideally the serialization engine would support partial deserialization so that only the Notary ( and the encumbrance can be replaced from the binary input state) + * + * + * TODO - currently this uses the main classloader. + */ + @CordaInternal + internal fun resolveOutputComponent(services: ServicesForResolution, stateRef: StateRef): SerializedBytes> { + return services.loadState(stateRef).serialize() + } + enum class Component { INPUTS, NOTARY, NEW_NOTARY } 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 9e6c0fceda..4013a6b6e8 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt @@ -270,7 +270,7 @@ open class TransactionBuilder @JvmOverloads constructor( } // The final step is to resolve AutomaticPlaceholderConstraint. - val automaticConstraintPropagation = contractClassName.contractHasAutomaticConstraintPropagation(serializationContext?.deserializationClassLoader) + val automaticConstraintPropagation = contractClassName.contractHasAutomaticConstraintPropagation(inputsAndOutputs.first().data::class.java.classLoader) // When automaticConstraintPropagation is disabled for a contract, output states must an explicit Constraint. require(automaticConstraintPropagation) { "Contract $contractClassName was marked with @NoConstraintPropagation, which means the constraint of the output states has to be set explicitly." } 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 6ff8983745..a7ecc86dce 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -7,11 +7,15 @@ import net.corda.core.contracts.* import net.corda.core.contracts.ComponentGroupEnum.* import net.corda.core.crypto.* import net.corda.core.identity.Party +import net.corda.core.internal.SerializedStateAndRef import net.corda.core.internal.Emoji import net.corda.core.node.NetworkParameters +import net.corda.core.node.ServiceHub import net.corda.core.node.ServicesForResolution import net.corda.core.node.services.AttachmentId 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.OpaqueBytes import net.corda.core.utilities.lazyMapped @@ -99,7 +103,7 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr return toLedgerTransactionInternal( resolveIdentity = { services.identityService.partyFromKey(it) }, resolveAttachment = { services.attachments.openAttachment(it) }, - resolveStateRef = { services.loadState(it) }, + resolveStateRefComponent = { resolveStateRefBinaryComponent(it, services) }, networkParameters = services.networkParameters ) } @@ -119,13 +123,14 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr resolveStateRef: (StateRef) -> TransactionState<*>?, @Suppress("UNUSED_PARAMETER") resolveContractAttachment: (TransactionState) -> AttachmentId? ): LedgerTransaction { - return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, resolveStateRef, null) + // This reverts to serializing the resolved transaction state. + return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, { stateRef -> resolveStateRef(stateRef)?.serialize() }, null) } private fun toLedgerTransactionInternal( resolveIdentity: (PublicKey) -> Party?, resolveAttachment: (SecureHash) -> Attachment?, - resolveStateRef: (StateRef) -> TransactionState<*>?, + resolveStateRefComponent: (StateRef) -> SerializedBytes>?, networkParameters: NetworkParameters? ): LedgerTransaction { // Look up public keys to authenticated identities. @@ -133,20 +138,38 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr val parties = cmd.signers.mapNotNull { pk -> resolveIdentity(pk) } CommandWithParties(cmd.signers, parties, cmd.value) } - val resolvedInputs = inputs.lazyMapped { ref, _ -> - resolveStateRef(ref)?.let { StateAndRef(it, ref) } ?: throw TransactionResolutionException(ref.txhash) + + val resolvedInputBytes = inputs.map { ref -> + SerializedStateAndRef(resolveStateRefComponent(ref) + ?: throw TransactionResolutionException(ref.txhash), ref) } - val resolvedReferences = references.lazyMapped { ref, _ -> - resolveStateRef(ref)?.let { StateAndRef(it, ref) } ?: throw TransactionResolutionException(ref.txhash) + val resolvedInputs = resolvedInputBytes.lazyMapped { (serialized, ref), _ -> + StateAndRef(serialized.deserialize(), ref) } + + val resolvedReferenceBytes = references.map { ref -> + SerializedStateAndRef(resolveStateRefComponent(ref) + ?: throw TransactionResolutionException(ref.txhash), ref) + } + val resolvedReferences = resolvedReferenceBytes.lazyMapped { (serialized, ref), _ -> + StateAndRef(serialized.deserialize(), ref) + } + val attachments = attachments.lazyMapped { att, _ -> resolveAttachment(att) ?: throw AttachmentResolutionException(att) } - val ltx = LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt, networkParameters, resolvedReferences) - checkTransactionSize(ltx, networkParameters?.maxTransactionSize ?: 10485760) + + val ltx = LedgerTransaction.makeLedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt, networkParameters, resolvedReferences, componentGroups, resolvedInputBytes, resolvedReferenceBytes) + + checkTransactionSize(ltx, networkParameters?.maxTransactionSize ?: DEFAULT_MAX_TX_SIZE) + return ltx } + /** + * Deterministic function that checks if the transaction is below the maximum allowed size. + * It uses the binary representation of transactions. + */ private fun checkTransactionSize(ltx: LedgerTransaction, maxTransactionSize: Int) { var remainingTransactionSize = maxTransactionSize @@ -164,9 +187,8 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr // it's likely that the same underlying Attachment CorDapp will occur more than once so we dedup on the attachment id. ltx.attachments.distinctBy { it.id }.forEach { minus(it.size) } - // TODO - these can be optimized by creating a LazyStateAndRef class, that just stores (a pointer) the serialized output componentGroup from the previous transaction. - minus(ltx.references.serialize().size) - minus(ltx.inputs.serialize().size) + minus(ltx.resolvedInputBytes!!.sumBy { it.serializedState.size }) + minus(ltx.resolvedReferenceBytes!!.sumBy { it.serializedState.size }) // For Commands and outputs we can use the component groups as they are already serialized. minus(componentGroupSize(COMMANDS_GROUP)) @@ -253,6 +275,8 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr } companion object { + private const val DEFAULT_MAX_TX_SIZE = 10485760 + /** * Creating list of [ComponentGroup] used in one of the constructors of [WireTransaction] required * for backwards compatibility purposes. @@ -281,6 +305,28 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr if (commands.isNotEmpty()) componentGroupMap.add(ComponentGroup(SIGNERS_GROUP.ordinal, commands.map { it.signers }.lazyMapped(serialize))) return componentGroupMap } + + /** + * This is the main logic that knows how to retrieve the binary representation of [StateRef]s. + * + * For [ContractUpgradeWireTransaction] or [NotaryChangeWireTransaction] it knows how to recreate the output state in the correct classloader independent of the node's classpath. + */ + @CordaInternal + fun resolveStateRefBinaryComponent(stateRef: StateRef, services: ServicesForResolution): SerializedBytes>? { + return if (services is ServiceHub) { + val coreTransaction = services.validatedTransactions.getTransaction(stateRef.txhash)?.coreTransaction + ?: throw TransactionResolutionException(stateRef.txhash) + when (coreTransaction) { + is WireTransaction -> coreTransaction.componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.OUTPUTS_GROUP.ordinal }?.components?.get(stateRef.index) as SerializedBytes>? + is ContractUpgradeWireTransaction -> coreTransaction.resolveOutputComponent(services, stateRef) + is NotaryChangeWireTransaction -> coreTransaction.resolveOutputComponent(services, stateRef) + else -> throw UnsupportedOperationException("Attempting to resolve input ${stateRef.index} of a ${coreTransaction.javaClass} transaction. This is not supported.") + } + } else { + // For backwards compatibility revert to using the node classloader. + services.loadState(stateRef).serialize() + } + } } @DeleteForDJVM 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 8ef7904a75..56a30be9a5 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt @@ -6,6 +6,7 @@ import net.corda.core.DeleteForDJVM import net.corda.core.KeepForDJVM import net.corda.core.internal.LazyMappedList import net.corda.core.internal.concurrent.get +import net.corda.core.internal.createSimpleCache import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.CordaSerializable import org.slf4j.Logger @@ -149,9 +150,7 @@ fun Future.getOrThrow(timeout: Duration? = null): V = try { fun List.lazyMapped(transform: (T, Int) -> U): List = LazyMappedList(this, transform) private const val MAX_SIZE = 100 -private val warnings = Collections.newSetFromMap(object : LinkedHashMap() { - override fun removeEldestEntry(eldest: MutableMap.MutableEntry?) = size > MAX_SIZE -}) +private val warnings = Collections.newSetFromMap(createSimpleCache(MAX_SIZE)) /** * Utility to help log a warning message only once. @@ -163,4 +162,4 @@ fun Logger.warnOnce(warning: String) { warnings.add(warning) this.warn(warning) } -} \ No newline at end of file +} diff --git a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt index e2a2e22b71..b7c5b8a4cd 100644 --- a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt @@ -14,18 +14,13 @@ import net.corda.core.internal.FetchAttachmentsFlow import net.corda.core.internal.FetchDataFlow import net.corda.core.internal.hash import net.corda.node.services.persistence.NodeAttachmentService -import net.corda.testing.core.ALICE_NAME -import net.corda.testing.core.BOB_NAME -import net.corda.testing.core.makeUnique -import net.corda.testing.core.singleIdentity +import net.corda.testing.core.* +import net.corda.testing.internal.fakeAttachment import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNodeParameters import net.corda.testing.node.internal.TestStartedNode import org.junit.AfterClass import org.junit.Test -import java.io.ByteArrayOutputStream -import java.util.jar.JarOutputStream -import java.util.zip.ZipEntry class AttachmentTests : WithMockNet { companion object { @@ -46,7 +41,7 @@ class AttachmentTests : WithMockNet { @Test fun `download and store`() { // Insert an attachment into node zero's store directly. - val id = aliceNode.importAttachment(fakeAttachment()) + val id = aliceNode.importAttachment(fakeAttachment("file1.txt", "Some useful content")) // Get node one to run a flow to fetch it and insert it. assert.that( @@ -87,7 +82,7 @@ class AttachmentTests : WithMockNet { val badAlice = badAliceNode.info.singleIdentity() // Insert an attachment into node zero's store directly. - val attachment = fakeAttachment() + val attachment = fakeAttachment("file1.txt", "Some useful content") val id = badAliceNode.importAttachment(attachment) // Corrupt its store. @@ -134,18 +129,6 @@ class AttachmentTests : WithMockNet { } }).apply { registerInitiatedFlow(FetchAttachmentsResponse::class.java) } - private fun fakeAttachment(): ByteArray = - ByteArrayOutputStream().use { baos -> - JarOutputStream(baos).use { jos -> - jos.putNextEntry(ZipEntry("file1.txt")) - jos.writer().apply { - append("Some useful content") - flush() - } - jos.closeEntry() - } - baos.toByteArray() - } //endregion //region Operations diff --git a/core/src/test/kotlin/net/corda/core/transactions/AttachmentsClassLoaderSerializationTests.kt b/core/src/test/kotlin/net/corda/core/transactions/AttachmentsClassLoaderSerializationTests.kt new file mode 100644 index 0000000000..c3364eeba3 --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/transactions/AttachmentsClassLoaderSerializationTests.kt @@ -0,0 +1,98 @@ +package net.corda.core.transactions + +import net.corda.core.contracts.Contract +import net.corda.core.crypto.SecureHash +import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.declaredField +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder +import net.corda.core.serialization.serialize +import net.corda.core.utilities.ByteSequence +import net.corda.core.utilities.OpaqueBytes +import net.corda.nodeapi.DummyContractBackdoor +import net.corda.testing.core.DUMMY_NOTARY_NAME +import net.corda.testing.core.SerializationEnvironmentRule +import net.corda.testing.core.TestIdentity +import net.corda.testing.internal.fakeAttachment +import net.corda.testing.services.MockAttachmentStorage +import org.apache.commons.io.IOUtils +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test +import java.io.NotSerializableException +import java.net.URL +import kotlin.test.assertFailsWith + +class AttachmentsClassLoaderSerializationTests { + + companion object { + val ISOLATED_CONTRACTS_JAR_PATH: URL = AttachmentsClassLoaderSerializationTests::class.java.getResource("isolated.jar") + private const val ISOLATED_CONTRACT_CLASS_NAME = "net.corda.finance.contracts.isolated.AnotherDummyContract" + } + + @Rule + @JvmField + val testSerialization = SerializationEnvironmentRule() + + val storage = MockAttachmentStorage() + + @Test + fun `Can serialize and deserialize with an attachment classloader`() { + + val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party + val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party + + val isolatedId = storage.importAttachment(ISOLATED_CONTRACTS_JAR_PATH.openStream(), "app", "isolated.jar") + val att1 = storage.importAttachment(fakeAttachment("file1.txt", "some data").inputStream(), "app", "file1.jar") + val att2 = storage.importAttachment(fakeAttachment("file2.txt", "some other data").inputStream(), "app", "file2.jar") + + val serialisedState = AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(arrayOf(isolatedId, att1, att2).map { storage.openAttachment(it)!! }) { classLoader -> + val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, classLoader) + val contract = contractClass.newInstance() as Contract + assertEquals("helloworld", contract.declaredField("magicString").value) + + val txt = IOUtils.toString(classLoader.getResourceAsStream("file1.txt"), Charsets.UTF_8.name()) + assertEquals("some data", txt) + + val state = (contract as DummyContractBackdoor).generateInitial(MEGA_CORP.ref(1), 1, DUMMY_NOTARY).outputStates().first() + val serialisedState = state.serialize() + + val state1 = serialisedState.deserialize() + assertEquals(state, state1) + serialisedState + } + + assertFailsWith { + serialisedState.deserialize() + } + } + + // These tests are not Attachment specific. Should they be removed? + @Test + fun `test serialization of SecureHash`() { + val secureHash = SecureHash.randomSHA256() + val bytes = secureHash.serialize() + val copiedSecuredHash = bytes.deserialize() + + assertEquals(secureHash, copiedSecuredHash) + } + + @Test + fun `test serialization of OpaqueBytes`() { + val opaqueBytes = OpaqueBytes("0123456789".toByteArray()) + val bytes = opaqueBytes.serialize() + val copiedOpaqueBytes = bytes.deserialize() + + assertEquals(opaqueBytes, copiedOpaqueBytes) + } + + @Test + fun `test serialization of sub-sequence OpaqueBytes`() { + val bytesSequence = ByteSequence.of("0123456789".toByteArray(), 3, 2) + val bytes = bytesSequence.serialize() + val copiedBytesSequence = bytes.deserialize() + + assertEquals(bytesSequence, copiedBytesSequence) + } +} + diff --git a/core/src/test/kotlin/net/corda/core/transactions/AttachmentsClassLoaderTests.kt b/core/src/test/kotlin/net/corda/core/transactions/AttachmentsClassLoaderTests.kt new file mode 100644 index 0000000000..0fe3727df8 --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/transactions/AttachmentsClassLoaderTests.kt @@ -0,0 +1,102 @@ +package net.corda.core.transactions + +import net.corda.core.contracts.Attachment +import net.corda.core.contracts.Contract +import net.corda.core.internal.declaredField +import net.corda.core.serialization.internal.AttachmentsClassLoader +import net.corda.testing.internal.fakeAttachment +import net.corda.testing.services.MockAttachmentStorage +import org.apache.commons.io.IOUtils +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.Test +import java.io.ByteArrayOutputStream +import java.net.URL +import kotlin.test.assertFailsWith + +class AttachmentsClassLoaderTests { + + companion object { + val ISOLATED_CONTRACTS_JAR_PATH: URL = AttachmentsClassLoaderTests::class.java.getResource("isolated.jar") + private const val ISOLATED_CONTRACT_CLASS_NAME = "net.corda.finance.contracts.isolated.AnotherDummyContract" + + private fun readAttachment(attachment: Attachment, filepath: String): ByteArray { + ByteArrayOutputStream().use { + attachment.extractFile(filepath, it) + return it.toByteArray() + } + } + } + + val storage = MockAttachmentStorage() + + @Test + fun `Loading AnotherDummyContract without using the AttachmentsClassLoader fails`() { + assertFailsWith { + Class.forName(ISOLATED_CONTRACT_CLASS_NAME) + } + } + + @Test + fun `Dynamically load AnotherDummyContract from isolated contracts jar using the AttachmentsClassLoader`() { + val isolatedId = storage.importAttachment(ISOLATED_CONTRACTS_JAR_PATH.openStream(), "app", "isolated.jar") + + val classloader = AttachmentsClassLoader(listOf(storage.openAttachment(isolatedId)!!)) + val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, classloader) + val contract = contractClass.newInstance() as Contract + assertEquals("helloworld", contract.declaredField("magicString").value) + } + + @Test + fun `Load text resources from AttachmentsClassLoader`() { + val att1 = storage.importAttachment(fakeAttachment("file1.txt", "some data").inputStream(), "app", "file1.jar") + val att2 = storage.importAttachment(fakeAttachment("file2.txt", "some other data").inputStream(), "app", "file2.jar") + + val cl = AttachmentsClassLoader(arrayOf(att1, att2).map { storage.openAttachment(it)!! }) + val txt = IOUtils.toString(cl.getResourceAsStream("file1.txt"), Charsets.UTF_8.name()) + assertEquals("some data", txt) + + val txt1 = IOUtils.toString(cl.getResourceAsStream("file2.txt"), Charsets.UTF_8.name()) + assertEquals("some other data", txt1) + } + + @Test + fun `Test overlapping file exception`() { + val att1 = storage.importAttachment(fakeAttachment("file1.txt", "some data").inputStream(), "app", "file1.jar") + val att2 = storage.importAttachment(fakeAttachment("file1.txt", "some other data").inputStream(), "app", "file2.jar") + + assertFailsWith(AttachmentsClassLoader.Companion.OverlappingAttachments::class) { + AttachmentsClassLoader(arrayOf(att1, att2).map { storage.openAttachment(it)!! }) + } + } + + @Test + fun `No overlapping exception thrown on certain META-INF files`() { + listOf("meta-inf/manifest.mf", "meta-inf/license", "meta-inf/test.dsa", "meta-inf/test.sf").forEach { path -> + val att1 = storage.importAttachment(fakeAttachment(path, "some data").inputStream(), "app", "file1.jar") + val att2 = storage.importAttachment(fakeAttachment(path, "some other data").inputStream(), "app", "file2.jar") + + AttachmentsClassLoader(arrayOf(att1, att2).map { storage.openAttachment(it)!! }) + } + } + + @Test + fun `Check platform independent path handling in attachment jars`() { + val storage = MockAttachmentStorage() + + val att1 = storage.importAttachment(fakeAttachment("/folder1/foldera/file1.txt", "some data").inputStream(), "app", "file1.jar") + val att2 = storage.importAttachment(fakeAttachment("\\folder1\\folderb\\file2.txt", "some other data").inputStream(), "app", "file2.jar") + + val data1a = readAttachment(storage.openAttachment(att1)!!, "/folder1/foldera/file1.txt") + assertArrayEquals("some data".toByteArray(), data1a) + + val data1b = readAttachment(storage.openAttachment(att1)!!, "\\folder1\\foldera\\file1.txt") + assertArrayEquals("some data".toByteArray(), data1b) + + val data2a = readAttachment(storage.openAttachment(att2)!!, "\\folder1\\folderb\\file2.txt") + assertArrayEquals("some other data".toByteArray(), data2a) + + val data2b = readAttachment(storage.openAttachment(att2)!!, "/folder1/folderb/file2.txt") + assertArrayEquals("some other data".toByteArray(), data2b) + } +} diff --git a/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt b/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt index 32f9105a1c..b37a34fc97 100644 --- a/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt @@ -10,6 +10,7 @@ import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.contracts.DummyContract import net.corda.testing.core.* import net.corda.testing.internal.createWireTransaction +import net.corda.testing.internal.fakeAttachment import net.corda.testing.internal.rigorousMock import org.junit.Rule import org.junit.Test @@ -118,7 +119,8 @@ class TransactionTests { val commands = emptyList>() val attachments = listOf(ContractAttachment(rigorousMock().also { doReturn(SecureHash.zeroHash).whenever(it).id - }, DummyContract.PROGRAM_ID)) + doReturn(fakeAttachment("nothing", "nada").inputStream()).whenever(it).open() + }, DummyContract.PROGRAM_ID, uploader = "app")) val id = SecureHash.randomSHA256() val timeWindow: TimeWindow? = null val privacySalt = PrivacySalt() diff --git a/serialization/src/test/kotlin/net/corda/nodeapi/DummyContractBackdoor.kt b/core/src/test/kotlin/net/corda/nodeapi/DummyContractBackdoor.kt similarity index 100% rename from serialization/src/test/kotlin/net/corda/nodeapi/DummyContractBackdoor.kt rename to core/src/test/kotlin/net/corda/nodeapi/DummyContractBackdoor.kt diff --git a/core/src/test/resources/net/corda/core/transactions/isolated.jar b/core/src/test/resources/net/corda/core/transactions/isolated.jar new file mode 100644 index 0000000000000000000000000000000000000000..17bf0c2436c9aee598228a41c675b4421a7cfa37 GIT binary patch literal 12262 zcmb_i1z45cvIYrB>6Y&9?(S}BHeH)WQmHK^9nxJQNOyO4NT&$WAt2?2zwr0?pX0sf z4$lXBKYVLu)>^Y>X3e}xvJjByU@$N+V5QGFWx>89STIO1d2uyidT9j-#@l`{FbFUu z*~c*VZ#??rHKl(XjDGjVUxVd^6{ID^Rn-{eB~IiA2jpbw8O9K0>1l=sM=Dg9CRtZL zIx@(}(@D#Zxx9sh)!M@8#KXm-vADn@r}&gZ!}SRZV*`sk9&F0`*zw2_{5LI>;b`Q< z?qrDHJr7!dff4_q+3$bIz+fM=uyg|27&)7o7}y$F+A&xIooy`b3~YfWE;gp>YEWR{ z_d@`NhK6Q$Pk^y21202d<0NX+#&uS#SX1R-V`t-r8WH3B94h9z94*EQE{aU7Zhy+^+($c?GRDRIY9>HfJ}CO9y#-^Ee--Jqx{oe> zmZMG~Sj4u4gz`(#4CdrD`u6TIqeZu8!-Z<8(_^dECy`nyi^oU#A*yZ>f|@d2!M5#Y z7o2(@lTU{;G@ev69xf;bjxVgm9K$N!ayPyz3iwR&*l15tAsH|LfG>Ze%HB?g+9$Lj z6NXx+31&pD_N+NAhct17{g-oD^X5T-!7@QQ*lxBM z*VMR&PYPtaH7$K5BaUS<%rdTGA{V*_^ipv6UaFi`6IK0tAAy7-2Xt6`Y%H-i2SLq; zBALLZPz}k;Y4H%HTVwN^)JxVnig)(R(pnbI06dz#R9V_bX9OrI1?g)%mIb=d%~DA!ek4cj{FN}Wb(6WkPr_ZZxuTBx1mlH9T7gQ|*DdM{Rt-nVXVYO@D2G6~j@mu6~-O5Y&#{ zr`R!tK<(_f%LrN|ZoQ)FyA&gU-oQqp&9i360}!Nc)&xT31RJb+|}WO`ingYh@! zo|6cnykIqaRYJcZnah`g4Qf2T zWUr%c!|T&hG3m$LwO~Q;DxiAqTkl>7DOguUY4P3;+1vGJ94XhQc z%fmIiytg+820l>sxa6SFaO$w~9rap=Wye}o4r!-=o`H5wy5*`mCfe@W^$@6uWSdDV zTC}m_Rc&EwSSD!iC`PA_O@*miEe8mYYP z=DnNHq2M;6TGh&xUd;XW1zR5KfH8FP2%T`C{VUYOO13K8)&*qo8W} zKFA_8onsdn#;!;wo~%Xkg_9yA*_#KNbE|_&hT7p>;fp)14ix4; zf=o5#7=69qM2{WI!ENm3i_>T9QyAiAcJfq>xYFhk8$I8{B#g5%(PeJ?U49&D96pM5 z=95?FC|GS`bHt7Xv5MLFUcsZ;b(!?qfTI`Ky=6HcE1fKW8Cms$17ysFCL3_n%10O~ z?ql@$B)sI1weLr7Y=@#m&#iu%oz}h)NmPYi5a};sNgq`xB6j=y){rdsl&bCgT3BYR zDmJT-4Jp8^IB;XyUit~{?5XQ`)$3Q!ki=;_b{!=(CeZb^)j zj?z|jhA`_N`)kcoX;M{_Q`v&5hpbvOvrBsI&jee-AkAPodSBDrsuNJgZ%G#!37dx6 zCC)o7R6OtF0QULvxQ{swmR^&}I!?dk(PiU{mUMIZQuadn{1M82vgN4C5FOV*JP_sL zLgQQh3u2lcs2Y*|Ml%%yGF3cfYDW1Zk60P2Y1`+fCn4l<&TS5I!1m2%VDv#VjF%l2 zn}SM=(sth3si|p0-dj6zZt&MM*J$GkkrD~8U#RB@2a2HxlYc|2*K`A`-hE_~A63ui-nRnJM$49eyt6fMeiOD&DIc(J zPc9M}oztP;ekr#vJd%PMS9&v)vOygw+HyI?X%YPTzF)K02^cnIzhj%Sq7H zys;H@Z+hABoRX)J6lFlJRVAhmd5rxj;uxu8G2`OX7(~vjsO;y74&PW27PWiIc7xf& zq+;0{lfBb7*i*cpl64h)a%GQrCEg>Nrs&|neq0-H)Vofz+HaRMfaD$FFWMsf75eH` znrLD)anIHjChV8T1N;;@hqyqVZK*@6p&4KWmaCWoc|~hE zj4?im!=A`~Uy0q1MG2zcLaJm&XI*a*#>pI5k%O1lsgTPboSvQ@$tx2ep|BoRGHLn`v{Zi-$56$F7B#If}+iJy)sl!j#d!811vCY3V%~Kr#_! zaEO+IqjcqYX<>Enl^j=;a2Rs*H36(1*ATK`RtM>T;y8YU$@8bx% zlL)+gYDbp9HkLe@dj*ehgV?&))y{zu z!vx~$IK)$~#wUy2IX@!l_zLm97;R=gt{$br6!3(rEa~|>IE7u8m5ZY%9aY{eh6!by z8vyhJ={K!c2IcS`BhuA$oJ2WGbxAAsZ4Yrn6g<0Zuc**krs{+g+I zpif`1>IqyLBc5w`oYULreDQL7ENWOc`v@be<%)NK>Cdyv;o{H9$qrv;x~nr|#ZWLR z2RGrQ0^cb~HXnfJFq`WIzj8haj8jL5>0w-oo0nJLXjguV4M7^@iB#L-45L<;?nrrs zBZ1_t42tnFapg0gdMiiAtCykP@dsUNEG_U1TMG@gRU1#jy7R|QY3RCY(kZB7LgY7d%dYjR2%kl!64gV z*vrpE!_rw%^HI){y`l#45&E}mq8{`w>iKRY{eKGLw6wRoCChM_yrA5$aQ->TvXt3~ljPnn`F3YzQmX%hl;- zx%r5r$SCAK&YQL$gW!^v(n?KEOPC6?p<g$h260iiM0*&$egZ<^lNMS zY#rLLE9K-d+k2uDl1mXexs{o&VM@$Z?7~gMf_S@1^2L?l(sMPR6jo=CU}*YXm;{&s z$`{m@I198n6fwM`mOSm0+QGH(>+mxJ+asU_g4&y$Eyyuo>R!fcUUlV);239HI&s*! z?ehHzi;Kzh=Q}k?Z>0kF3d);FxI}e|WSX|x+~S!f;9Jk*c(vI_Lo3h|Ojn@9r^VR# zaS6yE0IN=Hc1Bd59%$K`d^*?z;kL$&+ra1L`}A@q4Se&p=ss>}37^UISw88cV$h8{fc^5(8^`1t@QK9UP*F5TAq?x_r!LC5vX2hkVU^f z!I@jMmYn#Uhe)IxiR_wbCjMd5m7+kLCqfaQD*q{!fA;DKRri=Jg00Z4DNWV< zvj%?bmL@AvLTr0Bf2`ao@l*??D$IsN5cSEm@7ZOUI!lJBB(zlZti~e>sqm8d=#fK+ zF(z-svE>{B&$xm^j&<&%^qBBR!u_9C6^z?%G0L=3siohO%{50$ws4 zs;$%uRkS8N687cr)dv^NP+W-;Y8fI#eon{47>Uvg>-=Fbs1*mC*bpxE8^nFAQA79~ z|Lrc;&_DS;)@b|?YlQ!sSR;E^GrF%m*hb0%(U{Q#M^Z=G5N)MoGe4q8&sR@*qU7#=87ZsWU^I@P&qzFP%`Lc$$yKrZ^ynx=tu`# zd}F$X0)0c503Qgb`NFjw>g%{DW(5djONmwJX|(GuNdyrKmg^ICGfYab#m4Y&lX>d1 z^&c51XE3B8=@0b^A z>hf{IbtbA1C#G%vCx*{-7Y_r>;CjaS^(WwN*Muv^KIoOF5Kg#vtnj9-zcoa7c7z<* ztUFJHktyAkrONrbZmoJ69?HfYW@nO3Fs+}p96}|xy-D+DShLsMT z5jzO!Q!OMWoAZD%j0pt|f+h=E7kyK%b-dk773CDY-SaxGte~z8qG9`ODJ7aq9Fjm4 zcXoqkUcIAkbZcCw*gxvn)C|$a%s5V z;5fOoi|gy@skkP}OrO#$nJYnI`8~O1byKNW7$eSC!46TO>OffKj=4@TuTA7$wzthf z?NRqa?jRC~%^7;zX!eG`qxR-qohqvbel|-sA@hQO(LR2+R2}mU$9sHOoh~l&`r#%P zH=Vq;gk*KN6#YHOkiE%T*8|Zg^gi=4((|- z9U7Of4T7bdN*(M$zN*L8Q~kFZzzKAOub~<~HG!~Jja#)YwxOQtnw-H3HMTtyyp!ZT zwCwbumBxFzR~Yg8^3K3QriBYaD0bywB5~F>U}CX`D64HmL$#f9jVFV<39d*NKPa}W z@AY2(qyVA*d?a~ktHy%FuL)!PLeLx&x=ONMedDm*O#sPoG^bflR`MAY*^79e%W~0hn_)|gSkp=PWWB%o zz=(Sqf^g2DflU*_Hc&q$>|ZLmKFs0LKNXj^mx!oes*}fLY5Wl>6cpkVb@`H5+u0&d z!+9o_)mGgHn*mEcIUxxV*ZsDyG(w#lidtB)e0%cAQcCzZWg!wngtmm~%$EKHIY?{P zvL4!igIi2CmMgeKp`fO=o|b-|HaPFc(s{@|whP2eEFchdwCUxSnw@Rj zMS~t}iL%eC2H>dfB+X*h(tI*1a}q%r2b_)P<$LzCGthCzU}p9+ZmK6$qT?M2kHb58 zUrBB?bW4)v<>!7wd}N#P*o1mrUXDW0-Cy^Ny?IdK);DUsZLBGRS~K;j>sUv?nOwQb zNpyj8QepD^!5kO3*K)k#VTwtZ9R{BSY{#DSy9ou7LTHjVl;;QUNtIeP_geTc>l?Zr z)m=r#Ydw36c7Z#v69g*(18R4gNOY-!2MWi zm-yAP*uT0pX*)}2OQV1FYq=V>nrM>fJ`@2qFqxo&m4<=>@OPopbI<57M4~K&WRYc$ za$|;&F*zEX&HYu1av584(l5u(DhsA2I*tH>XOF*}D!3BAQKH)8?i-$O8u8v-;&t1M zXz{tiZ-w$2vG2l6v*WV!-C@levK1ZeJ=3*&uELoMa8N`)l^eyONsy84h(F0-1th2S z@%K!%VxQVzfSN_piE#>P``)Rty&jl#@2QZ}X%V;bzVSwotkxJsHsh+}wlbWn43$YK zZHKO*1A)|L$U4&0G+o0P9T(SJm0P0H(Lgg;uRsjHS8GBr(ut^{^9c?FaX zc5t;3UtuTpc5bP3r#WEr+=Bar3CQ@hMyyz{KFRi99o2}&4QC~T<)IB(=tmFOQ{D~8 z>8oOj$WzwMm}`o>d7(>2Nks2>Mx$xRvCCP6H2 z1!uyWV?UXR62j`?#fti zY&l|H<+oq>s@xp8+5Ju_D4AFD@+i{dH8in)YeWAm7S8alm}hw#Hp! zo{HKrW~+GQaFCw#pra^ZIn%$M zLOrk^rGv?i#iBgrywKo7yIO7~(1)WZN>!I~Yeo8Cm4dUM!x>vdrr0gtUAX2Ir?rYO z?%?fma%8E334Tq%2rT1)V+?|0@>{<1*AvuFX>~Nx4Xi2s$hb8Oo>++#_ushAGU0To zzdaEiOVOd*&f9~X*~DCoPm@)X6SZ1F*i*v}b5~g>7e>Xw88zlgeH$?VN=)&i)@ZlO zKo%t+0ZF*no~DWxMrBs1ycGTEb2SI>7Yy<$(1 z0jCY@;X)+o&eS-s4<*TC5fqo}opG}P0lg=pNoik#DJM=VgK zV(M;Vu@p^s&~Fn(s1x(*70xB6GZTi$+~mKMzfmHj`Jg?43HXT0&*ztT13tY$Aq5+9 zcPO`SO=plh@>sA&J~#t%=oxY&xg=jzD~sbws}C34 zL`$Gp9Vp@D#p~;nodij|x6n5gx+Oo8;}`WjhkMf8$_&Ct@d>Zd%zR}ao{x)5p>e2S zeWCc`D?bRUc5ns$-HMFH(0OAkvU>G?0(RQx{adIR4JOx3906_dbvV1lTj`HD%~`<5 z+JlCI6b{K|m)pTd)OkS|Wv%9K^3IrVBG4p>gmrbcZ=yP>ck_5zgpWO?8xWuFJxAT8 zmG*-mn-#l}#DIV2hefVGB$)lhvI{Q)!gBPa48zTV@z9q*ij!tsamKRyrVlZ8I+n9O z1|{{Xk{F6jEQwgWp7QHgr$J{y(Xxh5nz}`HO+yAqOzI;@qqrUcqb(X5IP_oxx5|3( z3!E4!&|W&035WPq%+C;4!3m;8<9I17(KB7+-y~01^ct2NC%>xVpfNS<$Yj9GYa?rZ z$@}_EXJtU_Az6t`nAAc-Ev(}E$zL#_A33yEiok$Bl2Oi)qLxzn7!5JU1#bg*ALXXnN&jabcJjUt%Ylex zSJntz@+279-@U%PS*LvIS*uVBj166*BkbEsO{bUolmW-sEza@qH1g(wynXvh%gIIPzZ`$q|19W{xGBFLwp2` zb(6y;s%@6{#|vORzU5n>G0*J3cww?Ok*#ddhD zEK;DRyFUNzS}2I?_OoW_DgK&A!9jP~Ey@oO1iK8*8xD)Ld4Zs<3*brTI~+* z_6ew>!4a(vsP@Yj%Lifj%UAjwDgkDUp{C;rb@3fy_~=4fdzRVy3gemmiYY7d9DRVP zu|SxH?gB5ysA(Dky{j*scYdO|Iw{8Obzh1z4=d(8GT+CwRar*R@bg4Z@aGd(28Fw-2Rzs z{0^@8(QuS_74h!ctY#wu9Y*cRZyMon381hD3WZ?&(EG;8L!>sk#Veb23_qTvIX8rh z^#C)oaz(O&kjd}N#@ZiQuhZ(&{MY8hvEjkRn=gW3Iu~Le*!sIHmCaZL#wC)}m|06= z3oFX1#Tioj*EX|HSPuxTRUuH~)8KK`Z9?R_9;YG2Jeex1F5xRCCJNx4v#61UiPwDQ zqJ|<-kRee}i)IUJ5Up8RCjUmJK5(EaxP_LIb(_pO>zs3CyBFdc5f20$lAn-Xe7Q^e z+K@IsDk5_w%2+M;JaYQB^_Md~^q93X5(KYJXe08kBg$V-j`H=to#t>XtPI4@)Q6i3 zta!9LtK_f%`&0%LN_;$tTXQZ*Da+2gWF1 zbsOXRX?{jR+U6XrZMAV~_5J)ALxSN3^)S3l0G&A`X!+a*MY3@%6iNVr1@EHwfWR8? zZh9Uj37c?%^UZVP7stI=FfqDgL{Y=0jy1c|8lvGQ_)dG6Gp!^o41p--1Dn>L_?(| z^g6V&M!>gQ$$L08=^9Z0do5eA|9Lf*FdJ3h@Yk~x?oY{1 zSGVf}nqVVkouvfKjpP>ua+#x`q$WvfI8aXl zn_H~Pa#ge3DK~4-g{%|j)J@ka2mzEKW0l(_}{uBM`*Mk+=%_b2X&VHvZjFXaK;A_+{2T0~Fh z3v4~~9V~Dt>1-#Pyk=ZnIkJNl)_#*IjQ)6Lbkqr60iI`IoZg=CA|41%RlzhVz zN?HFv*}Zhi;uPVu3?cKd;YauhNy=niQu)z1G}uI49&d^178|;0cYzBpy-`9%5?k-nKvfz)*y9fRKIP`nq z-95O0WB(4Wch4FQ?4M8b18bAJ)Bl%p zKLgtRGo$->@Ia|QWpw8@{+GbJf0lO-y?daWJ9+m`=`Up1znk+t6z_qk?zZ^BpZyYp zduZMRY5$GEPk?&=Jc)a#-UCJdl+i;k^p|wIhweSl)PG?4r}_MS;r=`X_jdR_2J0Tm z_dvvV`N6}&{WjNQqPRcP%l}xR|55h$SET#G*#m{){-0(4QyJ|?;os-j_qVVIY9#u< z3jYr_{C#%!cV~S;^1l@M(^UR`ioX_Rj>x{3_h%ybH}Cen2l_rl-{+7IL__{FM!%9# WD#=1Y-}Aw^``NiW!XGK`zx@xEsB`}S literal 0 HcmV?d00001 diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 534607d95a..602d57ab8a 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -7,6 +7,11 @@ release, see :doc:`upgrade-notes`. Unreleased ---------- +* Deprecated `SerializationContext.withAttachmentsClassLoader`. This functionality has always been disabled by flags +and there is no reason for a CorDapp developer to use it. It is just an internal implementation detail of Corda. + +* Deprecated the `LedgerTransaction` constructor. No client code should call it directly. LedgerTransactions can be created from WireTransactions if required. + * Introduced new optional network bootstrapper command line options (--register-package-owner, --unregister-package-owner) to register/unregister a java package namespace with an associated owner in the network parameter packageOwnership whitelist. diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt index e9e1ebf0d4..4b1bd358ef 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt @@ -25,6 +25,7 @@ import net.corda.testing.contracts.DummyState import net.corda.testing.core.* import net.corda.testing.dsl.* import net.corda.testing.internal.TEST_TX_TIME +import net.corda.testing.internal.fakeAttachment import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.vault.CommodityState import net.corda.testing.node.MockServices @@ -565,7 +566,7 @@ class ObligationTests { @Test fun `commodity settlement`() { - val commodityContractBytes = "https://www.big-book-of-banking-law.gov/commodity-claims.html".toByteArray() + val commodityContractBytes = fakeAttachment("file1.txt", "https://www.big-book-of-banking-law.gov/commodity-claims.html") val defaultFcoj = Issued(defaultIssuer, Commodity.getInstance("FCOJ")!!) val oneUnitFcoj = Amount(1, defaultFcoj) val obligationDef = Obligation.Terms(NonEmptySet.of(commodityContractBytes.sha256() as SecureHash), NonEmptySet.of(defaultFcoj), TEST_TX_TIME) @@ -957,7 +958,7 @@ class ObligationTests { assertEquals(expected, actual) } - private val cashContractBytes = "https://www.big-book-of-banking-law.gov/cash-claims.html".toByteArray() + private val cashContractBytes = fakeAttachment("file1.txt", "https://www.big-book-of-banking-law.gov/cash-claims.html") private val Issued.OBLIGATION_DEF: Obligation.Terms get() = Obligation.Terms(NonEmptySet.of(cashContractBytes.sha256() as SecureHash), NonEmptySet.of(this), TEST_TX_TIME) private val Amount>.OBLIGATION: Obligation.State diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt index ad86bee392..0ca39f664c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt @@ -61,7 +61,8 @@ class CordaPersistence( schemas: Set, val jdbcUrl: String, cacheFactory: NamedCacheFactory, - attributeConverters: Collection> = emptySet() + attributeConverters: Collection> = emptySet(), + customClassLoader: ClassLoader? = null ) : Closeable { companion object { private val log = contextLogger() @@ -70,7 +71,7 @@ class CordaPersistence( private val defaultIsolationLevel = databaseConfig.transactionIsolationLevel val hibernateConfig: HibernateConfiguration by lazy { transaction { - HibernateConfiguration(schemas, databaseConfig, attributeConverters, jdbcUrl, cacheFactory) + HibernateConfiguration(schemas, databaseConfig, attributeConverters, jdbcUrl, cacheFactory, customClassLoader) } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt index ae371ca88e..fea52e35f5 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt @@ -32,7 +32,7 @@ class HibernateConfiguration( private val attributeConverters: Collection>, private val jdbcUrl: String, cacheFactory: NamedCacheFactory, - val cordappClassLoader: ClassLoader? = null + val customClassLoader: ClassLoader? = null ) { companion object { private val logger = contextLogger() @@ -86,7 +86,7 @@ class HibernateConfiguration( schema.mappedTypes.forEach { config.addAnnotatedClass(it) } } - val sessionFactory = buildSessionFactory(config, metadataSources, cordappClassLoader) + val sessionFactory = buildSessionFactory(config, metadataSources, customClassLoader) logger.info("Created session factory for schemas: $schemas") // export Hibernate JMX statistics @@ -112,13 +112,13 @@ class HibernateConfiguration( } } - private fun buildSessionFactory(config: Configuration, metadataSources: MetadataSources, cordappClassLoader: ClassLoader?): SessionFactory { + private fun buildSessionFactory(config: Configuration, metadataSources: MetadataSources, customClassLoader: ClassLoader?): SessionFactory { config.standardServiceRegistryBuilder.applySettings(config.properties) - if (cordappClassLoader != null) { + if (customClassLoader != null) { config.standardServiceRegistryBuilder.addService( ClassLoaderService::class.java, - ClassLoaderServiceImpl(cordappClassLoader)) + ClassLoaderServiceImpl(customClassLoader)) } val metadataBuilder = metadataSources.getMetadataBuilder(config.standardServiceRegistryBuilder.build()) 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 index 1116ed8c29..59d1d457ce 100644 --- 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 @@ -69,10 +69,10 @@ class LargeTransactionsTest { 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(3.MB.toInt(), 0) - val bigFile2 = InputStreamAndHash.createInMemoryTestZip(3.MB.toInt(), 1) - val bigFile3 = InputStreamAndHash.createInMemoryTestZip(3.MB.toInt(), 2) - val bigFile4 = InputStreamAndHash.createInMemoryTestZip(3.MB.toInt(), 3) + val bigFile1 = InputStreamAndHash.createInMemoryTestZip(3.MB.toInt(), 0, "a") + val bigFile2 = InputStreamAndHash.createInMemoryTestZip(3.MB.toInt(), 1, "b") + val bigFile3 = InputStreamAndHash.createInMemoryTestZip(3.MB.toInt(), 2, "c") + val bigFile4 = InputStreamAndHash.createInMemoryTestZip(3.MB.toInt(), 3, "d") driver(DriverParameters( startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.testing.contracts"), diff --git a/node/src/main/java/CordaCaplet.java b/node/src/main/java/CordaCaplet.java index 78ccce99af..129b1e7dee 100644 --- a/node/src/main/java/CordaCaplet.java +++ b/node/src/main/java/CordaCaplet.java @@ -112,8 +112,6 @@ public class CordaCaplet extends Capsule { // If it fails, just return the existing class path. The main Corda jar will detect the error and fail gracefully. return cp; } - // Add additional directories of JARs to the classpath (at the end), e.g., for JDBC drivers. - augmentClasspath((List) cp, cordappsDir); try { List jarDirs = nodeConfig.getStringList("jarDirs"); log(LOG_VERBOSE, "Configured JAR directories = " + jarDirs); 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 e6669e14ec..bb788f2a72 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -155,7 +155,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration, identityService::wellKnownPartyFromAnonymous, schemaService, configuration.dataSourceProperties, - cacheFactory) + cacheFactory, + this.cordappLoader.appClassLoader) init { // TODO Break cyclic dependency @@ -748,7 +749,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, protected open fun startDatabase() { val props = configuration.dataSourceProperties if (props.isEmpty) throw DatabaseConfigurationException("There must be a database configured.") - database.startHikariPool(props, configuration.database, schemaService.internalSchemas(), metricRegistry) + database.startHikariPool(props, configuration.database, schemaService.internalSchemas(), metricRegistry, this.cordappLoader.appClassLoader) // Now log the vendor string as this will also cause a connection to be tested eagerly. logVendorString(database, log) } @@ -1061,7 +1062,8 @@ fun createCordaPersistence(databaseConfig: DatabaseConfig, wellKnownPartyFromAnonymous: (AbstractParty) -> Party?, schemaService: SchemaService, hikariProperties: Properties, - cacheFactory: NamedCacheFactory): CordaPersistence { + cacheFactory: NamedCacheFactory, + customClassLoader: ClassLoader?): CordaPersistence { // Register the AbstractPartyDescriptor so Hibernate doesn't warn when encountering AbstractParty. Unfortunately // Hibernate warns about not being able to find a descriptor if we don't provide one, but won't use it by default // so we end up providing both descriptor and converter. We should re-examine this in later versions to see if @@ -1069,13 +1071,13 @@ fun createCordaPersistence(databaseConfig: DatabaseConfig, JavaTypeDescriptorRegistry.INSTANCE.addDescriptor(AbstractPartyDescriptor(wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous)) val attributeConverters = listOf(PublicKeyToTextConverter(), AbstractPartyToX500NameAsStringConverter(wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous)) val jdbcUrl = hikariProperties.getProperty("dataSource.url", "") - return CordaPersistence(databaseConfig, schemaService.schemaOptions.keys, jdbcUrl, cacheFactory, attributeConverters) + return CordaPersistence(databaseConfig, schemaService.schemaOptions.keys, jdbcUrl, cacheFactory, attributeConverters, customClassLoader) } -fun CordaPersistence.startHikariPool(hikariProperties: Properties, databaseConfig: DatabaseConfig, schemas: Set, metricRegistry: MetricRegistry? = null) { +fun CordaPersistence.startHikariPool(hikariProperties: Properties, databaseConfig: DatabaseConfig, schemas: Set, metricRegistry: MetricRegistry? = null, classloader: ClassLoader = Thread.currentThread().contextClassLoader) { try { val dataSource = DataSourceFactory.createDataSource(hikariProperties, metricRegistry = metricRegistry) - val schemaMigration = SchemaMigration(schemas, dataSource, databaseConfig) + val schemaMigration = SchemaMigration(schemas, dataSource, databaseConfig, classloader) schemaMigration.nodeStartup(dataSource.connection.use { DBCheckpointStorage().getCheckpointCount(it) != 0L }) start(dataSource) } catch (ex: Exception) { diff --git a/node/src/main/kotlin/net/corda/node/serialization/kryo/CordaClassResolver.kt b/node/src/main/kotlin/net/corda/node/serialization/kryo/CordaClassResolver.kt index e3ff2584f7..87183504e8 100644 --- a/node/src/main/kotlin/net/corda/node/serialization/kryo/CordaClassResolver.kt +++ b/node/src/main/kotlin/net/corda/node/serialization/kryo/CordaClassResolver.kt @@ -11,7 +11,7 @@ import net.corda.core.internal.writer import net.corda.core.serialization.internal.CheckpointSerializationContext import net.corda.core.serialization.ClassWhitelist import net.corda.core.utilities.contextLogger -import net.corda.serialization.internal.AttachmentsClassLoader +import net.corda.core.serialization.internal.AttachmentsClassLoader import net.corda.serialization.internal.MutableClassWhitelist import net.corda.serialization.internal.TransientClassWhiteList import net.corda.serialization.internal.amqp.hasCordaSerializable 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 5f2b1a71be..88be7c6e8b 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 @@ -229,7 +229,7 @@ class NodeAttachmentService( val attachmentImpl = AttachmentImpl(id, { attachment.content }, checkAttachmentsOnLoad).let { val contracts = attachment.contractClassNames if (contracts != null && contracts.isNotEmpty()) { - ContractAttachment(it, contracts.first(), contracts.drop(1).toSet(), attachment.uploader, attachment.signers + ContractAttachment(it, contracts.first(), contracts.drop(1).toSet(), attachment.uploader, attachment.signers?.toList() ?: emptyList()) } else { it diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/MaxTransactionSizeTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/MaxTransactionSizeTests.kt index 28db2d7d13..19d8693539 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/MaxTransactionSizeTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/MaxTransactionSizeTests.kt @@ -54,10 +54,10 @@ class MaxTransactionSizeTests { @Test fun `check transaction will fail when exceed max transaction size limit`() { // These 4 attachments yield a transaction that's got ~ 4mb, which will exceed the 3mb max transaction size limit - val bigFile1 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 0) - val bigFile2 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 1) - val bigFile3 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 2) - val bigFile4 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 3) + val bigFile1 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 0, "a") + val bigFile2 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 1, "b") + val bigFile3 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 2, "c") + val bigFile4 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 3, "d") val flow = aliceNode.transaction { val hash1 = aliceNode.importAttachment(bigFile1.inputStream) val hash2 = aliceNode.importAttachment(bigFile2.inputStream) @@ -77,10 +77,10 @@ class MaxTransactionSizeTests { @Test fun `check transaction will be rejected by counterparty when exceed max transaction size limit`() { // These 4 attachments yield a transaction that's got ~ 4mb, which will exceed the 3mb max transaction size limit - val bigFile1 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 0) - val bigFile2 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 1) - val bigFile3 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 2) - val bigFile4 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 3) + val bigFile1 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 0, "a") + val bigFile2 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 1, "b") + val bigFile3 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 2, "c") + val bigFile4 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 3, "c") val flow = aliceNode.transaction { val hash1 = aliceNode.importAttachment(bigFile1.inputStream) val hash2 = aliceNode.importAttachment(bigFile2.inputStream) diff --git a/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderBuilder.kt b/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderBuilder.kt deleted file mode 100644 index 7f77351952..0000000000 --- a/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderBuilder.kt +++ /dev/null @@ -1,12 +0,0 @@ -package net.corda.serialization.internal - -import net.corda.core.crypto.SecureHash - -/** - * Drop-in replacement for [AttachmentsClassLoaderBuilder] in the serialization module. - * This version is not strongly-coupled to [net.corda.core.node.ServiceHub]. - */ -@Suppress("UNUSED", "UNUSED_PARAMETER") -internal class AttachmentsClassLoaderBuilder() { - fun build(attachmentHashes: List, properties: Map, deserializationClassLoader: ClassLoader): AttachmentsClassLoader? = null -} \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/AttachmentsClassLoader.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/AttachmentsClassLoader.kt deleted file mode 100644 index 79de2f342b..0000000000 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/AttachmentsClassLoader.kt +++ /dev/null @@ -1,115 +0,0 @@ -package net.corda.serialization.internal - -import net.corda.core.KeepForDJVM -import net.corda.core.contracts.Attachment -import net.corda.core.contracts.ContractAttachment -import net.corda.core.crypto.SecureHash -import net.corda.core.internal.isUploaderTrusted -import net.corda.core.serialization.CordaSerializable -import java.io.ByteArrayOutputStream -import java.io.FileNotFoundException -import java.io.InputStream -import java.net.URL -import java.net.URLConnection -import java.net.URLStreamHandler -import java.security.CodeSigner -import java.security.CodeSource -import java.security.SecureClassLoader -import java.util.* - -/** - * A custom ClassLoader that knows how to load classes from a set of attachments. The attachments themselves only - * need to provide JAR streams, and so could be fetched from a database, local disk, etc. Constructing an - * AttachmentsClassLoader is somewhat expensive, as every attachment is scanned to ensure that there are no overlapping - * file paths. - */ -@KeepForDJVM -class AttachmentsClassLoader(attachments: List, parent: ClassLoader = ClassLoader.getSystemClassLoader()) : SecureClassLoader(parent) { - private val pathsToAttachments = HashMap() - private val idsToAttachments = HashMap() - - @CordaSerializable - class OverlappingAttachments(val path: String) : Exception() { - override fun toString() = "Multiple attachments define a file at path $path" - } - - init { - require(attachments.mapNotNull { it as? ContractAttachment }.all { isUploaderTrusted(it.uploader) }) { - "Attempting to load Contract Attachments downloaded from the network" - } - - for (attachment in attachments) { - attachment.openAsJAR().use { jar -> - while (true) { - val entry = jar.nextJarEntry ?: break - - // We already verified that paths are not strange/game playing when we inserted the attachment - // into the storage service. So we don't need to repeat it here. - // - // We forbid files that differ only in case, or path separator to avoid issues for Windows/Mac developers where the - // filesystem tries to be case insensitive. This may break developers who attempt to use ProGuard. - // - // Also convert to Unix path separators as all resource/class lookups will expect this. - val path = entry.name.toLowerCase().replace('\\', '/') - if (path in pathsToAttachments) - throw OverlappingAttachments(path) - pathsToAttachments[path] = attachment - } - } - idsToAttachments[attachment.id] = attachment - } - } - - // Example: attachment://0b4fc1327f3bbebf1bfe98330ea402ae035936c3cb6da9bd3e26eeaa9584e74d/some/file.txt - // - // We have to provide a fake stream handler to satisfy the URL class that the scheme is known. But it's not - // a real scheme and we don't register it. It's just here to ensure that there aren't codepaths that could - // lead to data loading that we don't control right here in this class (URLs can have evil security properties!) - private val fakeStreamHandler = object : URLStreamHandler() { - override fun openConnection(u: URL?): URLConnection? { - throw UnsupportedOperationException() - } - } - - private fun Attachment.toURL(path: String?) = URL(null, "attachment://$id/" + (path ?: ""), fakeStreamHandler) - - override fun findClass(name: String): Class<*> { - val path = name.replace('.', '/').toLowerCase() + ".class" - val attachment = pathsToAttachments[path] ?: throw ClassNotFoundException(name) - val stream = ByteArrayOutputStream() - try { - attachment.extractFile(path, stream) - } catch (e: FileNotFoundException) { - throw ClassNotFoundException(name) - } - val bytes = stream.toByteArray() - // We don't attempt to propagate signatures from the JAR into the codesource, because our sandbox does not - // depend on external policy files to specify what it can do, so the data wouldn't be useful. - val codesource = CodeSource(attachment.toURL(null), emptyArray()) - // TODO: Define an empty ProtectionDomain to start enforcing the standard Java sandbox. - // The standard Java sandbox is insufficient for our needs and a much more sophisticated sandboxing - // ClassLoader will appear here in future, but it can't hurt to use the default one too: defence in depth! - return defineClass(name, bytes, 0, bytes.size, codesource) - } - - override fun findResource(name: String): URL? { - val attachment = pathsToAttachments[name.toLowerCase()] ?: return null - return attachment.toURL(name) - } - - override fun getResourceAsStream(name: String): InputStream? { - val url = getResource(name) ?: return null // May check parent classloaders, for example. - if (url.protocol != "attachment") return null - val attachment = idsToAttachments[SecureHash.parse(url.host)] ?: return null - val path = url.path?.substring(1) ?: return null // Chop off the leading slash. - return try { - val stream = ByteArrayOutputStream() - attachment.extractFile(path, stream) - stream.toByteArray().inputStream() - } catch (e: FileNotFoundException) { - null - } - } -} - - diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/CheckpointSerializationScheme.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/CheckpointSerializationScheme.kt index 12519312e9..83ec6971ce 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/CheckpointSerializationScheme.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/CheckpointSerializationScheme.kt @@ -16,15 +16,6 @@ data class CheckpointSerializationContextImpl @JvmOverloads constructor( override val objectReferencesEnabled: Boolean, override val encoding: SerializationEncoding?, override val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist) : CheckpointSerializationContext { - /** - * {@inheritDoc} - * - * Unsupported for checkpoints. - */ - override fun withAttachmentsClassLoader(attachmentHashes: List): CheckpointSerializationContext { - throw UnsupportedOperationException() - } - override fun withProperty(property: Any, value: Any): CheckpointSerializationContext { return copy(properties = properties + (property to value)) } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/SerializationScheme.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/SerializationScheme.kt index ed5aecd987..5f4b1127c3 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/SerializationScheme.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/SerializationScheme.kt @@ -8,6 +8,7 @@ import net.corda.core.contracts.Attachment import net.corda.core.crypto.SecureHash import net.corda.core.internal.copyBytes import net.corda.core.serialization.* +import net.corda.core.serialization.internal.AttachmentsClassLoader import net.corda.core.utilities.ByteSequence import net.corda.serialization.internal.amqp.amqpMagic import org.slf4j.LoggerFactory @@ -31,20 +32,12 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe override val useCase: SerializationContext.UseCase, override val encoding: SerializationEncoding?, override val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist, - override val lenientCarpenterEnabled: Boolean = false, - private val builder: AttachmentsClassLoaderBuilder = AttachmentsClassLoaderBuilder() -) : SerializationContext { - - + override val lenientCarpenterEnabled: Boolean = false) : SerializationContext { /** * {@inheritDoc} - * - * We need to cache the AttachmentClassLoaders to avoid too many contexts, since the class loader is part of cache key for the context. */ override fun withAttachmentsClassLoader(attachmentHashes: List): SerializationContext { - properties[attachmentsClassLoaderEnabledPropertyName] as? Boolean == true || return this - val classLoader = builder.build(attachmentHashes, properties, deserializationClassLoader) ?: return this - return withClassLoader(classLoader) + return this } override fun withProperty(property: Any, value: Any): SerializationContext { @@ -72,34 +65,6 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe override fun withEncodingWhitelist(encodingWhitelist: EncodingWhitelist) = copy(encodingWhitelist = encodingWhitelist) } -/* - * This class is internal rather than private so that serialization-deterministic - * can replace it with an alternative version. - */ -@DeleteForDJVM -class AttachmentsClassLoaderBuilder() { - private val cache: Cache, ClassLoader>, AttachmentsClassLoader> = Caffeine.newBuilder().weakValues().maximumSize(1024).build() - - fun build(attachmentHashes: List, properties: Map, deserializationClassLoader: ClassLoader): AttachmentsClassLoader? { - val serializationContext = properties[serializationContextKey] as? SerializeAsTokenContext ?: return null // Some tests don't set one. - try { - return cache.get(Pair(attachmentHashes, deserializationClassLoader)) { - val missing = ArrayList() - val attachments = ArrayList() - attachmentHashes.forEach { id -> - serializationContext.serviceHub.attachments.openAttachment(id)?.let { attachments += it } - ?: run { missing += id } - } - missing.isNotEmpty() && throw MissingAttachmentsException(missing) - AttachmentsClassLoader(attachments, parent = deserializationClassLoader) - }!! - } catch (e: ExecutionException) { - // Caught from within the cache get, so unwrap. - throw e.cause!! - } - } -} - @KeepForDJVM open class SerializationFactoryImpl( // TODO: This is read-mostly. Probably a faster implementation to be found. diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderTests.kt deleted file mode 100644 index b428fdd811..0000000000 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderTests.kt +++ /dev/null @@ -1,382 +0,0 @@ -package net.corda.serialization.internal - -import com.nhaarman.mockito_kotlin.doReturn -import com.nhaarman.mockito_kotlin.whenever -import net.corda.core.contracts.Attachment -import net.corda.core.contracts.Contract -import net.corda.core.crypto.SecureHash -import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.declaredField -import net.corda.core.internal.toWireTransaction -import net.corda.core.node.ServiceHub -import net.corda.core.node.services.AttachmentStorage -import net.corda.core.serialization.* -import net.corda.core.utilities.ByteSequence -import net.corda.core.utilities.OpaqueBytes -import net.corda.node.internal.cordapp.JarScanningCordappLoader -import net.corda.node.internal.cordapp.CordappProviderImpl -import net.corda.nodeapi.DummyContractBackdoor -import net.corda.testing.common.internal.testNetworkParameters -import net.corda.testing.core.DUMMY_NOTARY_NAME -import net.corda.testing.core.SerializationEnvironmentRule -import net.corda.testing.core.TestIdentity -import net.corda.testing.internal.MockCordappConfigProvider -import net.corda.testing.internal.kryoSpecific -import net.corda.testing.internal.rigorousMock -import net.corda.testing.services.MockAttachmentStorage -import org.apache.commons.io.IOUtils -import org.junit.Assert.* -import org.junit.Rule -import org.junit.Test -import java.io.ByteArrayOutputStream -import java.net.URL -import java.net.URLClassLoader -import java.util.jar.JarOutputStream -import java.util.zip.ZipEntry -import kotlin.test.assertFailsWith - -class AttachmentsClassLoaderTests { - companion object { - val ISOLATED_CONTRACTS_JAR_PATH: URL = AttachmentsClassLoaderTests::class.java.getResource("isolated.jar") - private const val ISOLATED_CONTRACT_CLASS_NAME = "net.corda.finance.contracts.isolated.AnotherDummyContract" - private val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party - private val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party - private fun SerializationContext.withAttachmentStorage(attachmentStorage: AttachmentStorage): SerializationContext { - val serviceHub = rigorousMock() - doReturn(attachmentStorage).whenever(serviceHub).attachments - return this.withServiceHub(serviceHub) - } - - private fun SerializationContext.withServiceHub(serviceHub: ServiceHub): SerializationContext { - return this.withTokenContext(SerializeAsTokenContextImpl(serviceHub) {}).withProperty(attachmentsClassLoaderEnabledPropertyName, true) - } - } - - @Rule - @JvmField - val testSerialization = SerializationEnvironmentRule() - private val attachments = MockAttachmentStorage() - private val networkParameters = testNetworkParameters() - private val cordappProvider = CordappProviderImpl(JarScanningCordappLoader.fromJarUrls(listOf(ISOLATED_CONTRACTS_JAR_PATH)), MockCordappConfigProvider(), attachments).apply { - start(networkParameters.whitelistedContractImplementations) - } - private val cordapp get() = cordappProvider.cordapps.first() - private val attachmentId get() = cordappProvider.getCordappAttachmentId(cordapp)!! - private val appContext get() = cordappProvider.getAppContext(cordapp) - private val serviceHub = rigorousMock().also { - doReturn(attachments).whenever(it).attachments - doReturn(cordappProvider).whenever(it).cordappProvider - doReturn(networkParameters).whenever(it).networkParameters - } - - // These ClassLoaders work together to load 'AnotherDummyContract' in a disposable way, such that even though - // the class may be on the unit test class path (due to default IDE settings, etc), it won't be loaded into the - // regular app classloader but rather than ClassLoaderForTests. This helps keep our environment clean and - // ensures we have precise control over where it's loaded. - object FilteringClassLoader : ClassLoader() { - @Throws(ClassNotFoundException::class) - override fun loadClass(name: String, resolve: Boolean): Class<*> { - if ("AnotherDummyContract" in name) { - throw ClassNotFoundException(name) - } - return super.loadClass(name, resolve) - } - } - - class ClassLoaderForTests : URLClassLoader(arrayOf(ISOLATED_CONTRACTS_JAR_PATH), FilteringClassLoader) - @Test - fun `dynamically load AnotherDummyContract from isolated contracts jar`() { - ClassLoaderForTests().use { child -> - val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child) - val contract = contractClass.newInstance() as Contract - - assertEquals("helloworld", contract.declaredField("magicString").value) - } - } - - private fun fakeAttachment(filepath: String, content: String): ByteArray { - val bs = ByteArrayOutputStream() - JarOutputStream(bs).use { js -> - js.putNextEntry(ZipEntry(filepath)) - js.writer().apply { append(content); flush() } - js.closeEntry() - } - return bs.toByteArray() - } - - private fun readAttachment(attachment: Attachment, filepath: String): ByteArray { - ByteArrayOutputStream().use { - attachment.extractFile(filepath, it) - return it.toByteArray() - } - } - - @Test - fun `test MockAttachmentStorage open as jar`() { - val storage = attachments - val key = attachmentId - val attachment = storage.openAttachment(key)!! - - val jar = attachment.openAsJAR() - - assertNotNull(jar.nextEntry) - } - - @Test - @Suppress("DEPRECATION") - fun `test overlapping file exception`() { - val storage = attachments - val att0 = attachmentId - val att1 = storage.importAttachment(fakeAttachment("file.txt", "some data").inputStream()) - val att2 = storage.importAttachment(fakeAttachment("file.txt", "some other data").inputStream()) - - assertFailsWith(AttachmentsClassLoader.OverlappingAttachments::class) { - AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }) - } - } - - @Test - @Suppress("DEPRECATION") - fun basic() { - val storage = attachments - val att0 = attachmentId - val att1 = storage.importAttachment(fakeAttachment("file1.txt", "some data").inputStream()) - val att2 = storage.importAttachment(fakeAttachment("file2.txt", "some other data").inputStream()) - - val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }) - val txt = IOUtils.toString(cl.getResourceAsStream("file1.txt"), Charsets.UTF_8.name()) - assertEquals("some data", txt) - } - - @Test - @Suppress("DEPRECATION") - fun `Check platform independent path handling in attachment jars`() { - val storage = MockAttachmentStorage() - - val att1 = storage.importAttachment(fakeAttachment("/folder1/foldera/file1.txt", "some data").inputStream()) - val att2 = storage.importAttachment(fakeAttachment("\\folder1\\folderb\\file2.txt", "some other data").inputStream()) - - val data1a = readAttachment(storage.openAttachment(att1)!!, "/folder1/foldera/file1.txt") - assertArrayEquals("some data".toByteArray(), data1a) - - val data1b = readAttachment(storage.openAttachment(att1)!!, "\\folder1\\foldera\\file1.txt") - assertArrayEquals("some data".toByteArray(), data1b) - - val data2a = readAttachment(storage.openAttachment(att2)!!, "\\folder1\\folderb\\file2.txt") - assertArrayEquals("some other data".toByteArray(), data2a) - - val data2b = readAttachment(storage.openAttachment(att2)!!, "/folder1/folderb/file2.txt") - assertArrayEquals("some other data".toByteArray(), data2b) - } - - @Test - @Suppress("DEPRECATION") - fun `loading class AnotherDummyContract`() { - val storage = attachments - val att0 = attachmentId - val att1 = storage.importAttachment(fakeAttachment("file1.txt", "some data").inputStream()) - val att2 = storage.importAttachment(fakeAttachment("file2.txt", "some other data").inputStream()) - - val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader) - val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, cl) - val contract = contractClass.newInstance() as Contract - assertEquals(cl, contract.javaClass.classLoader) - assertEquals("helloworld", contract.declaredField("magicString").value) - } - - private fun createContract2Cash(): Contract { - ClassLoaderForTests().use { cl -> - val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, cl) - return contractClass.newInstance() as Contract - } - } - - @Test - @Suppress("DEPRECATION") - fun `testing Kryo with ClassLoader (with top level class name)`() { - val contract = createContract2Cash() - - val bytes = contract.serialize() - val storage = attachments - val att0 = attachmentId - val att1 = storage.importAttachment(fakeAttachment("file1.txt", "some data").inputStream()) - val att2 = storage.importAttachment(fakeAttachment("file2.txt", "some other data").inputStream()) - - val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader) - - val context = SerializationFactory.defaultFactory.defaultContext.withClassLoader(cl).withWhitelisted(contract.javaClass) - val state2 = bytes.deserialize(context = context) - assertTrue(state2.javaClass.classLoader is AttachmentsClassLoader) - assertNotNull(state2) - } - - // top level wrapper - @CordaSerializable - class Data(val contract: Contract) - - @Test - @Suppress("DEPRECATION") - fun `testing Kryo with ClassLoader (without top level class name)`() { - val data = Data(createContract2Cash()) - - assertNotNull(data.contract) - - val context2 = SerializationFactory.defaultFactory.defaultContext.withWhitelisted(data.contract.javaClass) - - val bytes = data.serialize(context = context2) - val storage = attachments - val att0 = attachmentId - val att1 = storage.importAttachment(fakeAttachment("file1.txt", "some data").inputStream()) - val att2 = storage.importAttachment(fakeAttachment("file2.txt", "some other data").inputStream()) - - val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader) - - val context = SerializationFactory.defaultFactory.defaultContext.withClassLoader(cl).withWhitelisted(Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, cl)) - - 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) - - val context3 = SerializationFactory.defaultFactory.defaultContext.withClassLoader(cl2).withWhitelisted(Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, cl2)) - - val state3 = bytes.deserialize(context = context3) - assertEquals(cl2, state3.contract.javaClass.classLoader) - assertNotNull(state3) - } - - @Test - fun `test serialization of SecureHash`() { - val secureHash = SecureHash.randomSHA256() - val bytes = secureHash.serialize() - val copiedSecuredHash = bytes.deserialize() - - assertEquals(secureHash, copiedSecuredHash) - } - - @Test - fun `test serialization of OpaqueBytes`() { - val opaqueBytes = OpaqueBytes("0123456789".toByteArray()) - val bytes = opaqueBytes.serialize() - val copiedOpaqueBytes = bytes.deserialize() - - assertEquals(opaqueBytes, copiedOpaqueBytes) - } - - @Test - fun `test serialization of sub-sequence OpaqueBytes`() { - val bytesSequence = ByteSequence.of("0123456789".toByteArray(), 3, 2) - val bytes = bytesSequence.serialize() - val copiedBytesSequence = bytes.deserialize() - - assertEquals(bytesSequence, copiedBytesSequence) - } - - @Test - fun `test serialization of WireTransaction with dynamically loaded contract`() { - val child = appContext.classLoader - val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child) - val contract = contractClass.newInstance() as DummyContractBackdoor - val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY) - val context = SerializationFactory.defaultFactory.defaultContext - .withWhitelisted(contract.javaClass) - .withWhitelisted(Class.forName("$ISOLATED_CONTRACT_CLASS_NAME\$State", true, child)) - .withWhitelisted(Class.forName("$ISOLATED_CONTRACT_CLASS_NAME\$Commands\$Create", true, child)) - .withServiceHub(serviceHub) - .withClassLoader(child) - - val bytes = run { - val wireTransaction = tx.toWireTransaction(serviceHub, context) - wireTransaction.serialize(context = context) - } - val copiedWireTransaction = bytes.deserialize(context = context) - assertEquals(1, copiedWireTransaction.outputs.size) - // Contracts need to be loaded by the same classloader as the ContractState itself - val contractClassloader = copiedWireTransaction.getOutput(0).javaClass.classLoader - val contract2 = contractClassloader.loadClass(copiedWireTransaction.outputs.first().contract).newInstance() as DummyContractBackdoor - assertEquals(contract2.javaClass.classLoader, copiedWireTransaction.outputs[0].data.javaClass.classLoader) - assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0].data)) - } - - @Test - fun `test deserialize of WireTransaction where contract cannot be found`() { - kryoSpecific("Kryo verifies/loads attachments on deserialization, whereas AMQP currently does not") { - ClassLoaderForTests().use { child -> - val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child) - val contract = contractClass.newInstance() as DummyContractBackdoor - val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY) - val attachmentRef = attachmentId - val bytes = run { - val outboundContext = SerializationFactory.defaultFactory.defaultContext - .withServiceHub(serviceHub) - .withClassLoader(child) - val wireTransaction = tx.toWireTransaction(serviceHub, outboundContext) - wireTransaction.serialize(context = outboundContext) - } - // use empty attachmentStorage - - val e = assertFailsWith(MissingAttachmentsException::class) { - val mockAttStorage = MockAttachmentStorage() - val inboundContext = SerializationFactory.defaultFactory.defaultContext - .withAttachmentStorage(mockAttStorage) - .withAttachmentsClassLoader(listOf(attachmentRef)) - bytes.deserialize(context = inboundContext) - - if (mockAttStorage.openAttachment(attachmentRef) == null) { - throw MissingAttachmentsException(listOf(attachmentRef)) - } - } - assertEquals(attachmentRef, e.ids.single()) - } - } - } - - @Test - fun `test loading a class from attachment during deserialization`() { - ClassLoaderForTests().use { child -> - val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child) - val contract = contractClass.newInstance() as DummyContractBackdoor - val outboundContext = SerializationFactory.defaultFactory.defaultContext.withClassLoader(child) - val attachmentRef = attachmentId - // We currently ignore annotations in attachments, so manually whitelist. - val inboundContext = SerializationFactory - .defaultFactory - .defaultContext - .withWhitelisted(contract.javaClass) - .withServiceHub(serviceHub) - .withAttachmentsClassLoader(listOf(attachmentRef)) - - // Serialize with custom context to avoid populating the default context with the specially loaded class - val serialized = contract.serialize(context = outboundContext) - // Then deserialize with the attachment class loader associated with the attachment - serialized.deserialize(context = inboundContext) - } - } - - @Test - fun `test loading a class with attachment missing during deserialization`() { - ClassLoaderForTests().use { child -> - val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child) - val contract = contractClass.newInstance() as DummyContractBackdoor - val attachmentRef = SecureHash.randomSHA256() - val outboundContext = SerializationFactory.defaultFactory.defaultContext.withClassLoader(child) - // Serialize with custom context to avoid populating the default context with the specially loaded class - val serialized = contract.serialize(context = outboundContext) - - // Then deserialize with the attachment class loader associated with the attachment - val e = assertFailsWith(MissingAttachmentsException::class) { - // We currently ignore annotations in attachments, so manually whitelist. - val inboundContext = SerializationFactory - .defaultFactory - .defaultContext - .withWhitelisted(contract.javaClass) - .withServiceHub(serviceHub) - .withAttachmentsClassLoader(listOf(attachmentRef)) - serialized.deserialize(context = inboundContext) - } - assertEquals(attachmentRef, e.ids.single()) - } - } -} diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/CordaClassResolverTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/CordaClassResolverTests.kt index 860a04a81c..700b8b0560 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/CordaClassResolverTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/CordaClassResolverTests.kt @@ -14,6 +14,7 @@ import net.corda.core.node.services.AttachmentStorage import net.corda.core.serialization.internal.CheckpointSerializationContext import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.internal.AttachmentsClassLoader import net.corda.node.serialization.kryo.CordaClassResolver import net.corda.node.serialization.kryo.CordaKryo import net.corda.testing.internal.rigorousMock @@ -22,6 +23,7 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.ExpectedException import java.lang.IllegalStateException +import java.net.URL import java.sql.Connection import java.util.* import kotlin.test.assertEquals @@ -112,6 +114,7 @@ class CordaClassResolverTests { val emptyListClass = listOf().javaClass val emptySetClass = setOf().javaClass val emptyMapClass = mapOf().javaClass + val ISOLATED_CONTRACTS_JAR_PATH: URL = CordaClassResolverTests::class.java.getResource("isolated.jar") } private val emptyWhitelistContext: CheckpointSerializationContext = CheckpointSerializationContextImpl(this.javaClass.classLoader, EmptyWhitelist, emptyMap(), true, null) @@ -201,7 +204,7 @@ class CordaClassResolverTests { CordaClassResolver(emptyWhitelistContext).getRegistration(DefaultSerializable::class.java) } - private fun importJar(storage: AttachmentStorage, uploader: String = DEPLOYED_CORDAPP_UPLOADER) = AttachmentsClassLoaderTests.ISOLATED_CONTRACTS_JAR_PATH.openStream().use { storage.importAttachment(it, uploader, "") } + private fun importJar(storage: AttachmentStorage, uploader: String = DEPLOYED_CORDAPP_UPLOADER) = ISOLATED_CONTRACTS_JAR_PATH.openStream().use { storage.importAttachment(it, uploader, "") } @Test(expected = KryoException::class) fun `Annotation does not work in conjunction with AttachmentClassLoader annotation`() { diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/core/TestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/core/TestUtils.kt index 485e7671c2..54b6e7e605 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/core/TestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/core/TestUtils.kt @@ -161,3 +161,4 @@ fun NodeInfo.singleIdentityAndCert(): PartyAndCertificate = legalIdentitiesAndCe * Extract a single identity from the node info. Throws an error if the node has multiple identities. */ fun NodeInfo.singleIdentity(): Party = singleIdentityAndCert().party + diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt index 8f82cf3792..5f044339b6 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt @@ -33,10 +33,13 @@ import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.registerDevP2pCertificates import net.corda.serialization.internal.amqp.AMQP_ENABLED import net.corda.testing.internal.stubs.CertificateStoreStubs +import java.io.ByteArrayOutputStream import java.nio.file.Files import java.nio.file.Path import java.security.KeyPair import java.util.* +import java.util.jar.JarOutputStream +import java.util.zip.ZipEntry import javax.security.auth.x500.X500Principal @Suppress("unused") @@ -169,7 +172,20 @@ fun configureDatabase(hikariProperties: Properties, schemaService: SchemaService = NodeSchemaService(), internalSchemas: Set = NodeSchemaService().internalSchemas(), cacheFactory: NamedCacheFactory = TestingNamedCacheFactory()): CordaPersistence { - val persistence = createCordaPersistence(databaseConfig, wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous, schemaService, hikariProperties, cacheFactory) + val persistence = createCordaPersistence(databaseConfig, wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous, schemaService, hikariProperties, cacheFactory, null) persistence.startHikariPool(hikariProperties, databaseConfig, internalSchemas) return persistence +} + +/** + * Convenience method for creating a fake attachment containing a file with some content. + */ +fun fakeAttachment(filePath: String, content: String): ByteArray { + val bs = ByteArrayOutputStream() + JarOutputStream(bs).use { js -> + js.putNextEntry(ZipEntry(filePath)) + js.writer().apply { append(content); flush() } + js.closeEntry() + } + return bs.toByteArray() } \ No newline at end of file diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt index a6847b8fa2..3b87150155 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt @@ -39,7 +39,7 @@ class MockCordappProvider( allFlows = emptyList(), jarHash = SecureHash.allOnesHash) if (cordappRegistry.none { it.first.contractClassNames.contains(contractClassName) && it.second == contractHash }) { - cordappRegistry.add(Pair(cordapp, findOrImportAttachment(listOf(contractClassName), contractClassName.toByteArray(), attachments, contractHash, signers))) + cordappRegistry.add(Pair(cordapp, findOrImportAttachment(listOf(contractClassName), fakeAttachmentCached(contractClassName), attachments, contractHash, signers))) } return cordappRegistry.findLast { contractClassName in it.first.contractClassNames }?.second!! } @@ -57,4 +57,9 @@ class MockCordappProvider( attachments.importContractAttachment(contractClassNames, DEPLOYED_CORDAPP_UPLOADER, data.inputStream(), contractHash, signers) } } + + private val attachmentsCache = mutableMapOf() + private fun fakeAttachmentCached(contractClass: String): ByteArray = attachmentsCache.computeIfAbsent(contractClass) { + fakeAttachment(contractClass, contractClass) + } } From 349d9a5ffed75795e2ac16b7bc351e4d5e0ee433 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 19 Nov 2018 13:39:07 +0100 Subject: [PATCH 12/19] Expose SerializedBytes.from in the API --- .../kotlin/net/corda/core/serialization/SerializationAPI.kt | 5 ----- 1 file changed, 5 deletions(-) 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 941617ba04..4ef133d341 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt @@ -300,13 +300,8 @@ class SerializedBytes(bytes: ByteArray) : OpaqueBytes(bytes) { /** * Serializes the given object and returns a [SerializedBytes] wrapper for it. An alias for [Any.serialize] * intended to make the calling smoother for Java users. - * - * TODO: Take out the @CordaInternal annotation post-Enterprise GA when we can add API again. - * - * @suppress */ @JvmStatic - @CordaInternal @JvmOverloads fun from(obj: T, serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): SerializedBytes { From ddf45f4e079d7b71f03e93ed149b525ff77b8fd7 Mon Sep 17 00:00:00 2001 From: Dominic Fox <40790090+distributedleetravis@users.noreply.github.com> Date: Mon, 19 Nov 2018 16:07:01 +0000 Subject: [PATCH 13/19] CORDA-2099: define local type model (#4118) * CORDA-2099 define LocalTypeInformation and related data types and functions * Enums don't have superclasses * Separate ACollection from AMap * Remove spurious import * Small fixes, slightly improved testing * Log warnings if types that we expect to be able to serialise are non-composable * Rename lookup -> findOrBuild * Pull changes from working branch * Missing files needed for unit test * Pull in whitelist-based type model configuration * Move opaque type list across * Remote duplicate declaration * Restore fixes from other PR --- .../internal/amqp/CustomSerializerRegistry.kt | 94 ++++ .../amqp/DescriptorBasedSerializerRegistry.kt | 29 ++ .../WhitelistBasedTypeModelConfiguration.kt | 45 ++ .../model/LocalPropertyInformation.kt | 71 +++ .../internal/model/LocalTypeInformation.kt | 374 ++++++++++++++++ .../model/LocalTypeInformationBuilder.kt | 410 ++++++++++++++++++ .../internal/model/LocalTypeModel.kt | 91 ++++ .../internal/model/RemoteTypeCarpenter.kt | 2 - .../internal/model/LocalTypeModelTests.kt | 166 +++++++ 9 files changed, 1280 insertions(+), 2 deletions(-) create mode 100644 serialization/src/main/kotlin/net/corda/serialization/internal/amqp/CustomSerializerRegistry.kt create mode 100644 serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DescriptorBasedSerializerRegistry.kt create mode 100644 serialization/src/main/kotlin/net/corda/serialization/internal/amqp/WhitelistBasedTypeModelConfiguration.kt create mode 100644 serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalPropertyInformation.kt create mode 100644 serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeInformation.kt create mode 100644 serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeInformationBuilder.kt create mode 100644 serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeModel.kt create mode 100644 serialization/src/test/kotlin/net/corda/serialization/internal/model/LocalTypeModelTests.kt diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/CustomSerializerRegistry.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/CustomSerializerRegistry.kt new file mode 100644 index 0000000000..61478dc43c --- /dev/null +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/CustomSerializerRegistry.kt @@ -0,0 +1,94 @@ +package net.corda.serialization.internal.amqp + +import net.corda.core.internal.uncheckedCast +import net.corda.core.utilities.contextLogger +import net.corda.serialization.internal.model.DefaultCacheProvider +import net.corda.serialization.internal.model.TypeIdentifier +import java.lang.reflect.Type + +interface CustomSerializerRegistry { + /** + * Register a custom serializer for any type that cannot be serialized or deserialized by the default serializer + * that expects to find getters and a constructor with a parameter for each property. + */ + fun register(customSerializer: CustomSerializer) + fun registerExternal(customSerializer: CorDappCustomSerializer) + + fun findCustomSerializer(clazz: Class<*>, declaredType: Type): AMQPSerializer? +} + +class CachingCustomSerializerRegistry( + private val descriptorBasedSerializerRegistry: DescriptorBasedSerializerRegistry) + : CustomSerializerRegistry { + + companion object { + val logger = contextLogger() + } + + private data class CustomSerializerIdentifier(val actualTypeIdentifier: TypeIdentifier, val declaredTypeIdentifier: TypeIdentifier) + + private val customSerializersCache: MutableMap> = DefaultCacheProvider.createCache() + private var customSerializers: List = emptyList() + + /** + * Register a custom serializer for any type that cannot be serialized or deserialized by the default serializer + * that expects to find getters and a constructor with a parameter for each property. + */ + override fun register(customSerializer: CustomSerializer) { + logger.trace("action=\"Registering custom serializer\", class=\"${customSerializer.type}\"") + + descriptorBasedSerializerRegistry.getOrBuild(customSerializer.typeDescriptor.toString()) { + customSerializers += customSerializer + for (additional in customSerializer.additionalSerializers) { + register(additional) + } + customSerializer + } + } + + override fun registerExternal(customSerializer: CorDappCustomSerializer) { + logger.trace("action=\"Registering external serializer\", class=\"${customSerializer.type}\"") + + descriptorBasedSerializerRegistry.getOrBuild(customSerializer.typeDescriptor.toString()) { + customSerializers += customSerializer + customSerializer + } + } + + override fun findCustomSerializer(clazz: Class<*>, declaredType: Type): AMQPSerializer? { + val typeIdentifier = CustomSerializerIdentifier( + TypeIdentifier.forClass(clazz), + TypeIdentifier.forGenericType(declaredType)) + + return customSerializersCache[typeIdentifier] + ?: doFindCustomSerializer(clazz, declaredType)?.also { serializer -> + customSerializersCache.putIfAbsent(typeIdentifier, serializer) + } + } + + private fun doFindCustomSerializer(clazz: Class<*>, declaredType: Type): AMQPSerializer? { + // e.g. Imagine if we provided a Map serializer this way, then it won't work if the declared type is + // AbstractMap, only Map. Otherwise it needs to inject additional schema for a RestrictedType source of the + // super type. Could be done, but do we need it? + for (customSerializer in customSerializers) { + if (customSerializer.isSerializerFor(clazz)) { + val declaredSuperClass = declaredType.asClass().superclass + + return if (declaredSuperClass == null + || !customSerializer.isSerializerFor(declaredSuperClass) + || !customSerializer.revealSubclassesInSchema + ) { + logger.debug("action=\"Using custom serializer\", class=${clazz.typeName}, " + + "declaredType=${declaredType.typeName}") + + @Suppress("UNCHECKED_CAST") + customSerializer as? AMQPSerializer + } else { + // Make a subclass serializer for the subclass and return that... + CustomSerializer.SubClass(clazz, uncheckedCast(customSerializer)) + } + } + } + return null + } +} \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DescriptorBasedSerializerRegistry.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DescriptorBasedSerializerRegistry.kt new file mode 100644 index 0000000000..2a2d17127d --- /dev/null +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DescriptorBasedSerializerRegistry.kt @@ -0,0 +1,29 @@ +package net.corda.serialization.internal.amqp + +import net.corda.serialization.internal.model.DefaultCacheProvider + +/** + * The quickest way to find a serializer, if one has already been generated, is to look it up by type descriptor. + * + * This registry gets shared around between various participants that might want to use it as a lookup, or register + * serialisers that they have created with it. + */ +interface DescriptorBasedSerializerRegistry { + operator fun get(descriptor: String): AMQPSerializer? + operator fun set(descriptor: String, serializer: AMQPSerializer) + fun getOrBuild(descriptor: String, builder: () -> AMQPSerializer): AMQPSerializer +} + +class DefaultDescriptorBasedSerializerRegistry: DescriptorBasedSerializerRegistry { + + private val registry: MutableMap> = DefaultCacheProvider.createCache() + + override fun get(descriptor: String): AMQPSerializer? = registry[descriptor] + + override fun set(descriptor: String, serializer: AMQPSerializer) { + registry.putIfAbsent(descriptor, serializer) + } + + override fun getOrBuild(descriptor: String, builder: () -> AMQPSerializer) = + get(descriptor) ?: builder().also { newSerializer -> this[descriptor] = newSerializer } +} \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/WhitelistBasedTypeModelConfiguration.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/WhitelistBasedTypeModelConfiguration.kt new file mode 100644 index 0000000000..dfb7651b54 --- /dev/null +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/WhitelistBasedTypeModelConfiguration.kt @@ -0,0 +1,45 @@ +package net.corda.serialization.internal.amqp + +import com.google.common.primitives.Primitives +import net.corda.core.serialization.ClassWhitelist +import net.corda.serialization.internal.model.LocalTypeModelConfiguration +import org.apache.qpid.proton.amqp.* +import java.lang.reflect.Type +import java.util.* + +/** + * [LocalTypeModelConfiguration] based on a [ClassWhitelist] + */ +class WhitelistBasedTypeModelConfiguration( + private val whitelist: ClassWhitelist, + private val customSerializerRegistry: CustomSerializerRegistry) + : LocalTypeModelConfiguration { + override fun isExcluded(type: Type): Boolean = whitelist.isNotWhitelisted(type.asClass()) + override fun isOpaque(type: Type): Boolean = Primitives.unwrap(type.asClass()) in opaqueTypes || + customSerializerRegistry.findCustomSerializer(type.asClass(), type) != null +} + +// Copied from SerializerFactory so that we can have equivalent behaviour, for now. +private val opaqueTypes = setOf( + Character::class.java, + Char::class.java, + Boolean::class.java, + Byte::class.java, + UnsignedByte::class.java, + Short::class.java, + UnsignedShort::class.java, + Int::class.java, + UnsignedInteger::class.java, + Long::class.java, + UnsignedLong::class.java, + Float::class.java, + Double::class.java, + Decimal32::class.java, + Decimal64::class.java, + Decimal128::class.java, + Date::class.java, + UUID::class.java, + ByteArray::class.java, + String::class.java, + Symbol::class.java +) \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalPropertyInformation.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalPropertyInformation.kt new file mode 100644 index 0000000000..69b223d32b --- /dev/null +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalPropertyInformation.kt @@ -0,0 +1,71 @@ +package net.corda.serialization.internal.model + +import java.lang.reflect.Field +import java.lang.reflect.Method + +/** + * Represents the information we have about a property of a type. + */ +sealed class LocalPropertyInformation(val isCalculated: Boolean) { + + /** + * [LocalTypeInformation] for the type of the property. + */ + abstract val type: LocalTypeInformation + + /** + * True if the property is a primitive type or is flagged as non-nullable, false otherwise. + */ + abstract val isMandatory: Boolean + + /** + * A property of an interface, for which we have only a getter method. + * + * @param observedGetter The method which can be used to obtain the value of this property from an instance of its owning type. + */ + data class ReadOnlyProperty(val observedGetter: Method, override val type: LocalTypeInformation, override val isMandatory: Boolean) : LocalPropertyInformation(false) + + /** + * A property for which we have both a getter, and a matching slot in an array of constructor parameters. + * + * @param observedGetter The method which can be used to obtain the value of this property from an instance of its owning type. + * @param constructorSlot The [ConstructorSlot] to which the property corresponds, used to populate an array of + * constructor arguments when creating instances of its owning type. + */ + data class ConstructorPairedProperty(val observedGetter: Method, val constructorSlot: ConstructorSlot, override val type: LocalTypeInformation, override val isMandatory: Boolean) : LocalPropertyInformation(false) + + /** + * A property for which we have no getter, but for which there is a backing field a matching slot in an array of + * constructor parameters. + * + * @param observedField The field which can be used to obtain the value of this property from an instance of its owning type. + * @param constructorSlot The [ConstructorSlot] to which the property corresponds, used to populate an array of + * constructor arguments when creating instances of its owning type. + */ + data class PrivateConstructorPairedProperty(val observedField: Field, val constructorSlot: ConstructorSlot, override val type: LocalTypeInformation, override val isMandatory: Boolean) : LocalPropertyInformation(false) + + /** + * A property for which we have both getter and setter methods (usually belonging to a POJO which is initialised + * with the default no-argument constructor and then configured via setters). + * + * @param observedGetter The method which can be used to obtain the value of this property from an instance of its owning type. + * @param observedSetter The method which can be used to set the value of this property on an instance of its owning type. + */ + data class GetterSetterProperty(val observedGetter: Method, val observedSetter: Method, override val type: LocalTypeInformation, override val isMandatory: Boolean) : LocalPropertyInformation(false) + + /** + * A property for which we have only a getter method, which is annotated with [SerializableCalculatedProperty]. + */ + data class CalculatedProperty(val observedGetter: Method, override val type: LocalTypeInformation, override val isMandatory: Boolean) : LocalPropertyInformation(true) +} + + +/** + * References a slot in an array of constructor parameters. + */ +data class ConstructorSlot(val parameterIndex: Int, val constructorInformation: LocalConstructorInformation) { + val parameterInformation get() = constructorInformation.parameters.getOrNull(parameterIndex) ?: + throw IllegalStateException("Constructor slot refers to parameter #$parameterIndex " + + "of constructor $constructorInformation, " + + "but constructor has only ${constructorInformation.parameters.size} parameters") +} \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeInformation.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeInformation.kt new file mode 100644 index 0000000000..206417138b --- /dev/null +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeInformation.kt @@ -0,0 +1,374 @@ +package net.corda.serialization.internal.model + +import java.lang.reflect.* +import kotlin.reflect.KFunction +import java.util.* + +typealias PropertyName = String + +/** + * The [LocalTypeInformation] captured for a [Type] gathers together everything that can be ascertained about the type + * through runtime reflection, in the form of a directed acyclic graph (DAG) of types and relationships between types. + * + * Types can be related in the following ways: + * + * * Type A is the type of a _property_ of type B. + * * Type A is the type of an _interface_ of type B. + * * Type A is the type of the _superclass_ of type B. + * * Type A is the type of a _type parameter_ of type B. + * * Type A is an _array type_, of which type B is the _component type_. + * + * All of these relationships are represented by references and collections held by the objects representing the nodes + * themselves. + * + * A type is [Composable] if it is isomorphic to a dictionary of its property values, i.e. if we can obtain an instance + * of the type from a dictionary containing typed key/value pairs corresponding to its properties, and a dictionary from + * an instance of the type, and can round-trip (in both directions) between these representations without losing + * information. This is the basis for compositional serialization, i.e. building a serializer for a type out of the + * serializers we have for its property types. + * + * A type is [Atomic] if it cannot be decomposed or recomposed in this fashion (usually because it is the type of a + * scalar value of some sort, such as [Int]), and [Opaque] if we have chosen not to investigate its composability, + * typically because it is handled by a custom serializer. + * + * Abstract types are represented by [AnInterface] and [Abstract], the difference between them being that an [Abstract] + * type may have a superclass. + * + * If a concrete type does not have a unique deserialization constructor, it is represented by [NonComposable], meaning + * that we know how to take it apart but do not know how to put it back together again. + * + * An array of any type is represented by [ArrayOf]. Enums are represented by [AnEnum]. + * + * The type of [Any]/[java.lang.Object] is represented by [Top]. Unbounded wildcards, or wildcards whose upper bound is + * [Top], are represented by [Unknown]. Bounded wildcards are always resolved to their upper bounds, e.g. + * `List` becomes `List`. + * + * If we encounter a cycle while traversing the DAG, the type on which traversal detected the cycle is represented by + * [Cycle], and no further traversal is attempted from that type. Kotlin objects are represented by [Singleton]. + */ +sealed class LocalTypeInformation { + + companion object { + /** + * Using the provided [LocalTypeLookup] to record and locate already-visited nodes, traverse the DAG of related + * types beginning the with provided [Type] and construct a complete set of [LocalTypeInformation] for that type. + * + * @param type The [Type] to obtain [LocalTypeInformation] for. + * @param lookup The [LocalTypeLookup] to use to find previously-constructed [LocalTypeInformation]. + */ + fun forType(type: Type, lookup: LocalTypeLookup): LocalTypeInformation = + LocalTypeInformationBuilder(lookup).build(type, TypeIdentifier.forGenericType(type)) + } + + /** + * The actual type which was observed when constructing this type information. + */ + abstract val observedType: Type + + /** + * The [TypeIdentifier] for the type represented by this type information, used to cross-reference with + * [RemoteTypeInformation]. + */ + abstract val typeIdentifier: TypeIdentifier + + /** + * Obtain a multi-line, recursively-indented representation of this type information. + * + * @param simplifyClassNames By default, class names are printed as their "simple" class names, i.e. "String" instead + * of "java.lang.String". If this is set to `false`, then the full class name will be printed instead. + */ + fun prettyPrint(simplifyClassNames: Boolean = true): String = + LocalTypeInformationPrettyPrinter(simplifyClassNames).prettyPrint(this) + + /** + * The [LocalTypeInformation] corresponding to an unbounded wildcard ([TypeIdentifier.UnknownType]) + */ + object Unknown : LocalTypeInformation() { + override val observedType get() = TypeIdentifier.UnknownType.getLocalType() + override val typeIdentifier get() = TypeIdentifier.UnknownType + } + + /** + * The [LocalTypeInformation] corresponding to [java.lang.Object] / [Any] ([TypeIdentifier.TopType]) + */ + object Top : LocalTypeInformation() { + override val observedType get() = TypeIdentifier.TopType.getLocalType() + override val typeIdentifier get() = TypeIdentifier.TopType + } + + /** + * The [LocalTypeInformation] emitted if we hit a cycle while traversing the graph of related types. + */ + data class Cycle( + override val observedType: Type, + override val typeIdentifier: TypeIdentifier, + private val _follow: () -> LocalTypeInformation) : LocalTypeInformation() { + val follow: LocalTypeInformation get() = _follow() + + // Custom equals / hashcode because otherwise the "follow" lambda makes equality harder to reason about. + override fun equals(other: Any?): Boolean = + other is Cycle && + other.observedType == observedType && + other.typeIdentifier == typeIdentifier + + override fun hashCode(): Int = Objects.hash(observedType, typeIdentifier) + + override fun toString(): String = "Cycle($observedType, $typeIdentifier)" + } + + /** + * May in fact be a more complex class, but is treated as if atomic, i.e. we don't further expand its properties. + */ + data class Opaque(override val observedType: Class<*>, override val typeIdentifier: TypeIdentifier, + private val _expand: () -> LocalTypeInformation) : LocalTypeInformation() { + val expand: LocalTypeInformation get() = _expand() + + // Custom equals / hashcode because otherwise the "expand" lambda makes equality harder to reason about. + override fun equals(other: Any?): Boolean = + other is Cycle && + other.observedType == observedType && + other.typeIdentifier == typeIdentifier + + override fun hashCode(): Int = Objects.hash(observedType, typeIdentifier) + + override fun toString(): String = "Opaque($observedType, $typeIdentifier)" + } + + /** + * Represents a scalar type such as [Int]. + */ + data class Atomic(override val observedType: Class<*>, override val typeIdentifier: TypeIdentifier) : LocalTypeInformation() + + /** + * Represents an array of some other type. + * + * @param componentType The [LocalTypeInformation] for the component type of the array (e.g. [Int], if the type is [IntArray]) + */ + data class AnArray(override val observedType: Type, override val typeIdentifier: TypeIdentifier, val componentType: LocalTypeInformation) : LocalTypeInformation() + + /** + * Represents an `enum` + * + * @param members The string names of the members of the enum. + * @param superclass [LocalTypeInformation] for the superclass of the type (as enums can inherit from other types). + * @param interfaces [LocalTypeInformation] for each interface implemented by the type. + */ + data class AnEnum( + override val observedType: Class<*>, + override val typeIdentifier: TypeIdentifier, + val members: List, + val interfaces: List): LocalTypeInformation() + + /** + * Represents a type whose underlying class is an interface. + * + * @param properties [LocalPropertyInformation] for the read-only properties of the interface, i.e. its "getter" methods. + * @param interfaces [LocalTypeInformation] for the interfaces extended by this interface. + * @param typeParameters [LocalTypeInformation] for the resolved type parameters of the type. + */ + data class AnInterface( + override val observedType: Type, + override val typeIdentifier: TypeIdentifier, + val properties: Map, + val interfaces: List, + val typeParameters: List) : LocalTypeInformation() + + /** + * Represents a type whose underlying class is abstract. + * + * @param properties [LocalPropertyInformation] for the read-only properties of the interface, i.e. its "getter" methods. + * @param superclass [LocalTypeInformation] for the superclass of the underlying class of this type. + * @param interfaces [LocalTypeInformation] for the interfaces extended by this interface. + * @param typeParameters [LocalTypeInformation] for the resolved type parameters of the type. + */ + data class Abstract( + override val observedType: Type, + override val typeIdentifier: TypeIdentifier, + val properties: Map, + val superclass: LocalTypeInformation, + val interfaces: List, + val typeParameters: List) : LocalTypeInformation() + + /** + * Represents a type which has only a single instantiation, e.g. a Kotlin `object`. + * + * @param superclass [LocalTypeInformation] for the superclass of the underlying class of this type. + * @param interfaces [LocalTypeInformation] for the interfaces extended by this interface. + */ + data class Singleton(override val observedType: Type, override val typeIdentifier: TypeIdentifier, val superclass: LocalTypeInformation, val interfaces: List) : LocalTypeInformation() + + /** + * Represents a type whose instances can be reversibly decomposed into dictionaries of typed values. + * + * @param constructor [LocalConstructorInformation] for the constructor used when building instances of this type + * out of dictionaries of typed values. + * @param properties [LocalPropertyInformation] for the properties of the interface. + * @param superclass [LocalTypeInformation] for the superclass of the underlying class of this type. + * @param interfaces [LocalTypeInformation] for the interfaces extended by this interface. + * @param typeParameters [LocalTypeInformation] for the resolved type parameters of the type. + */ + data class Composable( + override val observedType: Type, + override val typeIdentifier: TypeIdentifier, + val constructor: LocalConstructorInformation, + val evolverConstructors: List, + val properties: Map, + val superclass: LocalTypeInformation, + val interfaces: List, + val typeParameters: List) : LocalTypeInformation() + + /** + * Represents a type whose instances may have observable properties (represented by "getter" methods), but for which + * we do not possess a method (such as a unique "deserialization constructor" satisfied by these properties) for + * creating a new instance from a dictionary of property values. + * + * @param constructor [LocalConstructorInformation] for the constructor of this type, if there is one. + * @param properties [LocalPropertyInformation] for the properties of the interface. + * @param superclass [LocalTypeInformation] for the superclass of the underlying class of this type. + * @param interfaces [LocalTypeInformation] for the interfaces extended by this interface. + * @param typeParameters [LocalTypeInformation] for the resolved type parameters of the type. + */ + data class NonComposable( + override val observedType: Type, + override val typeIdentifier: TypeIdentifier, + val constructor: LocalConstructorInformation?, + val properties: Map, + val superclass: LocalTypeInformation, + val interfaces: List, + val typeParameters: List) : LocalTypeInformation() + + /** + * Represents a type whose underlying class is a collection class such as [List] with a single type parameter. + * + * @param elementType [LocalTypeInformation] for the resolved type parameter of the type, i.e. the type of its + * elements. [Unknown] if the type is erased. + */ + data class ACollection(override val observedType: Type, override val typeIdentifier: TypeIdentifier, val elementType: LocalTypeInformation) : LocalTypeInformation() { + val isErased: Boolean get() = typeIdentifier is TypeIdentifier.Erased + + fun withElementType(parameter: LocalTypeInformation): ACollection = when(typeIdentifier) { + is TypeIdentifier.Erased -> { + val unerasedType = typeIdentifier.toParameterized(listOf(parameter.typeIdentifier)) + ACollection( + unerasedType.getLocalType(this::class.java.classLoader), + unerasedType, + parameter) + } + is TypeIdentifier.Parameterised -> { + val reparameterizedType = typeIdentifier.copy(parameters = listOf(parameter.typeIdentifier)) + ACollection( + reparameterizedType.getLocalType(this::class.java.classLoader), + reparameterizedType, + parameter + ) + } + else -> throw IllegalStateException("Cannot parameterise $this") + } + } + + /** + * Represents a type whose underlying class is a map class such as [Map] with two type parameters. + * + * @param keyType [LocalTypeInformation] for the first resolved type parameter of the type, i.e. the type of its + * keys. [Unknown] if the type is erased. + * @param valueType [LocalTypeInformation] for the second resolved type parameter of the type, i.e. the type of its + * values. [Unknown] if the type is erased. + */ + data class AMap(override val observedType: Type, override val typeIdentifier: TypeIdentifier, + val keyType: LocalTypeInformation, val valueType: LocalTypeInformation) : LocalTypeInformation() { + val isErased: Boolean get() = typeIdentifier is TypeIdentifier.Erased + + fun withParameters(keyType: LocalTypeInformation, valueType: LocalTypeInformation): AMap = when(typeIdentifier) { + is TypeIdentifier.Erased -> { + val unerasedType = typeIdentifier.toParameterized(listOf(keyType.typeIdentifier, valueType.typeIdentifier)) + AMap( + unerasedType.getLocalType(this::class.java.classLoader), + unerasedType, + keyType, valueType) + } + is TypeIdentifier.Parameterised -> { + val reparameterizedType = typeIdentifier.copy(parameters = listOf(keyType.typeIdentifier, valueType.typeIdentifier)) + AMap( + reparameterizedType.getLocalType(this::class.java.classLoader), + reparameterizedType, + keyType, valueType + ) + } + else -> throw IllegalStateException("Cannot parameterise $this") + } + } +} + +/** + * Represents information about a constructor. + */ +data class LocalConstructorInformation( + val observedMethod: KFunction, + val parameters: List) { + val hasParameters: Boolean get() = parameters.isNotEmpty() +} + +/** + * Represents information about a constructor that is specifically to be used for evolution, and is potentially matched + * with a different set of properties to the regular constructor. + */ +data class EvolverConstructorInformation( + val constructor: LocalConstructorInformation, + val properties: Map) + +/** + * Represents information about a constructor parameter + */ +data class LocalConstructorParameterInformation( + val name: String, + val type: LocalTypeInformation, + val isMandatory: Boolean) + +private data class LocalTypeInformationPrettyPrinter(private val simplifyClassNames: Boolean, private val indent: Int = 0) { + + fun prettyPrint(typeInformation: LocalTypeInformation): String = + with(typeInformation) { + when (this) { + is LocalTypeInformation.Abstract -> + typeIdentifier.prettyPrint() + + printInheritsFrom(interfaces, superclass) + + indentAnd { printProperties(properties) } + is LocalTypeInformation.AnInterface -> + typeIdentifier.prettyPrint() + printInheritsFrom(interfaces) + is LocalTypeInformation.Composable -> typeIdentifier.prettyPrint() + + printConstructor(constructor) + + printInheritsFrom(interfaces, superclass) + + indentAnd { printProperties(properties) } + else -> typeIdentifier.prettyPrint() + } + } + + private fun printConstructor(constructor: LocalConstructorInformation) = + constructor.parameters.joinToString(", ", "(", ")") { + it.name + + ": " + it.type.typeIdentifier.prettyPrint(simplifyClassNames) + + (if (!it.isMandatory) "?" else "") + } + + private fun printInheritsFrom(interfaces: List, superclass: LocalTypeInformation? = null): String { + val parents = if (superclass == null || superclass == LocalTypeInformation.Top) interfaces.asSequence() + else sequenceOf(superclass) + interfaces.asSequence() + return if (!parents.iterator().hasNext()) "" + else parents.joinToString(", ", ": ", "") { it.typeIdentifier.prettyPrint(simplifyClassNames) } + } + + private fun printProperties(properties: Map) = + properties.entries.asSequence().sortedBy { it.key }.joinToString("\n", "\n", "") { + it.prettyPrint() + } + + private fun Map.Entry.prettyPrint(): String = + " ".repeat(indent) + key + + (if(!value.isMandatory) " (optional)" else "") + + (if (value.isCalculated) " (calculated)" else "") + + ": " + value.type.prettyPrint(simplifyClassNames) + + private inline fun indentAnd(block: LocalTypeInformationPrettyPrinter.() -> String) = + copy(indent = indent + 1).block() +} + diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeInformationBuilder.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeInformationBuilder.kt new file mode 100644 index 0000000000..88df121a43 --- /dev/null +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeInformationBuilder.kt @@ -0,0 +1,410 @@ +package net.corda.serialization.internal.model + +import net.corda.core.internal.isAbstractClass +import net.corda.core.internal.isConcreteClass +import net.corda.core.internal.kotlinObjectInstance +import net.corda.core.serialization.ConstructorForDeserialization +import net.corda.core.serialization.DeprecatedConstructorForDeserialization +import net.corda.core.utilities.contextLogger +import net.corda.serialization.internal.amqp.* +import java.io.NotSerializableException +import java.lang.reflect.Method +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type +import java.util.* +import kotlin.collections.LinkedHashMap +import kotlin.reflect.KFunction +import kotlin.reflect.full.findAnnotation +import kotlin.reflect.full.memberProperties +import kotlin.reflect.full.primaryConstructor +import kotlin.reflect.jvm.internal.KotlinReflectionInternalError +import kotlin.reflect.jvm.isAccessible +import kotlin.reflect.jvm.javaConstructor +import kotlin.reflect.jvm.javaGetter +import kotlin.reflect.jvm.javaType + +/** + * Provides the logic for building instances of [LocalTypeInformation] by reflecting over local [Type]s. + * + * @param lookup The [LocalTypeLookup] to use to locate and register constructed [LocalTypeInformation]. + * @param resolutionContext The [Type] to use when attempting to resolve type variables. + * @param visited The [Set] of [TypeIdentifier]s already visited while building information for a given [Type]. Note that + * this is not a [MutableSet], as we want to be able to backtrack while traversing through the graph of related types, and + * will find it useful to revert to earlier states of knowledge about which types have been visited on a given branch. + */ +internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup, val resolutionContext: Type? = null, val visited: Set = emptySet()) { + + companion object { + private val logger = contextLogger() + } + + /** + * Recursively build [LocalTypeInformation] for the given [Type] and [TypeIdentifier] + */ + fun build(type: Type, typeIdentifier: TypeIdentifier): LocalTypeInformation = + if (typeIdentifier in visited) LocalTypeInformation.Cycle(type, typeIdentifier) { + LocalTypeInformationBuilder(lookup, resolutionContext).build(type, typeIdentifier) + } + else lookup.findOrBuild(type, typeIdentifier) { isOpaque -> + copy(visited = visited + typeIdentifier).buildIfNotFound(type, typeIdentifier, isOpaque) + } + + private fun resolveAndBuild(type: Type): LocalTypeInformation { + val resolved = type.resolveAgainstContext() + return build(resolved, TypeIdentifier.forGenericType(resolved, resolutionContext + ?: type)) + } + + private fun Type.resolveAgainstContext(): Type = + if (resolutionContext == null) this else resolveAgainst(resolutionContext) + + private fun buildIfNotFound(type: Type, typeIdentifier: TypeIdentifier, isOpaque: Boolean): LocalTypeInformation { + val rawType = type.asClass() + return when (typeIdentifier) { + is TypeIdentifier.TopType -> LocalTypeInformation.Top + is TypeIdentifier.UnknownType -> LocalTypeInformation.Unknown + is TypeIdentifier.Unparameterised, + is TypeIdentifier.Erased -> buildForClass(rawType, typeIdentifier, isOpaque) + is TypeIdentifier.ArrayOf -> { + LocalTypeInformation.AnArray( + type, + typeIdentifier, + resolveAndBuild(type.componentType())) + } + is TypeIdentifier.Parameterised -> buildForParameterised(rawType, type as ParameterizedType, typeIdentifier, isOpaque) + } + } + + private fun buildForClass(type: Class<*>, typeIdentifier: TypeIdentifier, isOpaque: Boolean): LocalTypeInformation = withContext(type) { + when { + Collection::class.java.isAssignableFrom(type) && + !EnumSet::class.java.isAssignableFrom(type) -> LocalTypeInformation.ACollection(type, typeIdentifier, LocalTypeInformation.Unknown) + Map::class.java.isAssignableFrom(type) -> LocalTypeInformation.AMap(type, typeIdentifier, LocalTypeInformation.Unknown, LocalTypeInformation.Unknown) + type.kotlin.javaPrimitiveType != null -> LocalTypeInformation.Atomic(type.kotlin.javaPrimitiveType!!, typeIdentifier) + type.isEnum -> LocalTypeInformation.AnEnum( + type, + typeIdentifier, + type.enumConstants.map { it.toString() }, + buildInterfaceInformation(type)) + type.kotlinObjectInstance != null -> LocalTypeInformation.Singleton( + type, + typeIdentifier, + buildSuperclassInformation(type), + buildInterfaceInformation(type)) + type.isInterface -> buildInterface(type, typeIdentifier, emptyList()) + type.isAbstractClass -> buildAbstract(type, typeIdentifier, emptyList()) + else -> when { + isOpaque -> LocalTypeInformation.Opaque(type, typeIdentifier) { + buildNonAtomic(type, type, typeIdentifier, emptyList()) + } + else -> buildNonAtomic(type, type, typeIdentifier, emptyList()) + } + } + } + + private fun buildForParameterised( + rawType: Class<*>, + type: ParameterizedType, + typeIdentifier: TypeIdentifier.Parameterised, + isOpaque: Boolean): LocalTypeInformation = withContext(type) { + when { + Collection::class.java.isAssignableFrom(rawType) && + !EnumSet::class.java.isAssignableFrom(rawType) -> + LocalTypeInformation.ACollection(type, typeIdentifier, buildTypeParameterInformation(type)[0]) + Map::class.java.isAssignableFrom(rawType) -> { + val (keyType, valueType) = buildTypeParameterInformation(type) + LocalTypeInformation.AMap(type, typeIdentifier, keyType, valueType) + } + rawType.isInterface -> buildInterface(type, typeIdentifier, buildTypeParameterInformation(type)) + rawType.isAbstractClass -> buildAbstract(type, typeIdentifier, buildTypeParameterInformation(type)) + else -> when { + isOpaque -> LocalTypeInformation.Opaque(rawType, typeIdentifier) { + buildNonAtomic(rawType, type, typeIdentifier, buildTypeParameterInformation(type)) + } + else -> buildNonAtomic(rawType, type, typeIdentifier, buildTypeParameterInformation(type)) + } + } + } + + private fun buildAbstract(type: Type, typeIdentifier: TypeIdentifier, + typeParameters: List): LocalTypeInformation.Abstract = + LocalTypeInformation.Abstract( + type, + typeIdentifier, + buildReadOnlyProperties(type.asClass()), + buildSuperclassInformation(type), + buildInterfaceInformation(type), + typeParameters) + + private fun buildInterface(type: Type, typeIdentifier: TypeIdentifier, + typeParameters: List): LocalTypeInformation.AnInterface = + LocalTypeInformation.AnInterface( + type, + typeIdentifier, + buildReadOnlyProperties(type.asClass()), + buildInterfaceInformation(type), + typeParameters) + + private inline fun withContext(newContext: Type, block: LocalTypeInformationBuilder.() -> T): T = + copy(resolutionContext = newContext).run(block) + + /** + * Build a non-atomic type, which is either [Composable] or [NonComposable]. + * + * Composability is a transitive property: a type is [Composable] iff it has a unique deserialization constructor _and_ + * all of its property types are also [Composable]. If not, the type is [NonComposable], meaning we cannot deserialize + * it without a custom serializer (in which case it should normally have been flagged as [Opaque]). + * + * Rather than throwing an exception if a type is [NonComposable], we capture its type information so that it can + * still be used to _serialize_ values, or as the basis for deciding on an evolution strategy. + */ + private fun buildNonAtomic(rawType: Class<*>, type: Type, typeIdentifier: TypeIdentifier, typeParameterInformation: List): LocalTypeInformation { + val superclassInformation = buildSuperclassInformation(type) + val interfaceInformation = buildInterfaceInformation(type) + val observedConstructor = constructorForDeserialization(type) + + if (observedConstructor == null) { + logger.warn("No unique deserialisation constructor found for class $rawType, type is marked as non-composable") + return LocalTypeInformation.NonComposable(type, typeIdentifier, null, buildReadOnlyProperties(rawType), + superclassInformation, interfaceInformation, typeParameterInformation) + } + + val constructorInformation = buildConstructorInformation(type, observedConstructor) + val properties = buildObjectProperties(rawType, constructorInformation) + + val hasNonComposableProperties = properties.values.any { it.type is LocalTypeInformation.NonComposable } + + if (!propertiesSatisfyConstructor(constructorInformation, properties) || hasNonComposableProperties) { + if (hasNonComposableProperties) { + logger.warn("Type ${type.typeName} has non-composable properties and has been marked as non-composable") + } else { + logger.warn("Properties of type ${type.typeName} do not satisfy its constructor, type has been marked as non-composable") + } + return LocalTypeInformation.NonComposable(type, typeIdentifier, constructorInformation, properties, superclassInformation, + interfaceInformation, typeParameterInformation) + } + + val evolverConstructors = evolverConstructors(type).map { ctor -> + val constructorInformation = buildConstructorInformation(type, ctor) + val evolverProperties = buildObjectProperties(rawType, constructorInformation) + EvolverConstructorInformation(constructorInformation, evolverProperties) + } + + return LocalTypeInformation.Composable(type, typeIdentifier, constructorInformation, evolverConstructors, properties, + superclassInformation, interfaceInformation, typeParameterInformation) + } + + // Can we supply all of the mandatory constructor parameters using values addressed by readable properties? + private fun propertiesSatisfyConstructor(constructorInformation: LocalConstructorInformation, properties: Map): Boolean { + if (!constructorInformation.hasParameters) return true + + val indicesAddressedByProperties = properties.values.asSequence().mapNotNull { + when (it) { + is LocalPropertyInformation.ConstructorPairedProperty -> it.constructorSlot.parameterIndex + is LocalPropertyInformation.PrivateConstructorPairedProperty -> it.constructorSlot.parameterIndex + else -> null + } + }.toSet() + + return (0 until constructorInformation.parameters.size).none { index -> + constructorInformation.parameters[index].isMandatory && index !in indicesAddressedByProperties + } + } + + private fun buildSuperclassInformation(type: Type): LocalTypeInformation = + resolveAndBuild(type.asClass().genericSuperclass) + + private fun buildInterfaceInformation(type: Type) = + type.allInterfaces.asSequence().mapNotNull { + if (it == type) return@mapNotNull null + resolveAndBuild(it) + }.toList() + + private val Type.allInterfaces: Set get() = exploreType(this) + + private fun exploreType(type: Type, interfaces: MutableSet = LinkedHashSet()): MutableSet { + val clazz = type.asClass() + + if (clazz.isInterface) { + // Ignore classes we've already seen, and stop exploring once we reach an excluded type. + if (clazz in interfaces || lookup.isExcluded(clazz)) return interfaces + else interfaces += type + } + + clazz.genericInterfaces.forEach { exploreType(it.resolveAgainstContext(), interfaces) } + if (clazz.genericSuperclass != null) exploreType(clazz.genericSuperclass.resolveAgainstContext(), interfaces) + + return interfaces + } + + private fun buildReadOnlyProperties(rawType: Class<*>): Map = + rawType.propertyDescriptors().asSequence().mapNotNull { (name, descriptor) -> + if (descriptor.field == null || descriptor.getter == null) null + else { + val paramType = (descriptor.getter.genericReturnType).resolveAgainstContext() + val paramTypeInformation = build(paramType, TypeIdentifier.forGenericType(paramType, resolutionContext + ?: rawType)) + val isMandatory = paramType.asClass().isPrimitive || !descriptor.getter.returnsNullable() + name to LocalPropertyInformation.ReadOnlyProperty(descriptor.getter, paramTypeInformation, isMandatory) + } + }.sortedBy { (name, _) -> name }.toMap(LinkedHashMap()) + + private fun buildObjectProperties(rawType: Class<*>, constructorInformation: LocalConstructorInformation): Map = + (calculatedProperties(rawType) + nonCalculatedProperties(rawType, constructorInformation)) + .sortedBy { (name, _) -> name } + .toMap(LinkedHashMap()) + + private fun nonCalculatedProperties(rawType: Class<*>, constructorInformation: LocalConstructorInformation): Sequence> = + if (constructorInformation.hasParameters) getConstructorPairedProperties(constructorInformation, rawType) + else getterSetterProperties(rawType) + + private fun getConstructorPairedProperties(constructorInformation: LocalConstructorInformation, rawType: Class<*>): Sequence> { + val constructorParameterIndices = constructorInformation.parameters.asSequence().mapIndexed { index, parameter -> + parameter.name to index + }.toMap() + + return rawType.propertyDescriptors().asSequence().mapNotNull { (name, descriptor) -> + val property = makeConstructorPairedProperty(constructorParameterIndices, name, descriptor, constructorInformation) + if (property == null) null else name to property + } + } + + private fun makeConstructorPairedProperty(constructorParameterIndices: Map, + name: String, + descriptor: PropertyDescriptor, + constructorInformation: LocalConstructorInformation): LocalPropertyInformation? { + val constructorIndex = constructorParameterIndices[name] ?: + // In some very rare cases we have a constructor parameter matched by a getter with no backing field, + // and cannot infer whether the property name should be capitalised or not. + constructorParameterIndices[name.decapitalize()] ?: return null + + if (descriptor.getter == null) { + if (descriptor.field == null) return null + val paramType = descriptor.field.genericType + val paramTypeInformation = resolveAndBuild(paramType) + + return LocalPropertyInformation.PrivateConstructorPairedProperty( + descriptor.field, + ConstructorSlot(constructorIndex, constructorInformation), + paramTypeInformation, + constructorInformation.parameters[constructorIndex].isMandatory) + } + + val paramType = descriptor.getter.genericReturnType + val paramTypeInformation = resolveAndBuild(paramType) + + return LocalPropertyInformation.ConstructorPairedProperty( + descriptor.getter, + ConstructorSlot(constructorIndex, constructorInformation), + paramTypeInformation, + descriptor.getter.returnType.isPrimitive || + !descriptor.getter.returnsNullable()) + } + + private fun getterSetterProperties(rawType: Class<*>): Sequence> = + rawType.propertyDescriptors().asSequence().mapNotNull { (name, descriptor) -> + if (descriptor.getter == null || descriptor.setter == null || descriptor.field == null) null + else { + val paramType = descriptor.getter.genericReturnType + val paramTypeInformation = resolveAndBuild(paramType) + val isMandatory = paramType.asClass().isPrimitive || !descriptor.getter.returnsNullable() + + name to LocalPropertyInformation.GetterSetterProperty( + descriptor.getter, + descriptor.setter, + paramTypeInformation, + isMandatory) + } + } + + private fun calculatedProperties(rawType: Class<*>): Sequence> = + rawType.calculatedPropertyDescriptors().asSequence().map { (name, v) -> + val paramType = v.getter!!.genericReturnType + val paramTypeInformation = resolveAndBuild(paramType) + val isMandatory = paramType.asClass().isPrimitive || !v.getter.returnsNullable() + + name to LocalPropertyInformation.CalculatedProperty(v.getter, paramTypeInformation, isMandatory) + } + + private fun buildTypeParameterInformation(type: ParameterizedType): List = + type.actualTypeArguments.map { + resolveAndBuild(it) + } + + private fun buildConstructorInformation(type: Type, observedConstructor: KFunction): LocalConstructorInformation { + if (observedConstructor.javaConstructor?.parameters?.getOrNull(0)?.name == "this$0") + throw NotSerializableException("Type '${type.typeName} has synthetic fields and is likely a nested inner class.") + + return LocalConstructorInformation(observedConstructor, observedConstructor.parameters.map { + val parameterType = it.type.javaType + LocalConstructorParameterInformation( + it.name ?: throw IllegalStateException("Unnamed parameter in constructor $observedConstructor"), + resolveAndBuild(parameterType), + parameterType.asClass().isPrimitive || !it.type.isMarkedNullable) + }) + } +} + +private fun Method.returnsNullable(): Boolean = try { + val returnTypeString = this.declaringClass.kotlin.memberProperties.firstOrNull { + it.javaGetter == this + }?.returnType?.toString() ?: "?" + + returnTypeString.endsWith('?') || returnTypeString.endsWith('!') +} catch (e: KotlinReflectionInternalError) { + // This might happen for some types, e.g. kotlin.Throwable? - the root cause of the issue + // is: https://youtrack.jetbrains.com/issue/KT-13077 + // TODO: Revisit this when Kotlin issue is fixed. + + true +} + +/** + * Code for finding the unique constructor we will use for deserialization. + * + * If any constructor is uniquely annotated with [@ConstructorForDeserialization], then that constructor is chosen. + * An error is reported if more than one constructor is annotated. + * + * Otherwise, if there is a Kotlin primary constructor, it selects that, and if not it selects either the unique + * constructor or, if there are two and one is the default no-argument constructor, the non-default constructor. + */ +private fun constructorForDeserialization(type: Type): KFunction? { + val clazz = type.asClass() + if (!clazz.isConcreteClass || clazz.isSynthetic) return null + + val kotlinCtors = clazz.kotlin.constructors + + val annotatedCtors = kotlinCtors.filter { it.findAnnotation() != null } + if (annotatedCtors.size > 1) return null + if (annotatedCtors.size == 1) return annotatedCtors.first().apply { isAccessible = true } + + val defaultCtor = kotlinCtors.firstOrNull { it.parameters.isEmpty() } + val nonDefaultCtors = kotlinCtors.filter { it != defaultCtor } + + val preferredCandidate = clazz.kotlin.primaryConstructor ?: + when(nonDefaultCtors.size) { + 1 -> nonDefaultCtors.first() + 0 -> defaultCtor + else -> null + } ?: return null + + return try { + preferredCandidate.apply { isAccessible = true } + } catch (e: SecurityException) { + null + } +} + +private fun evolverConstructors(type: Type): List> { + val clazz = type.asClass() + if (!clazz.isConcreteClass || clazz.isSynthetic) return emptyList() + + return clazz.kotlin.constructors.asSequence() + .mapNotNull { + val version = it.findAnnotation()?.version + if (version == null) null else version to it + } + .sortedBy { (version, ctor) -> version } + .map { (version, ctor) -> ctor.apply { isAccessible = true} } + .toList() +} \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeModel.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeModel.kt new file mode 100644 index 0000000000..9acdacb71e --- /dev/null +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeModel.kt @@ -0,0 +1,91 @@ +package net.corda.serialization.internal.model + +import net.corda.core.serialization.ClassWhitelist +import net.corda.serialization.internal.amqp.* +import java.lang.reflect.* + +/** + * Provides a means for looking up [LocalTypeInformation] by [Type] and [TypeIdentifier], falling back to building it + * if the lookup can't supply it. + * + * The purpose of this class is to make a registry of [LocalTypeInformation] usable by a [LocalTypeInformationBuilder] that + * recursively builds [LocalTypeInformation] for all of the types visible by traversing the DAG of related types of a given + * [Type]. + */ +interface LocalTypeLookup { + + /** + * Either return the [LocalTypeInformation] held in the registry for the given [Type] and [TypeIdentifier] or, if + * no such information is registered, call the supplied builder to construct the type information, add it to the + * registry and then return it. + */ + fun findOrBuild(type: Type, typeIdentifier: TypeIdentifier, builder: (Boolean) -> LocalTypeInformation): LocalTypeInformation + + /** + * Indicates whether a type should be excluded from lists of interfaces associated with inspected types, i.e. + * because it is not whitelisted. + */ + fun isExcluded(type: Type): Boolean +} + +/** + * A [LocalTypeModel] maintains a registry of [LocalTypeInformation] for all [Type]s which have been observed within a + * given classloader context. + */ +interface LocalTypeModel { + /** + * Look for a [Type] in the registry, and return its associated [LocalTypeInformation] if found. If the [Type] is + * not in the registry, build [LocalTypeInformation] for that type, using this [LocalTypeModel] as the [LocalTypeLookup] + * for recursively resolving dependencies, place it in the registry, and return it. + * + * @param type The [Type] to get [LocalTypeInformation] for. + */ + fun inspect(type: Type): LocalTypeInformation + + /** + * Get [LocalTypeInformation] directly from the registry by [TypeIdentifier], returning null if no type information + * is registered for that identifier. + */ + operator fun get(typeIdentifier: TypeIdentifier): LocalTypeInformation? +} + +/** + * A [LocalTypeLookup] that is configurable with [LocalTypeModelConfiguration], which controls which types are seen as "opaque" + * and which are "excluded" (see docs for [LocalTypeModelConfiguration] for explanation of these terms. + * + * @param typeModelConfiguration Configuration controlling the behaviour of the [LocalTypeModel]'s type inspection. + */ +class ConfigurableLocalTypeModel(private val typeModelConfiguration: LocalTypeModelConfiguration): LocalTypeModel, LocalTypeLookup { + + private val typeInformationCache = DefaultCacheProvider.createCache() + + override fun isExcluded(type: Type): Boolean = typeModelConfiguration.isExcluded(type) + + override fun inspect(type: Type): LocalTypeInformation = LocalTypeInformation.forType(type, this) + + override fun findOrBuild(type: Type, typeIdentifier: TypeIdentifier, builder: (Boolean) -> LocalTypeInformation): LocalTypeInformation = + this[typeIdentifier] ?: builder(typeModelConfiguration.isOpaque(type)).apply { + typeInformationCache.putIfAbsent(typeIdentifier, this) + } + + override operator fun get(typeIdentifier: TypeIdentifier): LocalTypeInformation? = typeInformationCache[typeIdentifier] +} + +/** + * Configuration which controls how a [LocalTypeModel] inspects classes to build [LocalTypeInformation]. + */ +interface LocalTypeModelConfiguration { + /** + * [Type]s which are flagged as "opaque" are converted into instances of [LocalTypeInformation.Opaque] without + * further inspection - the type model doesn't attempt to inspect their superclass/interface hierarchy, locate + * constructors or enumerate their properties. Usually this will be because the type is handled by a custom + * serializer, so we don't need detailed information about it to help us build one. + */ + fun isOpaque(type: Type): Boolean + + /** + * [Type]s which are excluded are silently omitted from the superclass/interface hierarchy of other types' + * [LocalTypeInformation], usually because they are not included in a whitelist. + */ + fun isExcluded(type: Type): Boolean +} diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/model/RemoteTypeCarpenter.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/model/RemoteTypeCarpenter.kt index f9e1341a23..75e4b9363d 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/model/RemoteTypeCarpenter.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/model/RemoteTypeCarpenter.kt @@ -5,8 +5,6 @@ import net.corda.serialization.internal.carpenter.* import java.io.NotSerializableException import java.lang.reflect.Type -typealias PropertyName = String - /** * Constructs [Type]s using [RemoteTypeInformation]. */ diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/model/LocalTypeModelTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/model/LocalTypeModelTests.kt new file mode 100644 index 0000000000..d14646337f --- /dev/null +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/model/LocalTypeModelTests.kt @@ -0,0 +1,166 @@ +package net.corda.serialization.internal.model + +import com.google.common.reflect.TypeToken +import net.corda.core.serialization.SerializableCalculatedProperty +import net.corda.serialization.internal.AllWhitelist +import net.corda.serialization.internal.amqp.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import java.lang.reflect.Type +import java.time.LocalDateTime +import java.util.* + +class LocalTypeModelTests { + + private val descriptorBasedSerializerRegistry = DefaultDescriptorBasedSerializerRegistry() + private val customSerializerRegistry: CustomSerializerRegistry = CachingCustomSerializerRegistry(descriptorBasedSerializerRegistry) + private val model = ConfigurableLocalTypeModel(WhitelistBasedTypeModelConfiguration(AllWhitelist, customSerializerRegistry)) + + interface CollectionHolder { + val list: List + val map: Map + val array: Array> + } + + open class StringKeyedCollectionHolder(override val list: List, override val map: Map, override val array: Array>) : CollectionHolder + + class StringCollectionHolder(list: List, map: Map, array: Array>) : StringKeyedCollectionHolder(list, map, array) + + @Suppress("unused") + class Nested( + val collectionHolder: StringKeyedCollectionHolder?, + private val intArray: IntArray, + optionalParam: Short?) + + // This can't be treated as a composable type, because the [intArray] parameter is mandatory but we have no readable + // field or property to populate it from. + @Suppress("unused") + class NonComposableNested(val collectionHolder: StringKeyedCollectionHolder?, intArray: IntArray) + + @Test + fun `Primitives and collections`() { + assertInformation>("CollectionHolder") + + assertInformation>(""" + StringKeyedCollectionHolder(list: List, map: Map, array: List[]): CollectionHolder + array: List[] + list: List + map: Map + """) + + assertInformation(""" + StringCollectionHolder(list: List, map: Map, array: List[]): StringKeyedCollectionHolder, CollectionHolder + array: List[] + list: List + map: Map + """) + + assertInformation(""" + Nested(collectionHolder: StringKeyedCollectionHolder?, intArray: int[], optionalParam: Short?) + collectionHolder (optional): StringKeyedCollectionHolder(list: List, map: Map, array: List[]): CollectionHolder + array: List[] + list: List + map: Map + intArray: int[] + """) + + assertInformation("NonComposableNested") + } + + interface SuperSuper { + val a: A + val b: B + } + + interface Super : SuperSuper { + val c: List + } + + abstract class Abstract(override val a: Array, override val b: Double) : Super> + + class Concrete(a: Array, b: Double, override val c: List>, val d: Int) : Abstract(a, b) + + @Test + fun `interfaces and superclasses`() { + assertInformation>("SuperSuper") + assertInformation>("Super: SuperSuper") + assertInformation>(""" + Abstract: Super, SuperSuper + a: LocalDateTime[] + b: Double + """) + assertInformation(""" + Concrete(a: Integer[], b: double, c: List, d: int): Abstract, Super, SuperSuper + a: Integer[] + b: Double + c: List + d: int + """) + } + + interface OldStylePojo { + var a: A? + var b: String + @get:SerializableCalculatedProperty + val c: String + } + + class OldStylePojoImpl : OldStylePojo { + override var a: IntArray? = null + override var b: String = "" + override val c: String = a.toString() + b + } + + @Test + fun `getter setter and calculated properties`() { + assertInformation(""" + OldStylePojoImpl(): OldStylePojo + a (optional): int[] + b: String + c (calculated): String + """) + } + + class AliasingOldStylePojoImpl(override var a: String?, override var b: String, override val c: String): OldStylePojo + + @Test + fun `calculated properties aliased by fields in implementing classes`() { + assertInformation(""" + AliasingOldStylePojoImpl(a: String?, b: String, c: String): OldStylePojo + a (optional): String + b: String + c: String + """) + } + + class TransitivelyNonComposable(val a: String, val b: Exception) + + @Test + fun `non-composable types`() { + val serializerRegistry = object: CustomSerializerRegistry { + override fun register(customSerializer: CustomSerializer) {} + + override fun registerExternal(customSerializer: CorDappCustomSerializer) {} + + override fun findCustomSerializer(clazz: Class<*>, declaredType: Type): AMQPSerializer? = null + } + val modelWithoutOpacity = ConfigurableLocalTypeModel(WhitelistBasedTypeModelConfiguration(AllWhitelist, serializerRegistry) ) + assertTrue(modelWithoutOpacity.inspect(typeOf()) is LocalTypeInformation.NonComposable) + assertTrue(modelWithoutOpacity.inspect(typeOf()) is LocalTypeInformation.NonComposable) + } + + private inline fun assertInformation(expected: String) { + assertEquals(expected.trimIndent(), model.inspect(typeOf()).prettyPrint()) + } + + /** + * Handy for seeing what the inspector/pretty printer actually outputs for a type + */ + @Suppress("unused") + private inline fun printInformation() { + println(model.inspect(typeOf()).prettyPrint()) + } + + private inline fun typeOf(): Type = object : TypeToken() {}.type +} \ No newline at end of file From b9f2401123312812c0a54d975aaf3a3039f6e0e3 Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Mon, 19 Nov 2018 16:47:57 +0000 Subject: [PATCH 14/19] Removed errorCode for raw configuration parsing errors. --- node/src/main/kotlin/net/corda/node/NodeCmdLineOptions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/src/main/kotlin/net/corda/node/NodeCmdLineOptions.kt b/node/src/main/kotlin/net/corda/node/NodeCmdLineOptions.kt index 1141c20297..37293ddf51 100644 --- a/node/src/main/kotlin/net/corda/node/NodeCmdLineOptions.kt +++ b/node/src/main/kotlin/net/corda/node/NodeCmdLineOptions.kt @@ -75,7 +75,7 @@ open class SharedNodeCmdLineOptions { errors.forEach { error -> when (error) { is ConfigException.IO -> logger.error(configFileNotFoundMessage(configFile)) - else -> logger.error(error.message, error) + else -> logger.error(error.message) } } } From f676941dbfb1b415d29edd4707f7470a8a30425a Mon Sep 17 00:00:00 2001 From: Chris Burlinchon Date: Mon, 19 Nov 2018 17:12:44 +0000 Subject: [PATCH 15/19] Add commons-lang dependency now pulled in --- bridge/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bridge/build.gradle b/bridge/build.gradle index b81818d3b8..f1234f9850 100644 --- a/bridge/build.gradle +++ b/bridge/build.gradle @@ -81,6 +81,8 @@ dependencies { // JAnsi: for drawing things to the terminal in nicely coloured ways. compile "org.fusesource.jansi:jansi:$jansi_version" + + compile "commons-lang:commons-lang:2.6" integrationTestCompile project(':node-driver') integrationTestCompile "org.apache.curator:curator-test:${curator_version}" From cb0091f0973501241e7d354d103ace5ef86f18d5 Mon Sep 17 00:00:00 2001 From: Chris Burlinchon Date: Mon, 19 Nov 2018 17:19:44 +0000 Subject: [PATCH 16/19] Pull out commons lang version --- bridge/build.gradle | 2 +- build.gradle | 1 + core/build.gradle | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bridge/build.gradle b/bridge/build.gradle index f1234f9850..d2a0d01b41 100644 --- a/bridge/build.gradle +++ b/bridge/build.gradle @@ -82,7 +82,7 @@ dependencies { // JAnsi: for drawing things to the terminal in nicely coloured ways. compile "org.fusesource.jansi:jansi:$jansi_version" - compile "commons-lang:commons-lang:2.6" + compile "commons-lang:commons-lang:$commons_lang_version" integrationTestCompile project(':node-driver') integrationTestCompile "org.apache.curator:curator-test:${curator_version}" diff --git a/build.gradle b/build.gradle index 6fd3838406..f24c896583 100644 --- a/build.gradle +++ b/build.gradle @@ -78,6 +78,7 @@ buildscript { ext.class_graph_version = '4.2.12' ext.jcabi_manifests_version = '1.1' ext.picocli_version = '3.8.0' + ext.commons_lang_version = '2.6' // Name of the IntelliJ SDK created for the deterministic Java rt.jar. // ext.deterministic_idea_sdk = '1.8 (Deterministic)' diff --git a/core/build.gradle b/core/build.gradle index 93603e2300..da6913b4ee 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -100,7 +100,7 @@ dependencies { // Apache JEXL: An embeddable expression evaluation library. // This may be temporary until we experiment with other ways to do on-the-fly contract specialisation via an API. compile "org.apache.commons:commons-jexl3:3.0" - compile 'commons-lang:commons-lang:2.6' + compile "commons-lang:commons-lang:$commons_lang_version" // Java ed25519 implementation. See https://github.com/str4d/ed25519-java/ compile "net.i2p.crypto:eddsa:$eddsa_version" From 12b79fc40b815a0817b4691e968f39bd882abf24 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Mon, 19 Nov 2018 18:13:07 +0000 Subject: [PATCH 17/19] Fix merge --- .../main/kotlin/net/corda/flowworker/FlowWorkerServiceHub.kt | 3 ++- .../src/main/kotlin/net/corda/rpcWorker/RpcWorkerServiceHub.kt | 3 ++- node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt | 1 - .../kotlin/net/corda/testing/internal/InternalTestUtils.kt | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/experimental/flow-worker/src/main/kotlin/net/corda/flowworker/FlowWorkerServiceHub.kt b/experimental/flow-worker/src/main/kotlin/net/corda/flowworker/FlowWorkerServiceHub.kt index e7462e69cf..ddc5af5f79 100644 --- a/experimental/flow-worker/src/main/kotlin/net/corda/flowworker/FlowWorkerServiceHub.kt +++ b/experimental/flow-worker/src/main/kotlin/net/corda/flowworker/FlowWorkerServiceHub.kt @@ -132,7 +132,8 @@ class FlowWorkerServiceHub(override val configuration: NodeConfiguration, identityService::wellKnownPartyFromX500Name, identityService::wellKnownPartyFromAnonymous, schemaService, - cacheFactory + cacheFactory, + cordappLoader.appClassLoader ) init { diff --git a/experimental/rpc-worker/src/main/kotlin/net/corda/rpcWorker/RpcWorkerServiceHub.kt b/experimental/rpc-worker/src/main/kotlin/net/corda/rpcWorker/RpcWorkerServiceHub.kt index 7d392b70d3..b905a8c3c2 100644 --- a/experimental/rpc-worker/src/main/kotlin/net/corda/rpcWorker/RpcWorkerServiceHub.kt +++ b/experimental/rpc-worker/src/main/kotlin/net/corda/rpcWorker/RpcWorkerServiceHub.kt @@ -92,7 +92,8 @@ class RpcWorkerServiceHub(override val configuration: NodeConfiguration, identityService::wellKnownPartyFromX500Name, identityService::wellKnownPartyFromAnonymous, schemaService, - cacheFactory + cacheFactory, + cordappLoader.appClassLoader ) init { 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 0e9d7d81e1..22c5785519 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -154,7 +154,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, identityService::wellKnownPartyFromX500Name, identityService::wellKnownPartyFromAnonymous, schemaService, - configuration.dataSourceProperties, cacheFactory, this.cordappLoader.appClassLoader) diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt index 298e95ce82..52ba72d528 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt @@ -173,7 +173,7 @@ fun configureDatabase(hikariProperties: Properties, cacheFactory: NamedCacheFactory = TestingNamedCacheFactory()): CordaPersistence { val isH2Database = isH2Database(hikariProperties.getProperty("dataSource.url", "")) val schemas = if (isH2Database) NodeSchemaService().internalSchemas() else schemaService.schemaOptions.keys - val persistence = createCordaPersistence(databaseConfig, wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous, schemaService, cacheFactory) + val persistence = createCordaPersistence(databaseConfig, wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous, schemaService, cacheFactory, null) persistence.startHikariPool(hikariProperties, databaseConfig, schemas) return persistence } From 1d35c322c947a215f245bc3ee211b26258e406d2 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Mon, 19 Nov 2018 18:30:29 +0000 Subject: [PATCH 18/19] Fix merge --- .../net/corda/node/services/persistence/SchemaMigrationTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/SchemaMigrationTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/SchemaMigrationTest.kt index fb45eb86dd..f1e6a835a5 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/SchemaMigrationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/SchemaMigrationTest.kt @@ -31,7 +31,7 @@ class SchemaMigrationTest { private fun configureDatabase(hikariProperties: Properties, databaseConfig: DatabaseConfig, schemaService: NodeSchemaService = NodeSchemaService()): CordaPersistence = - createCordaPersistence(databaseConfig, { null }, { null }, schemaService, TestingNamedCacheFactory()) + createCordaPersistence(databaseConfig, { null }, { null }, schemaService, TestingNamedCacheFactory(), null) .apply { startHikariPool(hikariProperties, databaseConfig, schemaService.schemaOptions.keys) } @Test From c35dde5e7a550d206fe177c1a997d22c61629b45 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 20 Nov 2018 10:30:20 +0000 Subject: [PATCH 19/19] Revert api-current --- .ci/api-current.txt | 1232 ------------------------------------------- 1 file changed, 1232 deletions(-) diff --git a/.ci/api-current.txt b/.ci/api-current.txt index c44ad203da..0028c8444c 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -1,7 +1,4 @@ @CordaSerializable -public interface net.corda.core.ClientRelevantError -## -@CordaSerializable public class net.corda.core.CordaException extends java.lang.Exception implements net.corda.core.CordaThrowable public () public (String) @@ -67,12 +64,6 @@ public interface net.corda.core.CordaThrowable public @interface net.corda.core.DoNotImplement ## public final class net.corda.core.Utils extends java.lang.Object - @NotNull - public static final net.corda.core.messaging.DataFeed doOnError(net.corda.core.messaging.DataFeed, kotlin.jvm.functions.Function1) - @NotNull - public static final net.corda.core.messaging.DataFeed mapErrors(net.corda.core.messaging.DataFeed, kotlin.jvm.functions.Function1) - @NotNull - public static final rx.Observable mapErrors(rx.Observable, kotlin.jvm.functions.Function1) @NotNull public static final net.corda.core.concurrent.CordaFuture toFuture(rx.Observable) @NotNull @@ -87,7 +78,6 @@ public final class net.corda.core.concurrent.ConcurrencyUtils extends java.lang. @NotNull public static final String shortCircuitedTaskFailedMessage = "Short-circuited task failed:" ## -@CordaSerializable public interface net.corda.core.concurrent.CordaFuture extends java.util.concurrent.Future public abstract void then(kotlin.jvm.functions.Function1, ? extends W>) @NotNull @@ -114,7 +104,6 @@ public final class net.corda.core.context.Actor extends java.lang.Object public int hashCode() @NotNull public static final net.corda.core.context.Actor service(String, net.corda.core.identity.CordaX500Name) - @NotNull public String toString() public static final net.corda.core.context.Actor$Companion Companion ## @@ -133,7 +122,6 @@ public static final class net.corda.core.context.Actor$Id extends java.lang.Obje @NotNull public final String getValue() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -147,7 +135,6 @@ public final class net.corda.core.context.AuthServiceId extends java.lang.Object @NotNull public final String getValue() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -191,7 +178,6 @@ public final class net.corda.core.context.InvocationContext extends java.lang.Ob public static final net.corda.core.context.InvocationContext service(String, net.corda.core.identity.CordaX500Name, net.corda.core.context.Trace, net.corda.core.context.Trace) @NotNull public static final net.corda.core.context.InvocationContext shell(net.corda.core.context.Trace, net.corda.core.context.Trace) - @NotNull public String toString() public static final net.corda.core.context.InvocationContext$Companion Companion ## @@ -227,23 +213,17 @@ public static final class net.corda.core.context.InvocationOrigin$Peer extends n public int hashCode() @NotNull public java.security.Principal principal() - @NotNull public String toString() ## @CordaSerializable public static final class net.corda.core.context.InvocationOrigin$RPC extends net.corda.core.context.InvocationOrigin public (net.corda.core.context.Actor) @NotNull - public final net.corda.core.context.Actor component1() - @NotNull public final net.corda.core.context.InvocationOrigin$RPC copy(net.corda.core.context.Actor) public boolean equals(Object) - @NotNull - public final net.corda.core.context.Actor getActor() public int hashCode() @NotNull public java.security.Principal principal() - @NotNull public String toString() ## @CordaSerializable @@ -259,7 +239,6 @@ public static final class net.corda.core.context.InvocationOrigin$Scheduled exte public int hashCode() @NotNull public java.security.Principal principal() - @NotNull public String toString() ## @CordaSerializable @@ -279,7 +258,6 @@ public static final class net.corda.core.context.InvocationOrigin$Service extend public int hashCode() @NotNull public java.security.Principal principal() - @NotNull public String toString() ## @CordaSerializable @@ -305,7 +283,6 @@ public final class net.corda.core.context.Trace extends java.lang.Object public int hashCode() @NotNull public static final net.corda.core.context.Trace newInstance(net.corda.core.context.Trace$InvocationId, net.corda.core.context.Trace$SessionId) - @NotNull public String toString() public static final net.corda.core.context.Trace$Companion Companion ## @@ -338,7 +315,6 @@ public static final class net.corda.core.context.Trace$SessionId$Companion exten @DoNotImplement @CordaSerializable public final class net.corda.core.contracts.AlwaysAcceptAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint - public boolean canBeTransitionedFrom(net.corda.core.contracts.AttachmentConstraint, net.corda.core.contracts.ContractAttachment) public boolean isSatisfiedBy(net.corda.core.contracts.Attachment) public static final net.corda.core.contracts.AlwaysAcceptAttachmentConstraint INSTANCE ## @@ -465,14 +441,8 @@ public interface net.corda.core.contracts.Attachment extends net.corda.core.cont @DoNotImplement @CordaSerializable public interface net.corda.core.contracts.AttachmentConstraint - public abstract boolean canBeTransitionedFrom(net.corda.core.contracts.AttachmentConstraint, net.corda.core.contracts.ContractAttachment) public abstract boolean isSatisfiedBy(net.corda.core.contracts.Attachment) ## -public final class net.corda.core.contracts.AttachmentConstraintKt extends java.lang.Object - public static final void checkConstraintValidity(net.corda.core.contracts.TransactionState) - public static final boolean contractHasAutomaticConstraintPropagation(String, ClassLoader) - public static final void warnContractWithoutConstraintPropagation(String, ClassLoader) -## @CordaSerializable public final class net.corda.core.contracts.AttachmentResolutionException extends net.corda.core.flows.FlowException public (net.corda.core.crypto.SecureHash) @@ -482,24 +452,12 @@ public final class net.corda.core.contracts.AttachmentResolutionException extend @DoNotImplement @CordaSerializable public final class net.corda.core.contracts.AutomaticHashConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint - public boolean canBeTransitionedFrom(net.corda.core.contracts.AttachmentConstraint, net.corda.core.contracts.ContractAttachment) public boolean isSatisfiedBy(net.corda.core.contracts.Attachment) public static final net.corda.core.contracts.AutomaticHashConstraint INSTANCE ## -@DoNotImplement -@CordaSerializable -public final class net.corda.core.contracts.AutomaticPlaceholderConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint - public boolean canBeTransitionedFrom(net.corda.core.contracts.AttachmentConstraint, net.corda.core.contracts.ContractAttachment) - public boolean isSatisfiedBy(net.corda.core.contracts.Attachment) - public static final net.corda.core.contracts.AutomaticPlaceholderConstraint INSTANCE -## public @interface net.corda.core.contracts.BelongsToContract public abstract Class value() ## -public final class net.corda.core.contracts.BelongsToContractKt extends java.lang.Object - @Nullable - public static final String getRequiredContractClassName(net.corda.core.contracts.ContractState) -## @CordaSerializable public final class net.corda.core.contracts.Command extends java.lang.Object public (T, java.security.PublicKey) @@ -533,7 +491,6 @@ public final class net.corda.core.contracts.CommandAndState extends java.lang.Ob @NotNull public final net.corda.core.contracts.OwnableState getOwnableState() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -558,7 +515,6 @@ public final class net.corda.core.contracts.CommandWithParties extends java.lang @NotNull public final T getValue() public int hashCode() - @NotNull public String toString() ## public final class net.corda.core.contracts.ComponentGroupEnum extends java.lang.Enum @@ -575,7 +531,6 @@ public final class net.corda.core.contracts.ContractAttachment extends java.lang public (net.corda.core.contracts.Attachment, String) public (net.corda.core.contracts.Attachment, String, java.util.Set) public (net.corda.core.contracts.Attachment, String, java.util.Set, String) - public (net.corda.core.contracts.Attachment, String, java.util.Set, String, java.util.List) @NotNull public final java.util.Set getAdditionalContracts() @NotNull @@ -614,7 +569,6 @@ public final class net.corda.core.contracts.ContractsDSL extends java.lang.Objec public interface net.corda.core.contracts.FungibleAsset extends net.corda.core.contracts.FungibleState, net.corda.core.contracts.OwnableState @NotNull public abstract net.corda.core.contracts.Amount> getAmount() - @SerializableCalculatedProperty @NotNull public abstract java.util.Collection getExitKeys() @NotNull @@ -629,7 +583,6 @@ public interface net.corda.core.contracts.FungibleState extends net.corda.core.c @CordaSerializable public final class net.corda.core.contracts.HashAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint public (net.corda.core.crypto.SecureHash) - public boolean canBeTransitionedFrom(net.corda.core.contracts.AttachmentConstraint, net.corda.core.contracts.ContractAttachment) @NotNull public final net.corda.core.crypto.SecureHash component1() @NotNull @@ -639,7 +592,6 @@ public final class net.corda.core.contracts.HashAttachmentConstraint extends jav public final net.corda.core.crypto.SecureHash getAttachmentId() public int hashCode() public boolean isSatisfiedBy(net.corda.core.contracts.Attachment) - @NotNull public String toString() ## @CordaSerializable @@ -695,8 +647,6 @@ public interface net.corda.core.contracts.NamedByHash @NotNull public abstract net.corda.core.crypto.SecureHash getId() ## -public @interface net.corda.core.contracts.NoConstraintPropagation -## @CordaSerializable public interface net.corda.core.contracts.OwnableState extends net.corda.core.contracts.ContractState @NotNull @@ -727,19 +677,6 @@ public final class net.corda.core.contracts.PrivacySalt extends net.corda.core.u public () public (byte[]) ## -public final class net.corda.core.contracts.ReferencedStateAndRef extends java.lang.Object - public (net.corda.core.contracts.StateAndRef) - @NotNull - public final net.corda.core.contracts.StateAndRef component1() - @NotNull - public final net.corda.core.contracts.ReferencedStateAndRef copy(net.corda.core.contracts.StateAndRef) - public boolean equals(Object) - @NotNull - public final net.corda.core.contracts.StateAndRef getStateAndRef() - public int hashCode() - @NotNull - public String toString() -## public final class net.corda.core.contracts.Requirements extends java.lang.Object public final void using(String, boolean) public static final net.corda.core.contracts.Requirements INSTANCE @@ -767,7 +704,6 @@ public final class net.corda.core.contracts.ScheduledActivity extends java.lang. @NotNull public java.time.Instant getScheduledAt() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -785,24 +721,6 @@ public final class net.corda.core.contracts.ScheduledStateRef extends java.lang. @NotNull public java.time.Instant getScheduledAt() public int hashCode() - @NotNull - public String toString() -## -@DoNotImplement -@CordaSerializable -public final class net.corda.core.contracts.SignatureAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint - public (java.security.PublicKey) - public boolean canBeTransitionedFrom(net.corda.core.contracts.AttachmentConstraint, net.corda.core.contracts.ContractAttachment) - @NotNull - public final java.security.PublicKey component1() - @NotNull - public final net.corda.core.contracts.SignatureAttachmentConstraint copy(java.security.PublicKey) - public boolean equals(Object) - @NotNull - public final java.security.PublicKey getKey() - public int hashCode() - public boolean isSatisfiedBy(net.corda.core.contracts.Attachment) - @NotNull public String toString() ## public final class net.corda.core.contracts.SourceAndAmount extends java.lang.Object @@ -823,7 +741,6 @@ public final class net.corda.core.contracts.SourceAndAmount extends java.lang.Ob @NotNull public final P getSource() public int hashCode() - @NotNull public String toString() ## public final class net.corda.core.contracts.StateAndContract extends java.lang.Object @@ -840,7 +757,6 @@ public final class net.corda.core.contracts.StateAndContract extends java.lang.O @NotNull public final net.corda.core.contracts.ContractState getState() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -858,9 +774,6 @@ public final class net.corda.core.contracts.StateAndRef extends java.lang.Object @NotNull public final net.corda.core.contracts.TransactionState getState() public int hashCode() - @NotNull - public final net.corda.core.contracts.ReferencedStateAndRef referenced() - @NotNull public String toString() ## @CordaSerializable @@ -960,7 +873,6 @@ public final class net.corda.core.contracts.TransactionState extends java.lang.O public (T, String, net.corda.core.identity.Party) public (T, String, net.corda.core.identity.Party, Integer) public (T, String, net.corda.core.identity.Party, Integer, net.corda.core.contracts.AttachmentConstraint) - public (T, net.corda.core.identity.Party) @NotNull public final T component1() @NotNull @@ -985,15 +897,12 @@ public final class net.corda.core.contracts.TransactionState extends java.lang.O @NotNull public final net.corda.core.identity.Party getNotary() public int hashCode() - @NotNull public String toString() - public static final net.corda.core.contracts.TransactionState$Companion Companion ## public final class net.corda.core.contracts.TransactionStateKt extends java.lang.Object ## @CordaSerializable public abstract class net.corda.core.contracts.TransactionVerificationException extends net.corda.core.flows.FlowException - public (net.corda.core.crypto.SecureHash, String, Throwable) @NotNull public final net.corda.core.crypto.SecureHash getTxId() ## @@ -1004,20 +913,6 @@ public static final class net.corda.core.contracts.TransactionVerificationExcept public final String getContractClass() ## @CordaSerializable -public static final class net.corda.core.contracts.TransactionVerificationException$ConstraintPropagationRejection extends net.corda.core.contracts.TransactionVerificationException - public (net.corda.core.crypto.SecureHash, String, net.corda.core.contracts.AttachmentConstraint, net.corda.core.contracts.AttachmentConstraint) - @NotNull - public final String getContractClass() -## -@CordaSerializable -public static final class net.corda.core.contracts.TransactionVerificationException$ContractAttachmentNotSignedByPackageOwnerException extends net.corda.core.contracts.TransactionVerificationException - public (net.corda.core.crypto.SecureHash, net.corda.core.crypto.SecureHash, String) - @NotNull - public final net.corda.core.crypto.SecureHash getAttachmentHash() - @NotNull - public final String getContractClass() -## -@CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$ContractConstraintRejection extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, String) @NotNull @@ -1077,10 +972,6 @@ public static final class net.corda.core.contracts.TransactionVerificationExcept public final java.util.List getMissing() ## @CordaSerializable -public static final class net.corda.core.contracts.TransactionVerificationException$TransactionDuplicateEncumbranceException extends net.corda.core.contracts.TransactionVerificationException - public (net.corda.core.crypto.SecureHash, int) -## -@CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$TransactionMissingEncumbranceException extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, int, net.corda.core.contracts.TransactionVerificationException$Direction) @NotNull @@ -1088,14 +979,6 @@ public static final class net.corda.core.contracts.TransactionVerificationExcept public final int getMissing() ## @CordaSerializable -public static final class net.corda.core.contracts.TransactionVerificationException$TransactionNonMatchingEncumbranceException extends net.corda.core.contracts.TransactionVerificationException - public (net.corda.core.crypto.SecureHash, java.util.Collection) -## -@CordaSerializable -public static final class net.corda.core.contracts.TransactionVerificationException$TransactionNotaryMismatchEncumbranceException extends net.corda.core.contracts.TransactionVerificationException - public (net.corda.core.crypto.SecureHash, int, int, net.corda.core.identity.Party, net.corda.core.identity.Party) -## -@CordaSerializable public abstract class net.corda.core.contracts.TypeOnlyCommandData extends java.lang.Object implements net.corda.core.contracts.CommandData public () public boolean equals(Object) @@ -1104,7 +987,6 @@ public abstract class net.corda.core.contracts.TypeOnlyCommandData extends java. @CordaSerializable public final class net.corda.core.contracts.UniqueIdentifier extends java.lang.Object implements java.lang.Comparable public () - public (String) public (String, java.util.UUID) public int compareTo(net.corda.core.contracts.UniqueIdentifier) @Nullable @@ -1142,14 +1024,11 @@ public interface net.corda.core.contracts.UpgradedContractWithLegacyConstraint e @DoNotImplement @CordaSerializable public final class net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint - public boolean canBeTransitionedFrom(net.corda.core.contracts.AttachmentConstraint, net.corda.core.contracts.ContractAttachment) public boolean isSatisfiedBy(net.corda.core.contracts.Attachment) public static final net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint INSTANCE ## @DoNotImplement public interface net.corda.core.cordapp.Cordapp - @NotNull - public abstract java.util.List>> getAllFlows() @NotNull public abstract java.util.List getContractClassNames() @NotNull @@ -1159,13 +1038,9 @@ public interface net.corda.core.cordapp.Cordapp @NotNull public abstract java.util.List>> getInitiatedFlows() @NotNull - public abstract net.corda.core.crypto.SecureHash$SHA256 getJarHash() - @NotNull public abstract java.net.URL getJarPath() @NotNull public abstract String getName() - @Nullable - public abstract Class getNotaryService() @NotNull public abstract java.util.List>> getRpcFlows() @NotNull @@ -1179,24 +1054,6 @@ public interface net.corda.core.cordapp.Cordapp @NotNull public abstract java.util.List> getServices() ## -@DoNotImplement -public interface net.corda.core.cordapp.CordappConfig - public abstract boolean exists(String) - @NotNull - public abstract Object get(String) - public abstract boolean getBoolean(String) - public abstract double getDouble(String) - public abstract float getFloat(String) - public abstract int getInt(String) - public abstract long getLong(String) - @NotNull - public abstract Number getNumber(String) - @NotNull - public abstract String getString(String) -## -public final class net.corda.core.cordapp.CordappConfigException extends java.lang.Exception - public (String, Throwable) -## public final class net.corda.core.cordapp.CordappContext extends java.lang.Object public (net.corda.core.cordapp.Cordapp, net.corda.core.crypto.SecureHash, ClassLoader, net.corda.core.cordapp.CordappConfig) @Nullable @@ -1204,8 +1061,6 @@ public final class net.corda.core.cordapp.CordappContext extends java.lang.Objec @NotNull public final ClassLoader getClassLoader() @NotNull - public final net.corda.core.cordapp.CordappConfig getConfig() - @NotNull public final net.corda.core.cordapp.Cordapp getCordapp() ## @DoNotImplement @@ -1335,7 +1190,6 @@ public static final class net.corda.core.crypto.CompositeSignature$State extends @NotNull public final net.corda.core.crypto.CompositeKey getVerifyKey() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -1349,7 +1203,6 @@ public final class net.corda.core.crypto.CompositeSignaturesWithKeys extends jav @NotNull public final java.util.List getSigs() public int hashCode() - @NotNull public String toString() public static final net.corda.core.crypto.CompositeSignaturesWithKeys$Companion Companion ## @@ -1372,8 +1225,6 @@ public final class net.corda.core.crypto.CordaSecurityProvider extends java.secu ## public static final class net.corda.core.crypto.CordaSecurityProvider$Companion extends java.lang.Object ## -public final class net.corda.core.crypto.CordaSecurityProviderKt extends java.lang.Object -## public final class net.corda.core.crypto.Crypto extends java.lang.Object @NotNull public static final java.security.PrivateKey decodePrivateKey(String, byte[]) @@ -1430,7 +1281,6 @@ public final class net.corda.core.crypto.Crypto extends java.lang.Object public static final boolean isValid(net.corda.core.crypto.SecureHash, net.corda.core.crypto.TransactionSignature) public static final boolean isValid(net.corda.core.crypto.SignatureScheme, java.security.PublicKey, byte[], byte[]) public static final boolean publicKeyOnCurve(net.corda.core.crypto.SignatureScheme, java.security.PublicKey) - public static final void registerProviders() @NotNull public static final java.util.List supportedSignatureSchemes() @NotNull @@ -1439,7 +1289,6 @@ public final class net.corda.core.crypto.Crypto extends java.lang.Object public static final java.security.PublicKey toSupportedPublicKey(java.security.PublicKey) @NotNull public static final java.security.PublicKey toSupportedPublicKey(org.bouncycastle.asn1.x509.SubjectPublicKeyInfo) - public static final boolean validatePublicKey(java.security.PublicKey) @NotNull public static final net.corda.core.crypto.SignatureScheme COMPOSITE_KEY @NotNull @@ -1477,10 +1326,7 @@ public final class net.corda.core.crypto.CryptoUtils extends java.lang.Object @NotNull public static final java.security.KeyPair generateKeyPair() @NotNull - public static final java.util.Set getBlacklistedCrlEndpoints() - @NotNull public static final java.util.Set getKeys(java.security.PublicKey) - public static final boolean isCRLDistributionPointBlacklisted(java.util.List) public static final boolean isFulfilledBy(java.security.PublicKey, Iterable) public static final boolean isFulfilledBy(java.security.PublicKey, java.security.PublicKey) public static final boolean isValid(java.security.PublicKey, byte[], net.corda.core.crypto.DigitalSignature) @@ -1522,9 +1368,6 @@ public static class net.corda.core.crypto.DigitalSignature$WithKey extends net.c @NotNull public final net.corda.core.crypto.DigitalSignature withoutKey() ## -public final class net.corda.core.crypto.DummySecureRandom extends java.security.SecureRandom - public static final net.corda.core.crypto.DummySecureRandom INSTANCE -## public abstract class net.corda.core.crypto.MerkleTree extends java.lang.Object @NotNull public abstract net.corda.core.crypto.SecureHash getHash() @@ -1544,7 +1387,6 @@ public static final class net.corda.core.crypto.MerkleTree$Leaf extends net.cord @NotNull public net.corda.core.crypto.SecureHash getHash() public int hashCode() - @NotNull public String toString() ## public static final class net.corda.core.crypto.MerkleTree$Node extends net.corda.core.crypto.MerkleTree @@ -1565,7 +1407,6 @@ public static final class net.corda.core.crypto.MerkleTree$Node extends net.cord @NotNull public final net.corda.core.crypto.MerkleTree getRight() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -1622,7 +1463,6 @@ public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$In @NotNull public final net.corda.core.crypto.SecureHash getHash() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -1636,7 +1476,6 @@ public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$Le @NotNull public final net.corda.core.crypto.SecureHash getHash() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -1654,7 +1493,6 @@ public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$No @NotNull public final net.corda.core.crypto.PartialMerkleTree$PartialTree getRight() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -1676,10 +1514,6 @@ public abstract class net.corda.core.crypto.SecureHash extends net.corda.core.ut @NotNull public String toString() public static final net.corda.core.crypto.SecureHash$Companion Companion - @NotNull - public static final net.corda.core.crypto.SecureHash$SHA256 allOnesHash - @NotNull - public static final net.corda.core.crypto.SecureHash$SHA256 zeroHash ## public static final class net.corda.core.crypto.SecureHash$Companion extends java.lang.Object @NotNull @@ -1722,7 +1556,6 @@ public final class net.corda.core.crypto.SignableData extends java.lang.Object @NotNull public final net.corda.core.crypto.SecureHash getTxId() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -1736,7 +1569,6 @@ public final class net.corda.core.crypto.SignatureMetadata extends java.lang.Obj public final int getPlatformVersion() public final int getSchemeNumberID() public int hashCode() - @NotNull public String toString() ## public final class net.corda.core.crypto.SignatureScheme extends java.lang.Object @@ -1783,7 +1615,6 @@ public final class net.corda.core.crypto.SignatureScheme extends java.lang.Objec @NotNull public final org.bouncycastle.asn1.x509.AlgorithmIdentifier getSignatureOID() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -1881,7 +1712,6 @@ public static final class net.corda.core.flows.AbstractStateReplacementFlow$Prop @NotNull public final net.corda.core.contracts.StateRef getStateRef() public int hashCode() - @NotNull public String toString() ## public static final class net.corda.core.flows.AbstractStateReplacementFlow$UpgradeTx extends java.lang.Object @@ -1894,7 +1724,6 @@ public static final class net.corda.core.flows.AbstractStateReplacementFlow$Upgr @NotNull public final net.corda.core.transactions.SignedTransaction getStx() public int hashCode() - @NotNull public String toString() ## @Suspendable @@ -1990,11 +1819,8 @@ public class net.corda.core.flows.DataVendingFlow extends net.corda.core.flows.F @InitiatingFlow public final class net.corda.core.flows.FinalityFlow extends net.corda.core.flows.FlowLogic public (net.corda.core.transactions.SignedTransaction) - public (net.corda.core.transactions.SignedTransaction, java.util.Collection) - public (net.corda.core.transactions.SignedTransaction, java.util.Collection, net.corda.core.utilities.ProgressTracker) public (net.corda.core.transactions.SignedTransaction, java.util.Set) public (net.corda.core.transactions.SignedTransaction, java.util.Set, net.corda.core.utilities.ProgressTracker) - public (net.corda.core.transactions.SignedTransaction, net.corda.core.flows.FlowSession, net.corda.core.flows.FlowSession...) public (net.corda.core.transactions.SignedTransaction, net.corda.core.utilities.ProgressTracker) @Suspendable @NotNull @@ -2026,13 +1852,7 @@ public class net.corda.core.flows.FlowException extends net.corda.core.CordaExce public () public (String) public (String, Throwable) - public (String, Throwable, Long) public (Throwable) - @Nullable - public Long getErrorId() - @Nullable - public final Long getOriginalErrorId() - public final void setOriginalErrorId(Long) ## @CordaSerializable public final class net.corda.core.flows.FlowInfo extends java.lang.Object @@ -2047,7 +1867,6 @@ public final class net.corda.core.flows.FlowInfo extends java.lang.Object public final String getAppName() public final int getFlowVersion() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -2068,7 +1887,6 @@ public static final class net.corda.core.flows.FlowInitiator$Peer extends net.co @NotNull public final net.corda.core.identity.Party getParty() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -2084,7 +1902,6 @@ public static final class net.corda.core.flows.FlowInitiator$RPC extends net.cor @NotNull public final String getUsername() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -2100,7 +1917,6 @@ public static final class net.corda.core.flows.FlowInitiator$Scheduled extends n @NotNull public final net.corda.core.contracts.ScheduledStateRef getScheduledState() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -2116,7 +1932,6 @@ public static final class net.corda.core.flows.FlowInitiator$Service extends net @NotNull public final String getServiceClassName() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -2163,13 +1978,7 @@ public abstract class net.corda.core.flows.FlowLogic extends java.lang.Object public java.util.List> receiveAll(Class, java.util.List) @Suspendable @NotNull - public java.util.List> receiveAll(Class, java.util.List, boolean) - @Suspendable - @NotNull public java.util.Map> receiveAllMap(java.util.Map>) - @Suspendable - @NotNull - public java.util.Map> receiveAllMap(java.util.Map>, boolean) public final void recordAuditEvent(String, String, java.util.Map) @Suspendable public void send(net.corda.core.identity.Party, Object) @@ -2179,8 +1988,6 @@ public abstract class net.corda.core.flows.FlowLogic extends java.lang.Object @Suspendable public static final void sleep(java.time.Duration) @Suspendable - public static final void sleep(java.time.Duration, boolean) - @Suspendable public R subFlow(net.corda.core.flows.FlowLogic) @Nullable public final net.corda.core.messaging.DataFeed track() @@ -2194,8 +2001,6 @@ public abstract class net.corda.core.flows.FlowLogic extends java.lang.Object @Suspendable @NotNull public final net.corda.core.transactions.SignedTransaction waitForLedgerCommit(net.corda.core.crypto.SecureHash, boolean) - @Suspendable - public final void waitForStateConsumption(java.util.Set) public static final net.corda.core.flows.FlowLogic$Companion Companion ## public static final class net.corda.core.flows.FlowLogic$Companion extends java.lang.Object @@ -2203,8 +2008,6 @@ public static final class net.corda.core.flows.FlowLogic$Companion extends java. public final net.corda.core.flows.FlowLogic getCurrentTopLevel() @Suspendable public final void sleep(java.time.Duration) - @Suspendable - public final void sleep(java.time.Duration, boolean) ## @DoNotImplement @CordaSerializable @@ -2265,7 +2068,6 @@ public final class net.corda.core.flows.FlowStackSnapshot extends java.lang.Obje @NotNull public final java.time.Instant getTime() public int hashCode() - @NotNull public String toString() ## public static final class net.corda.core.flows.FlowStackSnapshot$Frame extends java.lang.Object @@ -2285,10 +2087,6 @@ public static final class net.corda.core.flows.FlowStackSnapshot$Frame extends j @NotNull public String toString() ## -public interface net.corda.core.flows.IdentifiableException - @Nullable - public Long getErrorId() -## @CordaSerializable public final class net.corda.core.flows.IllegalFlowLogicException extends java.lang.IllegalArgumentException public (Class, String) @@ -2321,7 +2119,6 @@ public final class net.corda.core.flows.NotarisationPayload extends java.lang.Ob @NotNull public final Object getTransaction() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -2348,7 +2145,6 @@ public final class net.corda.core.flows.NotarisationRequestSignature extends jav public final net.corda.core.crypto.DigitalSignature$WithKey getDigitalSignature() public final int getPlatformVersion() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -2362,7 +2158,6 @@ public final class net.corda.core.flows.NotarisationResponse extends java.lang.O @NotNull public final java.util.List getSignatures() public int hashCode() - @NotNull public String toString() ## @InitiatingFlow @@ -2503,20 +2298,6 @@ public static final class net.corda.core.flows.NotaryFlow$Client$Companion$REQUE public static final class net.corda.core.flows.NotaryFlow$Client$Companion$VALIDATING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.NotaryFlow$Client$Companion$VALIDATING INSTANCE ## -public final class net.corda.core.flows.ReceiveFinalityFlow extends net.corda.core.flows.FlowLogic - public (net.corda.core.flows.FlowSession) - public (net.corda.core.flows.FlowSession, net.corda.core.crypto.SecureHash) - public (net.corda.core.flows.FlowSession, net.corda.core.crypto.SecureHash, net.corda.core.node.StatesToRecord) - @Suspendable - @NotNull - public net.corda.core.transactions.SignedTransaction call() - @Nullable - public final net.corda.core.crypto.SecureHash getExpectedTxId() - @NotNull - public final net.corda.core.flows.FlowSession getOtherSideSession() - @NotNull - public final net.corda.core.node.StatesToRecord getStatesToRecord() -## public final class net.corda.core.flows.ReceiveStateAndRefFlow extends net.corda.core.flows.FlowLogic public (net.corda.core.flows.FlowSession) @Suspendable @@ -2530,12 +2311,6 @@ public class net.corda.core.flows.ReceiveTransactionFlow extends net.corda.core. @Suspendable @NotNull public net.corda.core.transactions.SignedTransaction call() - @Suspendable - protected void checkBeforeRecording(net.corda.core.transactions.SignedTransaction) -## -@CordaSerializable -public final class net.corda.core.flows.RetrieveAnyTransactionPayload extends java.util.ArrayList - public static final net.corda.core.flows.RetrieveAnyTransactionPayload INSTANCE ## public @interface net.corda.core.flows.SchedulableFlow ## @@ -2546,7 +2321,6 @@ public class net.corda.core.flows.SendTransactionFlow extends net.corda.core.flo public (net.corda.core.flows.FlowSession, net.corda.core.transactions.SignedTransaction) ## public abstract class net.corda.core.flows.SignTransactionFlow extends net.corda.core.flows.FlowLogic - public (net.corda.core.flows.FlowSession) public (net.corda.core.flows.FlowSession, net.corda.core.utilities.ProgressTracker) @Suspendable @NotNull @@ -2587,7 +2361,6 @@ public final class net.corda.core.flows.StackFrameDataToken extends java.lang.Ob @NotNull public final String getClassName() public int hashCode() - @NotNull public String toString() ## public @interface net.corda.core.flows.StartableByRPC @@ -2597,31 +2370,17 @@ public @interface net.corda.core.flows.StartableByService @CordaSerializable public final class net.corda.core.flows.StateConsumptionDetails extends java.lang.Object public (net.corda.core.crypto.SecureHash) - public (net.corda.core.crypto.SecureHash, net.corda.core.flows.StateConsumptionDetails$ConsumedStateType) @NotNull public final net.corda.core.crypto.SecureHash component1() @NotNull - public final net.corda.core.flows.StateConsumptionDetails$ConsumedStateType component2() - @NotNull public final net.corda.core.flows.StateConsumptionDetails copy(net.corda.core.crypto.SecureHash) - @NotNull - public final net.corda.core.flows.StateConsumptionDetails copy(net.corda.core.crypto.SecureHash, net.corda.core.flows.StateConsumptionDetails$ConsumedStateType) public boolean equals(Object) @NotNull public final net.corda.core.crypto.SecureHash getHashOfTransactionId() - @NotNull - public final net.corda.core.flows.StateConsumptionDetails$ConsumedStateType getType() public int hashCode() - @NotNull public String toString() ## @CordaSerializable -public static final class net.corda.core.flows.StateConsumptionDetails$ConsumedStateType extends java.lang.Enum - protected () - public static net.corda.core.flows.StateConsumptionDetails$ConsumedStateType valueOf(String) - public static net.corda.core.flows.StateConsumptionDetails$ConsumedStateType[] values() -## -@CordaSerializable public final class net.corda.core.flows.StateMachineRunId extends java.lang.Object public (java.util.UUID) @NotNull @@ -2650,68 +2409,6 @@ public class net.corda.core.flows.StateReplacementException extends net.corda.co public final class net.corda.core.flows.UnexpectedFlowEndException extends net.corda.core.CordaRuntimeException implements net.corda.core.flows.IdentifiableException public (String) public (String, Throwable) - public (String, Throwable, Long) - @Nullable - public Long getErrorId() - @Nullable - public final Long getOriginalErrorId() -## -public final class net.corda.core.flows.WithReferencedStatesFlow extends net.corda.core.flows.FlowLogic - public (net.corda.core.flows.FlowLogic, net.corda.core.utilities.ProgressTracker) - @Suspendable - @NotNull - public T call() - @NotNull - public final net.corda.core.flows.FlowLogic getFlowLogic() - @NotNull - public net.corda.core.utilities.ProgressTracker getProgressTracker() - @NotNull - public static final net.corda.core.utilities.ProgressTracker tracker() - public static final net.corda.core.flows.WithReferencedStatesFlow$Companion Companion -## -public static final class net.corda.core.flows.WithReferencedStatesFlow$Companion extends java.lang.Object - @NotNull - public final org.slf4j.Logger getLogger() - @NotNull - public final net.corda.core.utilities.ProgressTracker tracker() -## -@CordaSerializable -public static final class net.corda.core.flows.WithReferencedStatesFlow$Companion$ATTEMPT extends net.corda.core.utilities.ProgressTracker$Step - public static final net.corda.core.flows.WithReferencedStatesFlow$Companion$ATTEMPT INSTANCE -## -@CordaSerializable -public static final class net.corda.core.flows.WithReferencedStatesFlow$Companion$RETRYING extends net.corda.core.utilities.ProgressTracker$Step - public static final net.corda.core.flows.WithReferencedStatesFlow$Companion$RETRYING INSTANCE -## -@CordaSerializable -public static final class net.corda.core.flows.WithReferencedStatesFlow$Companion$SUCCESS extends net.corda.core.utilities.ProgressTracker$Step - public static final net.corda.core.flows.WithReferencedStatesFlow$Companion$SUCCESS INSTANCE -## -public static final class net.corda.core.flows.WithReferencedStatesFlow$FlowResult$Conflict extends net.corda.core.flows.WithReferencedStatesFlow$FlowResult - public (java.util.Set) - @NotNull - public final java.util.Set component1() - @NotNull - public final net.corda.core.flows.WithReferencedStatesFlow$FlowResult$Conflict copy(java.util.Set) - public boolean equals(Object) - @NotNull - public final java.util.Set getStateRefs() - public int hashCode() - @NotNull - public String toString() -## -public static final class net.corda.core.flows.WithReferencedStatesFlow$FlowResult$Success extends net.corda.core.flows.WithReferencedStatesFlow$FlowResult - public (T) - @NotNull - public final T component1() - @NotNull - public final net.corda.core.flows.WithReferencedStatesFlow$FlowResult$Success copy(T) - public boolean equals(Object) - @NotNull - public final T getValue() - public int hashCode() - @NotNull - public String toString() ## @DoNotImplement @CordaSerializable @@ -2869,7 +2566,6 @@ public final class net.corda.core.messaging.ClientRpcSslOptions extends java.lan @NotNull public final String getTrustStoreProvider() public int hashCode() - @NotNull public String toString() ## @DoNotImplement @@ -2881,8 +2577,6 @@ public interface net.corda.core.messaging.CordaRPCOps extends net.corda.core.mes @NotNull public abstract java.time.Instant currentNodeTime() @NotNull - public abstract net.corda.core.node.NetworkParameters getNetworkParameters() - @NotNull public abstract Iterable getVaultTransactionNotes(net.corda.core.crypto.SecureHash) @RPCReturnsObservables @NotNull @@ -2890,8 +2584,6 @@ public interface net.corda.core.messaging.CordaRPCOps extends net.corda.core.mes @NotNull public abstract java.util.List internalVerifiedTransactionsSnapshot() public abstract boolean isFlowsDrainingModeEnabled() - public abstract boolean isWaitingForShutdown() - public abstract boolean killFlow(net.corda.core.flows.StateMachineRunId) @RPCReturnsObservables @NotNull public abstract net.corda.core.messaging.DataFeed, net.corda.core.node.services.NetworkMapCache$MapChange> networkMapFeed() @@ -2916,11 +2608,9 @@ public interface net.corda.core.messaging.CordaRPCOps extends net.corda.core.mes public abstract net.corda.core.identity.Party partyFromKey(java.security.PublicKey) @NotNull public abstract java.util.List queryAttachments(net.corda.core.node.services.vault.AttachmentQueryCriteria, net.corda.core.node.services.vault.AttachmentSort) - public abstract void refreshNetworkMapCache() @NotNull public abstract java.util.List registeredFlows() public abstract void setFlowsDrainingModeEnabled(boolean) - public abstract void shutdown() @RPCReturnsObservables @NotNull public abstract net.corda.core.messaging.FlowHandle startFlowDynamic(Class>, Object...) @@ -2937,7 +2627,6 @@ public interface net.corda.core.messaging.CordaRPCOps extends net.corda.core.mes public abstract net.corda.core.messaging.DataFeed, net.corda.core.messaging.StateMachineUpdate> stateMachinesFeed() @NotNull public abstract java.util.List stateMachinesSnapshot() - public abstract void terminate(boolean) @NotNull public abstract net.corda.core.crypto.SecureHash uploadAttachment(java.io.InputStream) @NotNull @@ -2987,11 +2676,9 @@ public final class net.corda.core.messaging.DataFeed extends java.lang.Object @NotNull public final rx.Observable getUpdates() public int hashCode() - @NotNull public String toString() ## @DoNotImplement -@CordaSerializable public interface net.corda.core.messaging.FlowHandle extends java.lang.AutoCloseable public abstract void close() @NotNull @@ -3016,11 +2703,9 @@ public final class net.corda.core.messaging.FlowHandleImpl extends java.lang.Obj @NotNull public net.corda.core.concurrent.CordaFuture getReturnValue() public int hashCode() - @NotNull public String toString() ## @DoNotImplement -@CordaSerializable public interface net.corda.core.messaging.FlowProgressHandle extends net.corda.core.messaging.FlowHandle public abstract void close() @NotNull @@ -3063,7 +2748,6 @@ public final class net.corda.core.messaging.FlowProgressHandleImpl extends java. @Nullable public net.corda.core.messaging.DataFeed getStepsTreeIndexFeed() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -3095,7 +2779,6 @@ public final class net.corda.core.messaging.ParametersUpdateInfo extends java.la @NotNull public final java.time.Instant getUpdateDeadline() public int hashCode() - @NotNull public String toString() ## @DoNotImplement @@ -3155,7 +2838,6 @@ public final class net.corda.core.messaging.StateMachineTransactionMapping exten @NotNull public final net.corda.core.crypto.SecureHash getTransactionId() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -3176,7 +2858,6 @@ public static final class net.corda.core.messaging.StateMachineUpdate$Added exte @NotNull public final net.corda.core.messaging.StateMachineInfo getStateMachineInfo() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -3194,7 +2875,6 @@ public static final class net.corda.core.messaging.StateMachineUpdate$Removed ex @NotNull public final net.corda.core.utilities.Try getResult() public int hashCode() - @NotNull public String toString() ## @DoNotImplement @@ -3205,27 +2885,8 @@ public interface net.corda.core.node.AppServiceHub extends net.corda.core.node.S public abstract net.corda.core.messaging.FlowProgressHandle startTrackedFlow(net.corda.core.flows.FlowLogic) ## @CordaSerializable -public final class net.corda.core.node.JavaPackageName extends java.lang.Object - public (String) - @NotNull - public final String component1() - @NotNull - public final net.corda.core.node.JavaPackageName copy(String) - public boolean equals(Object) - @NotNull - public final String getName() - public int hashCode() - public final boolean owns(String) - @NotNull - public String toString() -## -@CordaSerializable public final class net.corda.core.node.NetworkParameters extends java.lang.Object - @DeprecatedConstructorForDeserialization public (int, java.util.List, int, int, java.time.Instant, int, java.util.Map>) - @DeprecatedConstructorForDeserialization - public (int, java.util.List, int, int, java.time.Instant, int, java.util.Map>, java.time.Duration) - public (int, java.util.List, int, int, java.time.Instant, int, java.util.Map>, java.time.Duration, java.util.Map) public final int component1() @NotNull public final java.util.List component2() @@ -3237,19 +2898,9 @@ public final class net.corda.core.node.NetworkParameters extends java.lang.Objec @NotNull public final java.util.Map> component7() @NotNull - public final java.time.Duration component8() - @NotNull - public final java.util.Map component9() - @NotNull public final net.corda.core.node.NetworkParameters copy(int, java.util.List, int, int, java.time.Instant, int, java.util.Map>) - @NotNull - public final net.corda.core.node.NetworkParameters copy(int, java.util.List, int, int, java.time.Instant, int, java.util.Map>, java.time.Duration) - @NotNull - public final net.corda.core.node.NetworkParameters copy(int, java.util.List, int, int, java.time.Instant, int, java.util.Map>, java.time.Duration, java.util.Map) public boolean equals(Object) public final int getEpoch() - @NotNull - public final java.time.Duration getEventHorizon() public final int getMaxMessageSize() public final int getMaxTransactionSize() public final int getMinimumPlatformVersion() @@ -3257,18 +2908,11 @@ public final class net.corda.core.node.NetworkParameters extends java.lang.Objec public final java.time.Instant getModifiedTime() @NotNull public final java.util.List getNotaries() - @Nullable - public final java.security.PublicKey getOwnerOf(String) - @NotNull - public final java.util.Map getPackageOwnership() @NotNull public final java.util.Map> getWhitelistedContractImplementations() public int hashCode() - @NotNull public String toString() ## -public final class net.corda.core.node.NetworkParametersKt extends java.lang.Object -## @CordaSerializable public final class net.corda.core.node.NodeInfo extends java.lang.Object public (java.util.List, java.util.List, int, long) @@ -3295,7 +2939,6 @@ public final class net.corda.core.node.NodeInfo extends java.lang.Object @NotNull public final net.corda.core.identity.Party identityFromX500Name(net.corda.core.identity.CordaX500Name) public final boolean isLegalIdentity(net.corda.core.identity.Party) - @NotNull public String toString() ## @CordaSerializable @@ -3311,7 +2954,6 @@ public final class net.corda.core.node.NotaryInfo extends java.lang.Object public final net.corda.core.identity.Party getIdentity() public final boolean getValidating() public int hashCode() - @NotNull public String toString() ## @DoNotImplement @@ -3331,8 +2973,6 @@ public interface net.corda.core.node.ServiceHub extends net.corda.core.node.Serv @NotNull public abstract net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey) @NotNull - public abstract net.corda.core.cordapp.CordappContext getAppContext() - @NotNull public abstract java.time.Clock getClock() @NotNull public abstract net.corda.core.node.services.ContractUpgradeService getContractUpgradeService() @@ -3364,9 +3004,6 @@ public interface net.corda.core.node.ServiceHub extends net.corda.core.node.Serv public abstract net.corda.core.transactions.SignedTransaction signInitialTransaction(net.corda.core.transactions.TransactionBuilder, java.security.PublicKey) @NotNull public abstract net.corda.core.contracts.StateAndRef toStateAndRef(net.corda.core.contracts.StateRef) - public abstract void withEntityManager(java.util.function.Consumer) - @NotNull - public abstract T withEntityManager(kotlin.jvm.functions.Function1) ## @DoNotImplement public interface net.corda.core.node.ServicesForResolution @@ -3388,10 +3025,6 @@ public final class net.corda.core.node.StatesToRecord extends java.lang.Enum public static net.corda.core.node.StatesToRecord valueOf(String) public static net.corda.core.node.StatesToRecord[] values() ## -@CordaSerializable -public final class net.corda.core.node.ZoneVersionTooLowException extends net.corda.core.CordaRuntimeException - public (String) -## @DoNotImplement public interface net.corda.core.node.services.AttachmentStorage public abstract boolean hasAttachment(net.corda.core.crypto.SecureHash) @@ -3485,7 +3118,6 @@ public static final class net.corda.core.node.services.NetworkMapCache$MapChange @NotNull public net.corda.core.node.NodeInfo getNode() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -3503,7 +3135,6 @@ public static final class net.corda.core.node.services.NetworkMapCache$MapChange @NotNull public final net.corda.core.node.NodeInfo getPreviousNode() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -3517,7 +3148,6 @@ public static final class net.corda.core.node.services.NetworkMapCache$MapChange @NotNull public net.corda.core.node.NodeInfo getNode() public int hashCode() - @NotNull public String toString() ## @DoNotImplement @@ -3566,7 +3196,6 @@ public static final class net.corda.core.node.services.PartyInfo$DistributedNode @NotNull public net.corda.core.identity.Party getParty() public int hashCode() - @NotNull public String toString() ## public static final class net.corda.core.node.services.PartyInfo$SingleNode extends net.corda.core.node.services.PartyInfo @@ -3583,7 +3212,6 @@ public static final class net.corda.core.node.services.PartyInfo$SingleNode exte @NotNull public net.corda.core.identity.Party getParty() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -3611,8 +3239,6 @@ public interface net.corda.core.node.services.TransactionStorage public abstract rx.Observable getUpdates() @NotNull public abstract net.corda.core.messaging.DataFeed, net.corda.core.transactions.SignedTransaction> track() - @NotNull - public abstract net.corda.core.concurrent.CordaFuture trackTransaction(net.corda.core.crypto.SecureHash) ## @DoNotImplement public interface net.corda.core.node.services.TransactionVerifierService @@ -3637,35 +3263,6 @@ public static final class net.corda.core.node.services.Vault$Companion extends j public final net.corda.core.node.services.Vault$Update getNoUpdate() ## @CordaSerializable -public static final class net.corda.core.node.services.Vault$ConstraintInfo extends java.lang.Object - public (net.corda.core.contracts.AttachmentConstraint) - @NotNull - public final net.corda.core.contracts.AttachmentConstraint component1() - @NotNull - public final net.corda.core.node.services.Vault$ConstraintInfo copy(net.corda.core.contracts.AttachmentConstraint) - @Nullable - public final byte[] data() - public boolean equals(Object) - @NotNull - public final net.corda.core.contracts.AttachmentConstraint getConstraint() - public int hashCode() - @NotNull - public String toString() - @NotNull - public final net.corda.core.node.services.Vault$ConstraintInfo$Type type() - public static final net.corda.core.node.services.Vault$ConstraintInfo$Companion Companion -## -public static final class net.corda.core.node.services.Vault$ConstraintInfo$Companion extends java.lang.Object - @NotNull - public final net.corda.core.node.services.Vault$ConstraintInfo constraintInfo(net.corda.core.node.services.Vault$ConstraintInfo$Type, byte[]) -## -@CordaSerializable -public static final class net.corda.core.node.services.Vault$ConstraintInfo$Type extends java.lang.Enum - protected () - public static net.corda.core.node.services.Vault$ConstraintInfo$Type valueOf(String) - public static net.corda.core.node.services.Vault$ConstraintInfo$Type[] values() -## -@CordaSerializable public static final class net.corda.core.node.services.Vault$Page extends java.lang.Object public (java.util.List>, java.util.List, long, net.corda.core.node.services.Vault$StateStatus, java.util.List) @NotNull @@ -3690,16 +3287,9 @@ public static final class net.corda.core.node.services.Vault$Page extends java.l public final java.util.List getStatesMetadata() public final long getTotalStatesAvailable() public int hashCode() - @NotNull public String toString() ## @CordaSerializable -public static final class net.corda.core.node.services.Vault$RelevancyStatus extends java.lang.Enum - protected () - public static net.corda.core.node.services.Vault$RelevancyStatus valueOf(String) - public static net.corda.core.node.services.Vault$RelevancyStatus[] values() -## -@CordaSerializable public static final class net.corda.core.node.services.Vault$StateMetadata extends java.lang.Object public (net.corda.core.contracts.StateRef, String, java.time.Instant, java.time.Instant, net.corda.core.node.services.Vault$StateStatus, net.corda.core.identity.AbstractParty, String, java.time.Instant) public (net.corda.core.contracts.StateRef, String, java.time.Instant, java.time.Instant, net.corda.core.node.services.Vault$StateStatus, net.corda.core.identity.AbstractParty, String, java.time.Instant, net.corda.core.node.services.Vault$RelevancyStatus) @@ -3843,7 +3433,6 @@ public interface net.corda.core.node.services.VaultService public abstract net.corda.core.concurrent.CordaFuture> whenConsumed(net.corda.core.contracts.StateRef) ## public final class net.corda.core.node.services.VaultServiceKt extends java.lang.Object - public static final int MAX_CONSTRAINT_DATA_SIZE = 563 ## @CordaSerializable public final class net.corda.core.node.services.vault.AggregateFunctionType extends java.lang.Enum @@ -3890,7 +3479,6 @@ public static final class net.corda.core.node.services.vault.AttachmentQueryCrit @Nullable public final net.corda.core.node.services.vault.ColumnPredicate getUploaderCondition() public int hashCode() - @NotNull public String toString() @NotNull public java.util.Collection visit(net.corda.core.node.services.vault.AttachmentsQueryCriteriaParser) @@ -3916,7 +3504,6 @@ public final class net.corda.core.node.services.vault.AttachmentSort extends net @NotNull public final java.util.Collection getColumns() public int hashCode() - @NotNull public String toString() ## public static final class net.corda.core.node.services.vault.AttachmentSort$AttachmentSortAttribute extends java.lang.Enum @@ -3941,7 +3528,6 @@ public static final class net.corda.core.node.services.vault.AttachmentSort$Atta @NotNull public final net.corda.core.node.services.vault.AttachmentSort$AttachmentSortAttribute getSortAttribute() public int hashCode() - @NotNull public String toString() ## public interface net.corda.core.node.services.vault.AttachmentsQueryCriteriaParser extends net.corda.core.node.services.vault.BaseQueryCriteriaParser @@ -3984,88 +3570,50 @@ public final class net.corda.core.node.services.vault.Builder extends java.lang. @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression avg(kotlin.reflect.KProperty1, java.util.List>, net.corda.core.node.services.vault.Sort$Direction) @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression avg(net.corda.core.node.services.vault.FieldInfo) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression avg(net.corda.core.node.services.vault.FieldInfo, java.util.List) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression avg(net.corda.core.node.services.vault.FieldInfo, java.util.List, net.corda.core.node.services.vault.Sort$Direction) - @NotNull public final net.corda.core.node.services.vault.ColumnPredicate$Between between(R, R) @NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression between(reflect.Field, R, R) @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression between(kotlin.reflect.KProperty1, R, R) @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression between(net.corda.core.node.services.vault.FieldInfo, R, R) - @NotNull public final net.corda.core.node.services.vault.ColumnPredicate$BinaryComparison compare(net.corda.core.node.services.vault.BinaryComparisonOperator, R) @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression comparePredicate(reflect.Field, net.corda.core.node.services.vault.BinaryComparisonOperator, R) @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression comparePredicate(kotlin.reflect.KProperty1, net.corda.core.node.services.vault.BinaryComparisonOperator, R) @NotNull - public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression comparePredicate(net.corda.core.node.services.vault.FieldInfo, net.corda.core.node.services.vault.BinaryComparisonOperator, R) - @NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression count(reflect.Field) @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression count(kotlin.reflect.KProperty1) @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression count(net.corda.core.node.services.vault.FieldInfo) - @NotNull public final net.corda.core.node.services.vault.ColumnPredicate$EqualityComparison equal(R) @NotNull - public final net.corda.core.node.services.vault.ColumnPredicate$EqualityComparison equal(R, boolean) - @NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression equal(reflect.Field, R) @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression equal(reflect.Field, R, boolean) - @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression equal(kotlin.reflect.KProperty1, R) @NotNull - public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression equal(kotlin.reflect.KProperty1, R, boolean) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression equal(net.corda.core.node.services.vault.FieldInfo, R) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression equal(net.corda.core.node.services.vault.FieldInfo, R, boolean) - @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression functionPredicate(reflect.Field, net.corda.core.node.services.vault.ColumnPredicate, java.util.List>, net.corda.core.node.services.vault.Sort$Direction) @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression functionPredicate(kotlin.reflect.KProperty1, net.corda.core.node.services.vault.ColumnPredicate, java.util.List>, net.corda.core.node.services.vault.Sort$Direction) @NotNull - public final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression functionPredicate(net.corda.core.node.services.vault.FieldInfo, net.corda.core.node.services.vault.ColumnPredicate, java.util.List>, net.corda.core.node.services.vault.Sort$Direction) - @NotNull public final net.corda.core.node.services.vault.ColumnPredicate$BinaryComparison greaterThan(R) @NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression greaterThan(reflect.Field, R) @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression greaterThan(kotlin.reflect.KProperty1, R) @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression greaterThan(net.corda.core.node.services.vault.FieldInfo, R) - @NotNull public final net.corda.core.node.services.vault.ColumnPredicate$BinaryComparison greaterThanOrEqual(R) @NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression greaterThanOrEqual(reflect.Field, R) @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression greaterThanOrEqual(kotlin.reflect.KProperty1, R) @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression greaterThanOrEqual(net.corda.core.node.services.vault.FieldInfo, R) - @NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression in(reflect.Field, java.util.Collection) @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression in(reflect.Field, java.util.Collection, boolean) - @NotNull public final net.corda.core.node.services.vault.ColumnPredicate$CollectionExpression in(java.util.Collection) @NotNull - public final net.corda.core.node.services.vault.ColumnPredicate$CollectionExpression in(java.util.Collection, boolean) - @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression in(kotlin.reflect.KProperty1, java.util.Collection) @NotNull - public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression in(kotlin.reflect.KProperty1, java.util.Collection, boolean) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression in(net.corda.core.node.services.vault.FieldInfo, java.util.Collection) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression in(net.corda.core.node.services.vault.FieldInfo, java.util.Collection, boolean) - @NotNull public final net.corda.core.node.services.vault.ColumnPredicate$NullExpression isNotNull() @NotNull public final net.corda.core.node.services.vault.ColumnPredicate$NullExpression isNull() @@ -4074,40 +3622,24 @@ public final class net.corda.core.node.services.vault.Builder extends java.lang. @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression isNull(kotlin.reflect.KProperty1) @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression isNull(net.corda.core.node.services.vault.FieldInfo) - @NotNull public final net.corda.core.node.services.vault.ColumnPredicate$BinaryComparison lessThan(R) @NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression lessThan(reflect.Field, R) @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression lessThan(kotlin.reflect.KProperty1, R) @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression lessThan(net.corda.core.node.services.vault.FieldInfo, R) - @NotNull public final net.corda.core.node.services.vault.ColumnPredicate$BinaryComparison lessThanOrEqual(R) @NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression lessThanOrEqual(reflect.Field, R) @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression lessThanOrEqual(kotlin.reflect.KProperty1, R) @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression lessThanOrEqual(net.corda.core.node.services.vault.FieldInfo, R) - @NotNull public final net.corda.core.node.services.vault.ColumnPredicate$Likeness like(String) @NotNull - public final net.corda.core.node.services.vault.ColumnPredicate$Likeness like(String, boolean) - @NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression like(reflect.Field, String) @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression like(reflect.Field, String, boolean) - @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression like(kotlin.reflect.KProperty1, String) @NotNull - public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression like(kotlin.reflect.KProperty1, String, boolean) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression like(net.corda.core.node.services.vault.FieldInfo, String) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression like(net.corda.core.node.services.vault.FieldInfo, String, boolean) - @NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression max(reflect.Field) @NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression max(reflect.Field, java.util.List) @@ -4116,12 +3648,6 @@ public final class net.corda.core.node.services.vault.Builder extends java.lang. @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression max(kotlin.reflect.KProperty1, java.util.List>, net.corda.core.node.services.vault.Sort$Direction) @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression max(net.corda.core.node.services.vault.FieldInfo) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression max(net.corda.core.node.services.vault.FieldInfo, java.util.List) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression max(net.corda.core.node.services.vault.FieldInfo, java.util.List, net.corda.core.node.services.vault.Sort$Direction) - @NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression min(reflect.Field) @NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression min(reflect.Field, java.util.List) @@ -4130,72 +3656,32 @@ public final class net.corda.core.node.services.vault.Builder extends java.lang. @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression min(kotlin.reflect.KProperty1, java.util.List>, net.corda.core.node.services.vault.Sort$Direction) @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression min(net.corda.core.node.services.vault.FieldInfo) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression min(net.corda.core.node.services.vault.FieldInfo, java.util.List) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression min(net.corda.core.node.services.vault.FieldInfo, java.util.List, net.corda.core.node.services.vault.Sort$Direction) - @NotNull public final net.corda.core.node.services.vault.ColumnPredicate$EqualityComparison notEqual(R) @NotNull - public final net.corda.core.node.services.vault.ColumnPredicate$EqualityComparison notEqual(R, boolean) - @NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notEqual(reflect.Field, R) @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notEqual(reflect.Field, R, boolean) - @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notEqual(kotlin.reflect.KProperty1, R) @NotNull - public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notEqual(kotlin.reflect.KProperty1, R, boolean) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notEqual(net.corda.core.node.services.vault.FieldInfo, R) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notEqual(net.corda.core.node.services.vault.FieldInfo, R, boolean) - @NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notIn(reflect.Field, java.util.Collection) @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notIn(reflect.Field, java.util.Collection, boolean) - @NotNull public final net.corda.core.node.services.vault.ColumnPredicate$CollectionExpression notIn(java.util.Collection) @NotNull - public final net.corda.core.node.services.vault.ColumnPredicate$CollectionExpression notIn(java.util.Collection, boolean) - @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notIn(kotlin.reflect.KProperty1, java.util.Collection) @NotNull - public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notIn(kotlin.reflect.KProperty1, java.util.Collection, boolean) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notIn(net.corda.core.node.services.vault.FieldInfo, java.util.Collection) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notIn(net.corda.core.node.services.vault.FieldInfo, java.util.Collection, boolean) - @NotNull public final net.corda.core.node.services.vault.ColumnPredicate$Likeness notLike(String) @NotNull - public final net.corda.core.node.services.vault.ColumnPredicate$Likeness notLike(String, boolean) - @NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notLike(reflect.Field, String) @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notLike(reflect.Field, String, boolean) - @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notLike(kotlin.reflect.KProperty1, String) @NotNull - public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notLike(kotlin.reflect.KProperty1, String, boolean) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notLike(net.corda.core.node.services.vault.FieldInfo, String) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notLike(net.corda.core.node.services.vault.FieldInfo, String, boolean) - @NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notNull(reflect.Field) @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notNull(kotlin.reflect.KProperty1) @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notNull(net.corda.core.node.services.vault.FieldInfo) - @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression predicate(reflect.Field, net.corda.core.node.services.vault.ColumnPredicate) @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression predicate(kotlin.reflect.KProperty1, net.corda.core.node.services.vault.ColumnPredicate) @NotNull - public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression predicate(net.corda.core.node.services.vault.FieldInfo, net.corda.core.node.services.vault.ColumnPredicate) - @NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression sum(reflect.Field) @NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression sum(reflect.Field, java.util.List) @@ -4203,12 +3689,6 @@ public final class net.corda.core.node.services.vault.Builder extends java.lang. public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression sum(reflect.Field, java.util.List, net.corda.core.node.services.vault.Sort$Direction) @NotNull public final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression sum(kotlin.reflect.KProperty1, java.util.List>, net.corda.core.node.services.vault.Sort$Direction) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression sum(net.corda.core.node.services.vault.FieldInfo) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression sum(net.corda.core.node.services.vault.FieldInfo, java.util.List) - @NotNull - public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression sum(net.corda.core.node.services.vault.FieldInfo, java.util.List, net.corda.core.node.services.vault.Sort$Direction) public static final net.corda.core.node.services.vault.Builder INSTANCE ## @DoNotImplement @@ -4223,12 +3703,10 @@ public final class net.corda.core.node.services.vault.Column extends java.lang.O public (String, Class) public (reflect.Field) public (kotlin.reflect.KProperty1) - public (net.corda.core.node.services.vault.FieldInfo) @NotNull public final Class getDeclaringClass() @NotNull public final String getName() - public static final net.corda.core.node.services.vault.Column$Companion Companion ## @CordaSerializable public abstract class net.corda.core.node.services.vault.ColumnPredicate extends java.lang.Object @@ -4244,7 +3722,6 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Agg @NotNull public final net.corda.core.node.services.vault.AggregateFunctionType getType() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -4262,7 +3739,6 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Bet @NotNull public final C getRightToLiteral() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -4280,7 +3756,6 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Bin @NotNull public final C getRightLiteral() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -4298,7 +3773,6 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Col @NotNull public final java.util.Collection getRightLiteral() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -4314,7 +3788,6 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Equ public final net.corda.core.node.services.vault.EqualityComparisonOperator getOperator() public final C getRightLiteral() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -4332,7 +3805,6 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Lik @NotNull public final String getRightLiteral() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -4346,7 +3818,6 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Nul @NotNull public final net.corda.core.node.services.vault.NullOperator getOperator() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -4375,7 +3846,6 @@ public static final class net.corda.core.node.services.vault.CriteriaExpression$ @NotNull public final net.corda.core.node.services.vault.ColumnPredicate getPredicate() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -4397,7 +3867,6 @@ public static final class net.corda.core.node.services.vault.CriteriaExpression$ @NotNull public final net.corda.core.node.services.vault.CriteriaExpression getRight() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -4415,7 +3884,6 @@ public static final class net.corda.core.node.services.vault.CriteriaExpression$ @NotNull public final net.corda.core.node.services.vault.ColumnPredicate getPredicate() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -4429,7 +3897,6 @@ public static final class net.corda.core.node.services.vault.CriteriaExpression$ @NotNull public final net.corda.core.node.services.vault.CriteriaExpression getExpression() public int hashCode() - @NotNull public String toString() ## @DoNotImplement @@ -4439,13 +3906,6 @@ public final class net.corda.core.node.services.vault.EqualityComparisonOperator public static net.corda.core.node.services.vault.EqualityComparisonOperator valueOf(String) public static net.corda.core.node.services.vault.EqualityComparisonOperator[] values() ## -public final class net.corda.core.node.services.vault.FieldInfo extends java.lang.Object - public (String, Class) - @NotNull - public final Class getEntityClass() - @NotNull - public final String getName() -## public interface net.corda.core.node.services.vault.GenericQueryCriteria @NotNull public abstract java.util.Collection visit(P) @@ -4516,7 +3976,6 @@ public final class net.corda.core.node.services.vault.PageSpecification extends public final int getPageSize() public int hashCode() public final boolean isDefault() - @NotNull public String toString() ## @CordaSerializable @@ -4539,15 +3998,9 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$AndCo @CordaSerializable public abstract static class net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria public () - @NotNull - public java.util.Set getConstraintTypes() - @NotNull - public java.util.Set getConstraints() @Nullable public abstract java.util.Set> getContractStateTypes() @NotNull - public net.corda.core.node.services.Vault$RelevancyStatus getRelevancyStatus() - @NotNull public abstract net.corda.core.node.services.Vault$StateStatus getStatus() @NotNull public java.util.Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) @@ -4562,7 +4015,6 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Fungi public (java.util.List, java.util.List, net.corda.core.node.services.vault.ColumnPredicate, java.util.List, java.util.List) public (java.util.List, java.util.List, net.corda.core.node.services.vault.ColumnPredicate, java.util.List, java.util.List, net.corda.core.node.services.Vault$StateStatus) public (java.util.List, java.util.List, net.corda.core.node.services.vault.ColumnPredicate, java.util.List, java.util.List, net.corda.core.node.services.Vault$StateStatus, java.util.Set>) - public (java.util.List, java.util.List, net.corda.core.node.services.vault.ColumnPredicate, java.util.List, java.util.List, net.corda.core.node.services.Vault$StateStatus, java.util.Set>, net.corda.core.node.services.Vault$RelevancyStatus) @Nullable public final java.util.List component1() @Nullable @@ -4578,11 +4030,7 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Fungi @Nullable public final java.util.Set> component7() @NotNull - public final net.corda.core.node.services.Vault$RelevancyStatus component8() - @NotNull public final net.corda.core.node.services.vault.QueryCriteria$FungibleAssetQueryCriteria copy(java.util.List, java.util.List, net.corda.core.node.services.vault.ColumnPredicate, java.util.List, java.util.List, net.corda.core.node.services.Vault$StateStatus, java.util.Set>) - @NotNull - public final net.corda.core.node.services.vault.QueryCriteria$FungibleAssetQueryCriteria copy(java.util.List, java.util.List, net.corda.core.node.services.vault.ColumnPredicate, java.util.List, java.util.List, net.corda.core.node.services.Vault$StateStatus, java.util.Set>, net.corda.core.node.services.Vault$RelevancyStatus) public boolean equals(Object) @Nullable public java.util.Set> getContractStateTypes() @@ -4597,44 +4045,8 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Fungi @Nullable public final net.corda.core.node.services.vault.ColumnPredicate getQuantity() @NotNull - public net.corda.core.node.services.Vault$RelevancyStatus getRelevancyStatus() - @NotNull public net.corda.core.node.services.Vault$StateStatus getStatus() public int hashCode() - @NotNull - public String toString() - @NotNull - public java.util.Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) -## -@CordaSerializable -public static final class net.corda.core.node.services.vault.QueryCriteria$FungibleStateQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria - public () - public (java.util.List, net.corda.core.node.services.vault.ColumnPredicate, net.corda.core.node.services.Vault$StateStatus, java.util.Set>, net.corda.core.node.services.Vault$RelevancyStatus) - @Nullable - public final java.util.List component1() - @Nullable - public final net.corda.core.node.services.vault.ColumnPredicate component2() - @NotNull - public final net.corda.core.node.services.Vault$StateStatus component3() - @Nullable - public final java.util.Set> component4() - @NotNull - public final net.corda.core.node.services.Vault$RelevancyStatus component5() - @NotNull - public final net.corda.core.node.services.vault.QueryCriteria$FungibleStateQueryCriteria copy(java.util.List, net.corda.core.node.services.vault.ColumnPredicate, net.corda.core.node.services.Vault$StateStatus, java.util.Set>, net.corda.core.node.services.Vault$RelevancyStatus) - public boolean equals(Object) - @Nullable - public java.util.Set> getContractStateTypes() - @Nullable - public final java.util.List getParticipants() - @Nullable - public final net.corda.core.node.services.vault.ColumnPredicate getQuantity() - @NotNull - public net.corda.core.node.services.Vault$RelevancyStatus getRelevancyStatus() - @NotNull - public net.corda.core.node.services.Vault$StateStatus getStatus() - public int hashCode() - @NotNull public String toString() @NotNull public java.util.Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) @@ -4647,9 +4059,7 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Linea public (java.util.List, java.util.List, java.util.List) public (java.util.List, java.util.List, java.util.List, net.corda.core.node.services.Vault$StateStatus) public (java.util.List, java.util.List, java.util.List, net.corda.core.node.services.Vault$StateStatus, java.util.Set>) - public (java.util.List, java.util.List, java.util.List, net.corda.core.node.services.Vault$StateStatus, java.util.Set>, net.corda.core.node.services.Vault$RelevancyStatus) public (java.util.List, java.util.List, net.corda.core.node.services.Vault$StateStatus, java.util.Set>) - public (java.util.List, java.util.List, net.corda.core.node.services.Vault$StateStatus, java.util.Set>, net.corda.core.node.services.Vault$RelevancyStatus) @Nullable public final java.util.List component1() @Nullable @@ -4661,11 +4071,7 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Linea @Nullable public final java.util.Set> component5() @NotNull - public final net.corda.core.node.services.Vault$RelevancyStatus component6() - @NotNull public final net.corda.core.node.services.vault.QueryCriteria$LinearStateQueryCriteria copy(java.util.List, java.util.List, java.util.List, net.corda.core.node.services.Vault$StateStatus, java.util.Set>) - @NotNull - public final net.corda.core.node.services.vault.QueryCriteria$LinearStateQueryCriteria copy(java.util.List, java.util.List, java.util.List, net.corda.core.node.services.Vault$StateStatus, java.util.Set>, net.corda.core.node.services.Vault$RelevancyStatus) public boolean equals(Object) @Nullable public java.util.Set> getContractStateTypes() @@ -4674,13 +4080,10 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Linea @Nullable public final java.util.List getParticipants() @NotNull - public net.corda.core.node.services.Vault$RelevancyStatus getRelevancyStatus() - @NotNull public net.corda.core.node.services.Vault$StateStatus getStatus() @Nullable public final java.util.List getUuid() public int hashCode() - @NotNull public String toString() @NotNull public java.util.Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) @@ -4710,7 +4113,6 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$SoftL @NotNull public final net.corda.core.node.services.vault.QueryCriteria$SoftLockingType getType() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -4734,7 +4136,6 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$TimeC @NotNull public final net.corda.core.node.services.vault.QueryCriteria$TimeInstantType getType() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -4748,7 +4149,6 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Vault public (net.corda.core.node.services.vault.CriteriaExpression) public (net.corda.core.node.services.vault.CriteriaExpression, net.corda.core.node.services.Vault$StateStatus) public (net.corda.core.node.services.vault.CriteriaExpression, net.corda.core.node.services.Vault$StateStatus, java.util.Set>) - public (net.corda.core.node.services.vault.CriteriaExpression, net.corda.core.node.services.Vault$StateStatus, java.util.Set>, net.corda.core.node.services.Vault$RelevancyStatus) @NotNull public final net.corda.core.node.services.vault.CriteriaExpression component1() @NotNull @@ -4756,22 +4156,15 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Vault @Nullable public final java.util.Set> component3() @NotNull - public final net.corda.core.node.services.Vault$RelevancyStatus component4() - @NotNull public final net.corda.core.node.services.vault.QueryCriteria$VaultCustomQueryCriteria copy(net.corda.core.node.services.vault.CriteriaExpression, net.corda.core.node.services.Vault$StateStatus, java.util.Set>) - @NotNull - public final net.corda.core.node.services.vault.QueryCriteria$VaultCustomQueryCriteria copy(net.corda.core.node.services.vault.CriteriaExpression, net.corda.core.node.services.Vault$StateStatus, java.util.Set>, net.corda.core.node.services.Vault$RelevancyStatus) public boolean equals(Object) @Nullable public java.util.Set> getContractStateTypes() @NotNull public final net.corda.core.node.services.vault.CriteriaExpression getExpression() @NotNull - public net.corda.core.node.services.Vault$RelevancyStatus getRelevancyStatus() - @NotNull public net.corda.core.node.services.Vault$StateStatus getStatus() public int hashCode() - @NotNull public String toString() @NotNull public java.util.Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) @@ -4785,9 +4178,6 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Vault public (net.corda.core.node.services.Vault$StateStatus, java.util.Set>, java.util.List, java.util.List) public (net.corda.core.node.services.Vault$StateStatus, java.util.Set>, java.util.List, java.util.List, net.corda.core.node.services.vault.QueryCriteria$SoftLockingCondition) public (net.corda.core.node.services.Vault$StateStatus, java.util.Set>, java.util.List, java.util.List, net.corda.core.node.services.vault.QueryCriteria$SoftLockingCondition, net.corda.core.node.services.vault.QueryCriteria$TimeCondition) - public (net.corda.core.node.services.Vault$StateStatus, java.util.Set>, java.util.List, java.util.List, net.corda.core.node.services.vault.QueryCriteria$SoftLockingCondition, net.corda.core.node.services.vault.QueryCriteria$TimeCondition, net.corda.core.node.services.Vault$RelevancyStatus) - public (net.corda.core.node.services.Vault$StateStatus, java.util.Set>, java.util.List, java.util.List, net.corda.core.node.services.vault.QueryCriteria$SoftLockingCondition, net.corda.core.node.services.vault.QueryCriteria$TimeCondition, net.corda.core.node.services.Vault$RelevancyStatus, java.util.Set) - public (net.corda.core.node.services.Vault$StateStatus, java.util.Set>, java.util.List, java.util.List, net.corda.core.node.services.vault.QueryCriteria$SoftLockingCondition, net.corda.core.node.services.vault.QueryCriteria$TimeCondition, net.corda.core.node.services.Vault$RelevancyStatus, java.util.Set, java.util.Set) @NotNull public final net.corda.core.node.services.Vault$StateStatus component1() @Nullable @@ -4801,26 +4191,12 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Vault @Nullable public final net.corda.core.node.services.vault.QueryCriteria$TimeCondition component6() @NotNull - public final net.corda.core.node.services.Vault$RelevancyStatus component7() - @NotNull - public final java.util.Set component8() - @NotNull - public final java.util.Set component9() - @NotNull public final net.corda.core.node.services.vault.QueryCriteria$VaultQueryCriteria copy(net.corda.core.node.services.Vault$StateStatus, java.util.Set>, java.util.List, java.util.List, net.corda.core.node.services.vault.QueryCriteria$SoftLockingCondition, net.corda.core.node.services.vault.QueryCriteria$TimeCondition) - @NotNull - public final net.corda.core.node.services.vault.QueryCriteria$VaultQueryCriteria copy(net.corda.core.node.services.Vault$StateStatus, java.util.Set>, java.util.List, java.util.List, net.corda.core.node.services.vault.QueryCriteria$SoftLockingCondition, net.corda.core.node.services.vault.QueryCriteria$TimeCondition, net.corda.core.node.services.Vault$RelevancyStatus, java.util.Set, java.util.Set) public boolean equals(Object) - @NotNull - public java.util.Set getConstraintTypes() - @NotNull - public java.util.Set getConstraints() @Nullable public java.util.Set> getContractStateTypes() @Nullable public final java.util.List getNotary() - @NotNull - public net.corda.core.node.services.Vault$RelevancyStatus getRelevancyStatus() @Nullable public final net.corda.core.node.services.vault.QueryCriteria$SoftLockingCondition getSoftLockingCondition() @Nullable @@ -4830,7 +4206,6 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Vault @Nullable public final net.corda.core.node.services.vault.QueryCriteria$TimeCondition getTimeCondition() public int hashCode() - @NotNull public String toString() @NotNull public java.util.Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) @@ -4840,8 +4215,6 @@ public final class net.corda.core.node.services.vault.QueryCriteriaUtils extends @NotNull public static final String getColumnName(net.corda.core.node.services.vault.Column) @NotNull - public static final net.corda.core.node.services.vault.FieldInfo getField(String, Class) - @NotNull public static final Class resolveEnclosingObjectFromColumn(net.corda.core.node.services.vault.Column) @NotNull public static final Class resolveEnclosingObjectFromExpression(net.corda.core.node.services.vault.CriteriaExpression) @@ -4860,7 +4233,6 @@ public final class net.corda.core.node.services.vault.Sort extends net.corda.cor @NotNull public final java.util.Collection getColumns() public int hashCode() - @NotNull public String toString() ## @DoNotImplement @@ -4917,7 +4289,6 @@ public static final class net.corda.core.node.services.vault.Sort$SortColumn ext @NotNull public final net.corda.core.node.services.vault.SortAttribute getSortAttribute() public int hashCode() - @NotNull public String toString() ## @DoNotImplement @@ -4947,7 +4318,6 @@ public static final class net.corda.core.node.services.vault.SortAttribute$Custo @NotNull public final String getEntityStateColumnName() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -4961,15 +4331,12 @@ public static final class net.corda.core.node.services.vault.SortAttribute$Stand @NotNull public final net.corda.core.node.services.vault.Sort$Attribute getAttribute() public int hashCode() - @NotNull public String toString() ## public final class net.corda.core.schemas.CommonSchema extends java.lang.Object public static final net.corda.core.schemas.CommonSchema INSTANCE ## public final class net.corda.core.schemas.CommonSchemaV1 extends net.corda.core.schemas.MappedSchema - @NotNull - public String getMigrationResource() public static final net.corda.core.schemas.CommonSchemaV1 INSTANCE ## @MappedSuperclass @@ -5010,34 +4377,14 @@ public static class net.corda.core.schemas.CommonSchemaV1$LinearState extends ne ## public class net.corda.core.schemas.MappedSchema extends java.lang.Object public (Class, int, Iterable>) - public boolean equals(Object) @NotNull public final Iterable> getMappedTypes() - @Nullable - public String getMigrationResource() @NotNull public final String getName() public final int getVersion() - public int hashCode() @NotNull public String toString() ## -public final class net.corda.core.schemas.MappedSchemaValidator extends java.lang.Object - @NotNull - public final java.util.List crossReferencesToOtherMappedSchema(net.corda.core.schemas.MappedSchema) - @NotNull - public final java.util.List fieldsFromOtherMappedSchema(net.corda.core.schemas.MappedSchema) - @NotNull - public final java.util.List methodsFromOtherMappedSchema(net.corda.core.schemas.MappedSchema) - public static final net.corda.core.schemas.MappedSchemaValidator INSTANCE -## -public static final class net.corda.core.schemas.MappedSchemaValidator$SchemaCrossReferenceReport extends java.lang.Object - public (String, String, String, String, String) - @NotNull - public String toString() - @NotNull - public final String toWarning() -## @MappedSuperclass @CordaSerializable public class net.corda.core.schemas.PersistentState extends java.lang.Object implements net.corda.core.schemas.StatePersistable @@ -5048,7 +4395,6 @@ public class net.corda.core.schemas.PersistentState extends java.lang.Object imp public void setStateRef(net.corda.core.schemas.PersistentStateRef) ## @Embeddable -@Immutable public class net.corda.core.schemas.PersistentStateRef extends java.lang.Object implements java.io.Serializable public () public (String, int) @@ -5065,7 +4411,6 @@ public class net.corda.core.schemas.PersistentStateRef extends java.lang.Object public int hashCode() public void setIndex(int) public void setTxId(String) - @NotNull public String toString() ## @CordaSerializable @@ -5082,11 +4427,6 @@ public interface net.corda.core.serialization.ClassWhitelist ## public @interface net.corda.core.serialization.ConstructorForDeserialization ## -public final class net.corda.core.serialization.ContextPropertyKeys extends java.lang.Enum - protected () - public static net.corda.core.serialization.ContextPropertyKeys valueOf(String) - public static net.corda.core.serialization.ContextPropertyKeys[] values() -## public @interface net.corda.core.serialization.CordaSerializable ## public @interface net.corda.core.serialization.CordaSerializationTransformEnumDefault @@ -5106,10 +4446,6 @@ public @interface net.corda.core.serialization.CordaSerializationTransformRename public @interface net.corda.core.serialization.DeprecatedConstructorForDeserialization public abstract int version() ## -@DoNotImplement -public interface net.corda.core.serialization.EncodingWhitelist - public abstract boolean acceptEncoding(net.corda.core.serialization.SerializationEncoding) -## @CordaSerializable public final class net.corda.core.serialization.MissingAttachmentsException extends net.corda.core.CordaException public (java.util.List) @@ -5130,24 +4466,15 @@ public final class net.corda.core.serialization.ObjectWithCompatibleContext exte @NotNull public final T getObj() public int hashCode() - @NotNull public String toString() ## -public @interface net.corda.core.serialization.SerializableCalculatedProperty -## public final class net.corda.core.serialization.SerializationAPIKt extends java.lang.Object @NotNull public static final net.corda.core.serialization.SerializedBytes serialize(T, net.corda.core.serialization.SerializationFactory, net.corda.core.serialization.SerializationContext) ## -@DoNotImplement public interface net.corda.core.serialization.SerializationContext @NotNull public abstract ClassLoader getDeserializationClassLoader() - @Nullable - public abstract net.corda.core.serialization.SerializationEncoding getEncoding() - @NotNull - public abstract net.corda.core.serialization.EncodingWhitelist getEncodingWhitelist() - public abstract boolean getLenientCarpenterEnabled() public abstract boolean getObjectReferencesEnabled() @NotNull public abstract net.corda.core.utilities.ByteSequence getPreferredSerializationVersion() @@ -5162,12 +4489,6 @@ public interface net.corda.core.serialization.SerializationContext @NotNull public abstract net.corda.core.serialization.SerializationContext withClassLoader(ClassLoader) @NotNull - public abstract net.corda.core.serialization.SerializationContext withEncoding(net.corda.core.serialization.SerializationEncoding) - @NotNull - public abstract net.corda.core.serialization.SerializationContext withEncodingWhitelist(net.corda.core.serialization.EncodingWhitelist) - @NotNull - public abstract net.corda.core.serialization.SerializationContext withLenientCarpenter() - @NotNull public abstract net.corda.core.serialization.SerializationContext withPreferredSerializationVersion(net.corda.core.utilities.ByteSequence) @NotNull public abstract net.corda.core.serialization.SerializationContext withProperty(Object, Object) @@ -5198,9 +4519,6 @@ public final class net.corda.core.serialization.SerializationDefaults extends ja public final net.corda.core.serialization.SerializationContext getSTORAGE_CONTEXT() public static final net.corda.core.serialization.SerializationDefaults INSTANCE ## -@DoNotImplement -public interface net.corda.core.serialization.SerializationEncoding -## public abstract class net.corda.core.serialization.SerializationFactory extends java.lang.Object public () public final T asCurrent(kotlin.jvm.functions.Function1) @@ -5248,9 +4566,6 @@ public final class net.corda.core.serialization.SerializedBytes extends net.cord public (byte[]) @NotNull public final net.corda.core.crypto.SecureHash getHash() - public static final net.corda.core.serialization.SerializedBytes$Companion Companion -## -public static final class net.corda.core.serialization.SerializedBytes$Companion extends java.lang.Object ## public final class net.corda.core.serialization.SingletonSerializationToken extends java.lang.Object implements net.corda.core.serialization.SerializationToken @NotNull @@ -5292,8 +4607,6 @@ public abstract class net.corda.core.transactions.BaseTransaction extends java.l @NotNull public abstract java.util.List> getOutputs() @NotNull - public abstract java.util.List getReferences() - @NotNull public final net.corda.core.contracts.StateAndRef outRef(int) @NotNull public final net.corda.core.contracts.StateAndRef outRef(net.corda.core.contracts.ContractState) @@ -5341,11 +4654,8 @@ public final class net.corda.core.transactions.ContractUpgradeFilteredTransactio @NotNull public java.util.List> getOutputs() @NotNull - public java.util.List getReferences() - @NotNull public final java.util.Map getVisibleComponents() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -5393,8 +4703,6 @@ public final class net.corda.core.transactions.ContractUpgradeLedgerTransaction @NotNull public final net.corda.core.contracts.PrivacySalt getPrivacySalt() @NotNull - public java.util.List> getReferences() - @NotNull public java.util.Set getRequiredSigningKeys() @NotNull public java.util.List getSigs() @@ -5403,7 +4711,6 @@ public final class net.corda.core.transactions.ContractUpgradeLedgerTransaction @NotNull public final String getUpgradedContractClassName() public int hashCode() - @NotNull public String toString() ## @DoNotImplement @@ -5432,8 +4739,6 @@ public final class net.corda.core.transactions.ContractUpgradeWireTransaction ex @NotNull public final net.corda.core.contracts.PrivacySalt getPrivacySalt() @NotNull - public java.util.List getReferences() - @NotNull public final java.util.List getSerializedComponents() @NotNull public final net.corda.core.crypto.SecureHash getUpgradedContractAttachmentId() @@ -5442,7 +4747,6 @@ public final class net.corda.core.transactions.ContractUpgradeWireTransaction ex public int hashCode() @NotNull public final net.corda.core.transactions.ContractUpgradeLedgerTransaction resolve(net.corda.core.node.ServicesForResolution, java.util.List) - @NotNull public String toString() ## public static final class net.corda.core.transactions.ContractUpgradeWireTransaction$Component extends java.lang.Enum @@ -5456,8 +4760,6 @@ public abstract class net.corda.core.transactions.CoreTransaction extends net.co public () @NotNull public abstract java.util.List getInputs() - @NotNull - public abstract java.util.List getReferences() ## @CordaSerializable public final class net.corda.core.transactions.FilteredComponentGroup extends net.corda.core.transactions.ComponentGroup @@ -5480,7 +4782,6 @@ public final class net.corda.core.transactions.FilteredComponentGroup extends ne @NotNull public final net.corda.core.crypto.PartialMerkleTree getPartialMerkleTree() public int hashCode() - @NotNull public String toString() ## @DoNotImplement @@ -5519,22 +4820,17 @@ public abstract class net.corda.core.transactions.FullTransaction extends net.co protected void checkBaseInvariants() @NotNull public abstract java.util.List> getInputs() - @NotNull - public abstract java.util.List> getReferences() ## @DoNotImplement @CordaSerializable public final class net.corda.core.transactions.LedgerTransaction extends net.corda.core.transactions.FullTransaction public (java.util.List>, java.util.List>, java.util.List>, java.util.List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt) public (java.util.List>, java.util.List>, java.util.List>, java.util.List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt, net.corda.core.node.NetworkParameters) - public (java.util.List>, java.util.List>, java.util.List>, java.util.List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt, net.corda.core.node.NetworkParameters, java.util.List>) @NotNull public final java.util.List> commandsOfType(Class) @NotNull public final java.util.List> component1() @NotNull - public final java.util.List> component10() - @NotNull public final java.util.List> component2() @NotNull public final java.util.List> component3() @@ -5549,27 +4845,9 @@ public final class net.corda.core.transactions.LedgerTransaction extends net.cor @NotNull public final net.corda.core.contracts.PrivacySalt component8() @NotNull - public final net.corda.core.transactions.LedgerTransaction copy() - @NotNull - public final net.corda.core.transactions.LedgerTransaction copy(java.util.List>) - @NotNull - public final net.corda.core.transactions.LedgerTransaction copy(java.util.List>, java.util.List>) - @NotNull - public final net.corda.core.transactions.LedgerTransaction copy(java.util.List>, java.util.List>, java.util.List>) - @NotNull - public final net.corda.core.transactions.LedgerTransaction copy(java.util.List>, java.util.List>, java.util.List>, java.util.List) - @NotNull - public final net.corda.core.transactions.LedgerTransaction copy(java.util.List>, java.util.List>, java.util.List>, java.util.List, net.corda.core.crypto.SecureHash) - @NotNull - public final net.corda.core.transactions.LedgerTransaction copy(java.util.List>, java.util.List>, java.util.List>, java.util.List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party) - @NotNull - public final net.corda.core.transactions.LedgerTransaction copy(java.util.List>, java.util.List>, java.util.List>, java.util.List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow) - @NotNull public final net.corda.core.transactions.LedgerTransaction copy(java.util.List>, java.util.List>, java.util.List>, java.util.List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt) @NotNull public final net.corda.core.transactions.LedgerTransaction copy(java.util.List>, java.util.List>, java.util.List>, java.util.List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt, net.corda.core.node.NetworkParameters) - @NotNull - public final net.corda.core.transactions.LedgerTransaction copy(java.util.List>, java.util.List>, java.util.List>, java.util.List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt, net.corda.core.node.NetworkParameters, java.util.List>) public boolean equals(Object) @NotNull public final java.util.List> filterCommands(Class, java.util.function.Predicate) @@ -5578,20 +4856,12 @@ public final class net.corda.core.transactions.LedgerTransaction extends net.cor @NotNull public final java.util.List filterInputs(Class, java.util.function.Predicate) @NotNull - public final java.util.List> filterReferenceInputRefs(Class, java.util.function.Predicate) - @NotNull - public final java.util.List filterReferenceInputs(Class, java.util.function.Predicate) - @NotNull public final net.corda.core.contracts.Command findCommand(Class, java.util.function.Predicate) @NotNull public final net.corda.core.contracts.StateAndRef findInRef(Class, java.util.function.Predicate) @NotNull public final T findInput(Class, java.util.function.Predicate) @NotNull - public final T findReference(Class, java.util.function.Predicate) - @NotNull - public final net.corda.core.contracts.StateAndRef findReferenceInputRef(Class, java.util.function.Predicate) - @NotNull public final net.corda.core.contracts.Attachment getAttachment(int) @NotNull public final net.corda.core.contracts.Attachment getAttachment(net.corda.core.crypto.SecureHash) @@ -5615,12 +4885,6 @@ public final class net.corda.core.transactions.LedgerTransaction extends net.cor public java.util.List> getOutputs() @NotNull public final net.corda.core.contracts.PrivacySalt getPrivacySalt() - @NotNull - public final net.corda.core.contracts.ContractState getReferenceInput(int) - @NotNull - public final java.util.List getReferenceStates() - @NotNull - public java.util.List> getReferences() @Nullable public final net.corda.core.contracts.TimeWindow getTimeWindow() @NotNull @@ -5632,11 +4896,6 @@ public final class net.corda.core.transactions.LedgerTransaction extends net.cor public final java.util.List> inRefsOfType(Class) @NotNull public final java.util.List inputsOfType(Class) - @NotNull - public final java.util.List> referenceInputRefsOfType(Class) - @NotNull - public final java.util.List referenceInputsOfType(Class) - @NotNull public String toString() public final void verify() public static final net.corda.core.transactions.LedgerTransaction$Companion Companion @@ -5659,7 +4918,6 @@ public static final class net.corda.core.transactions.LedgerTransaction$InOutGro @NotNull public final java.util.List getOutputs() public int hashCode() - @NotNull public String toString() ## @CordaSerializable @@ -5697,13 +4955,10 @@ public final class net.corda.core.transactions.NotaryChangeLedgerTransaction ext @NotNull public java.util.List> getOutputs() @NotNull - public java.util.List> getReferences() - @NotNull public java.util.Set getRequiredSigningKeys() @NotNull public java.util.List getSigs() public int hashCode() - @NotNull public String toString() ## @DoNotImplement @@ -5727,15 +4982,12 @@ public final class net.corda.core.transactions.NotaryChangeWireTransaction exten @NotNull public java.util.List> getOutputs() @NotNull - public java.util.List getReferences() - @NotNull public final java.util.List getSerializedComponents() public int hashCode() @NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolve(net.corda.core.node.ServiceHub, java.util.List) @NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolve(net.corda.core.node.ServicesForResolution, java.util.List) - @NotNull public String toString() ## public static final class net.corda.core.transactions.NotaryChangeWireTransaction$Component extends java.lang.Enum @@ -5770,8 +5022,6 @@ public final class net.corda.core.transactions.SignedTransaction extends java.la @NotNull public final net.corda.core.transactions.NotaryChangeWireTransaction getNotaryChangeTx() @NotNull - public final java.util.List getReferences() - @NotNull public java.util.Set getRequiredSigningKeys() @NotNull public java.util.List getSigs() @@ -5832,15 +5082,7 @@ public static final class net.corda.core.transactions.SignedTransaction$Signatur public class net.corda.core.transactions.TransactionBuilder extends java.lang.Object public () public (net.corda.core.identity.Party) - public (net.corda.core.identity.Party, java.util.UUID) - public (net.corda.core.identity.Party, java.util.UUID, java.util.List) - public (net.corda.core.identity.Party, java.util.UUID, java.util.List, java.util.List) - public (net.corda.core.identity.Party, java.util.UUID, java.util.List, java.util.List, java.util.List>) - public (net.corda.core.identity.Party, java.util.UUID, java.util.List, java.util.List, java.util.List>, java.util.List>) - public (net.corda.core.identity.Party, java.util.UUID, java.util.List, java.util.List, java.util.List>, java.util.List>, net.corda.core.contracts.TimeWindow) public (net.corda.core.identity.Party, java.util.UUID, java.util.List, java.util.List, java.util.List>, java.util.List>, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt) - public (net.corda.core.identity.Party, java.util.UUID, java.util.List, java.util.List, java.util.List>, java.util.List>, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt, java.util.List) - public (net.corda.core.identity.Party, java.util.UUID, java.util.List, java.util.List, java.util.List>, java.util.List>, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt, java.util.List, net.corda.core.node.ServiceHub) @NotNull public final net.corda.core.transactions.TransactionBuilder addAttachment(net.corda.core.crypto.SecureHash) @NotNull @@ -5852,8 +5094,6 @@ public class net.corda.core.transactions.TransactionBuilder extends java.lang.Ob @NotNull public net.corda.core.transactions.TransactionBuilder addInputState(net.corda.core.contracts.StateAndRef) @NotNull - public final net.corda.core.transactions.TransactionBuilder addOutputState(net.corda.core.contracts.ContractState) - @NotNull public final net.corda.core.transactions.TransactionBuilder addOutputState(net.corda.core.contracts.ContractState, String) @NotNull public final net.corda.core.transactions.TransactionBuilder addOutputState(net.corda.core.contracts.ContractState, String, net.corda.core.contracts.AttachmentConstraint) @@ -5864,12 +5104,8 @@ public class net.corda.core.transactions.TransactionBuilder extends java.lang.Ob @NotNull public final net.corda.core.transactions.TransactionBuilder addOutputState(net.corda.core.contracts.ContractState, String, net.corda.core.identity.Party, Integer, net.corda.core.contracts.AttachmentConstraint) @NotNull - public final net.corda.core.transactions.TransactionBuilder addOutputState(net.corda.core.contracts.ContractState, net.corda.core.identity.Party) - @NotNull public final net.corda.core.transactions.TransactionBuilder addOutputState(net.corda.core.contracts.TransactionState) @NotNull - public net.corda.core.transactions.TransactionBuilder addReferenceState(net.corda.core.contracts.ReferencedStateAndRef) - @NotNull public final java.util.List attachments() @NotNull public final java.util.List> commands() @@ -5889,18 +5125,12 @@ public class net.corda.core.transactions.TransactionBuilder extends java.lang.Ob protected final java.util.List> getOutputs() @NotNull protected final net.corda.core.contracts.PrivacySalt getPrivacySalt() - @NotNull - protected final java.util.List getReferences() - @Nullable - protected final net.corda.core.node.ServiceHub getServiceHub() @Nullable protected final net.corda.core.contracts.TimeWindow getWindow() @NotNull public final java.util.List inputStates() @NotNull public final java.util.List> outputStates() - @NotNull - public final java.util.List referenceStates() public final void setLockId(java.util.UUID) public final void setNotary(net.corda.core.identity.Party) @NotNull @@ -5920,7 +5150,6 @@ public class net.corda.core.transactions.TransactionBuilder extends java.lang.Ob public final void verify(net.corda.core.node.ServiceHub) @NotNull public final net.corda.core.transactions.TransactionBuilder withItems(Object...) - public static final net.corda.core.transactions.TransactionBuilder$Companion Companion ## @DoNotImplement public interface net.corda.core.transactions.TransactionWithSignatures extends net.corda.core.contracts.NamedByHash @@ -5955,16 +5184,12 @@ public abstract class net.corda.core.transactions.TraversableTransaction extends public net.corda.core.identity.Party getNotary() @NotNull public java.util.List> getOutputs() - @NotNull - public java.util.List getReferences() @Nullable public final net.corda.core.contracts.TimeWindow getTimeWindow() ## @DoNotImplement @CordaSerializable public final class net.corda.core.transactions.WireTransaction extends net.corda.core.transactions.TraversableTransaction - public (java.util.List) - public (java.util.List, java.util.List, java.util.List>, java.util.List>, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow) public (java.util.List, java.util.List, java.util.List>, java.util.List>, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt) public (java.util.List, net.corda.core.contracts.PrivacySalt) @NotNull @@ -6003,8 +5228,6 @@ public abstract class net.corda.core.utilities.ByteSequence extends java.lang.Ob public int compareTo(net.corda.core.utilities.ByteSequence) @NotNull public final net.corda.core.utilities.ByteSequence copy() - @NotNull - public final byte[] copyBytes() public boolean equals(Object) @NotNull public abstract byte[] getBytes() @@ -6020,16 +5243,11 @@ public abstract class net.corda.core.utilities.ByteSequence extends java.lang.Ob @NotNull public final java.io.ByteArrayInputStream open() @NotNull - public final java.nio.ByteBuffer putTo(java.nio.ByteBuffer) - @NotNull - public final java.nio.ByteBuffer slice(int, int) - @NotNull public final net.corda.core.utilities.ByteSequence subSequence(int, int) @NotNull public final net.corda.core.utilities.ByteSequence take(int) @NotNull public String toString() - public final void writeTo(java.io.OutputStream) public static final net.corda.core.utilities.ByteSequence$Companion Companion ## public static final class net.corda.core.utilities.ByteSequence$Companion extends java.lang.Object @@ -6106,8 +5324,6 @@ public final class net.corda.core.utilities.KotlinUtilsKt extends java.lang.Obje public static final int exactAdd(int, int) public static final long exactAdd(long, long) @NotNull - public static final java.util.Map filterNotNullValues(java.util.Map) - @NotNull public static final java.time.Duration getDays(int) @NotNull public static final java.time.Duration getHours(int) @@ -6119,13 +5335,10 @@ public final class net.corda.core.utilities.KotlinUtilsKt extends java.lang.Obje @NotNull public static final java.time.Duration getSeconds(int) @NotNull - public static final java.util.List lazyMapped(java.util.List, kotlin.jvm.functions.Function2) - @NotNull public static final net.corda.core.utilities.NonEmptySet toNonEmptySet(java.util.Collection) public static final void trace(org.slf4j.Logger, kotlin.jvm.functions.Function0) @NotNull public static final net.corda.core.utilities.PropertyDelegate transient(kotlin.jvm.functions.Function0) - public static final synchronized void warnOnce(org.slf4j.Logger, String) ## @CordaSerializable public final class net.corda.core.utilities.NetworkHostAndPort extends java.lang.Object @@ -6251,7 +5464,6 @@ public final class net.corda.core.utilities.ProgressTracker extends java.lang.Ob public final net.corda.core.utilities.ProgressTracker$Step nextStep() public final void setChildProgressTracker(net.corda.core.utilities.ProgressTracker$Step, net.corda.core.utilities.ProgressTracker) public final void setCurrentStep(net.corda.core.utilities.ProgressTracker$Step) - public static final net.corda.core.utilities.ProgressTracker$Companion Companion ## @CordaSerializable public abstract static class net.corda.core.utilities.ProgressTracker$Change extends java.lang.Object @@ -6312,21 +5524,12 @@ public static final class net.corda.core.utilities.ProgressTracker$Change$Struct @NotNull public String toString() ## -public static final class net.corda.core.utilities.ProgressTracker$Companion extends java.lang.Object - @NotNull - public final kotlin.jvm.functions.Function0 getDEFAULT_TRACKER() -## @CordaSerializable public static final class net.corda.core.utilities.ProgressTracker$DONE extends net.corda.core.utilities.ProgressTracker$Step public boolean equals(Object) public static final net.corda.core.utilities.ProgressTracker$DONE INSTANCE ## @CordaSerializable -public static final class net.corda.core.utilities.ProgressTracker$STARTING extends net.corda.core.utilities.ProgressTracker$Step - public boolean equals(Object) - public static final net.corda.core.utilities.ProgressTracker$STARTING INSTANCE -## -@CordaSerializable public static class net.corda.core.utilities.ProgressTracker$Step extends java.lang.Object public (String) @Nullable @@ -6346,21 +5549,11 @@ public static final class net.corda.core.utilities.ProgressTracker$UNSTARTED ext public interface net.corda.core.utilities.PropertyDelegate public abstract T getValue(Object, kotlin.reflect.KProperty) ## -public final class net.corda.core.utilities.SgxSupport extends java.lang.Object - public static final boolean isInsideEnclave() - public static final net.corda.core.utilities.SgxSupport INSTANCE -## @CordaSerializable public abstract class net.corda.core.utilities.Try extends java.lang.Object @NotNull public final net.corda.core.utilities.Try combine(net.corda.core.utilities.Try, kotlin.jvm.functions.Function2) @NotNull - public final net.corda.core.utilities.Try doOnException(kotlin.jvm.functions.Function1) - @NotNull - public final net.corda.core.utilities.Try doOnFailure(kotlin.jvm.functions.Function1) - @NotNull - public final net.corda.core.utilities.Try doOnSuccess(kotlin.jvm.functions.Function1) - @NotNull public final net.corda.core.utilities.Try flatMap(kotlin.jvm.functions.Function1>) public abstract A getOrThrow() public abstract boolean isFailure() @@ -6439,8 +5632,6 @@ public final class net.corda.client.jackson.JacksonSupport extends java.lang.Obj @NotNull public static final com.fasterxml.jackson.databind.ObjectMapper createDefaultMapper(net.corda.core.messaging.CordaRPCOps, com.fasterxml.jackson.core.JsonFactory, boolean) @NotNull - public static final com.fasterxml.jackson.databind.ObjectMapper createDefaultMapper(net.corda.core.messaging.CordaRPCOps, com.fasterxml.jackson.core.JsonFactory, boolean, boolean) - @NotNull public static final com.fasterxml.jackson.databind.ObjectMapper createInMemoryMapper(net.corda.core.node.services.IdentityService) @NotNull public static final com.fasterxml.jackson.databind.ObjectMapper createInMemoryMapper(net.corda.core.node.services.IdentityService, com.fasterxml.jackson.core.JsonFactory) @@ -6451,8 +5642,6 @@ public final class net.corda.client.jackson.JacksonSupport extends java.lang.Obj @NotNull public static final com.fasterxml.jackson.databind.ObjectMapper createNonRpcMapper(com.fasterxml.jackson.core.JsonFactory) @NotNull - public static final com.fasterxml.jackson.databind.ObjectMapper createNonRpcMapper(com.fasterxml.jackson.core.JsonFactory, boolean) - @NotNull public final com.fasterxml.jackson.databind.Module getCordaModule() public static final net.corda.client.jackson.JacksonSupport INSTANCE ## @@ -6483,16 +5672,11 @@ public static final class net.corda.client.jackson.JacksonSupport$CordaX500NameS public void serialize(net.corda.core.identity.CordaX500Name, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider) public static final net.corda.client.jackson.JacksonSupport$CordaX500NameSerializer INSTANCE ## -@DoNotImplement public static final class net.corda.client.jackson.JacksonSupport$IdentityObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper implements net.corda.client.jackson.JacksonSupport$PartyObjectMapper public (net.corda.core.node.services.IdentityService, com.fasterxml.jackson.core.JsonFactory, boolean) - public (net.corda.core.node.services.IdentityService, com.fasterxml.jackson.core.JsonFactory, boolean, boolean) public final boolean getFuzzyIdentityMatch() @NotNull public final net.corda.core.node.services.IdentityService getIdentityService() - public boolean isFullParties() - @Nullable - public net.corda.core.node.NodeInfo nodeInfoFromParty(net.corda.core.identity.AbstractParty) @NotNull public java.util.Set partiesFromName(String) @Nullable @@ -6503,10 +5687,6 @@ public static final class net.corda.client.jackson.JacksonSupport$IdentityObject @DoNotImplement public static final class net.corda.client.jackson.JacksonSupport$NoPartyObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper implements net.corda.client.jackson.JacksonSupport$PartyObjectMapper public (com.fasterxml.jackson.core.JsonFactory) - public (com.fasterxml.jackson.core.JsonFactory, boolean) - public boolean isFullParties() - @Nullable - public net.corda.core.node.NodeInfo nodeInfoFromParty(net.corda.core.identity.AbstractParty) @NotNull public java.util.Set partiesFromName(String) @Nullable @@ -6537,11 +5717,7 @@ public static final class net.corda.client.jackson.JacksonSupport$PartyDeseriali public net.corda.core.identity.Party deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext) public static final net.corda.client.jackson.JacksonSupport$PartyDeserializer INSTANCE ## -@DoNotImplement public static interface net.corda.client.jackson.JacksonSupport$PartyObjectMapper - public abstract boolean isFullParties() - @Nullable - public abstract net.corda.core.node.NodeInfo nodeInfoFromParty(net.corda.core.identity.AbstractParty) @NotNull public abstract java.util.Set partiesFromName(String) @Nullable @@ -6562,16 +5738,11 @@ public static final class net.corda.client.jackson.JacksonSupport$PublicKeySeria public void serialize(java.security.PublicKey, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider) public static final net.corda.client.jackson.JacksonSupport$PublicKeySerializer INSTANCE ## -@DoNotImplement public static final class net.corda.client.jackson.JacksonSupport$RpcObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper implements net.corda.client.jackson.JacksonSupport$PartyObjectMapper public (net.corda.core.messaging.CordaRPCOps, com.fasterxml.jackson.core.JsonFactory, boolean) - public (net.corda.core.messaging.CordaRPCOps, com.fasterxml.jackson.core.JsonFactory, boolean, boolean) public final boolean getFuzzyIdentityMatch() @NotNull public final net.corda.core.messaging.CordaRPCOps getRpc() - public boolean isFullParties() - @Nullable - public net.corda.core.node.NodeInfo nodeInfoFromParty(net.corda.core.identity.AbstractParty) @NotNull public java.util.Set partiesFromName(String) @Nullable @@ -6713,8 +5884,6 @@ public interface net.corda.testing.driver.DriverDSL @NotNull public abstract net.corda.core.concurrent.CordaFuture startNode(net.corda.testing.driver.NodeParameters, net.corda.core.identity.CordaX500Name, java.util.List, net.corda.testing.driver.VerifierType, java.util.Map, Boolean, String) @NotNull - public abstract net.corda.core.concurrent.CordaFuture startNode(net.corda.testing.driver.NodeParameters, net.corda.core.identity.CordaX500Name, java.util.List, net.corda.testing.driver.VerifierType, java.util.Map, Boolean, String, java.util.Collection, boolean, java.util.Map>, ? extends Class>>) - @NotNull public abstract net.corda.core.concurrent.CordaFuture startWebserver(net.corda.testing.driver.NodeHandle) @NotNull public abstract net.corda.core.concurrent.CordaFuture startWebserver(net.corda.testing.driver.NodeHandle, String) @@ -6722,11 +5891,8 @@ public interface net.corda.testing.driver.DriverDSL public final class net.corda.testing.driver.DriverParameters extends java.lang.Object public () public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters) - public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Collection) public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Map, boolean, boolean) - public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Map, boolean, boolean, java.util.Collection, boolean) public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, boolean, boolean) - public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, boolean, boolean, java.util.Set) public final boolean component1() @NotNull public final java.util.List component10() @@ -6735,13 +5901,6 @@ public final class net.corda.testing.driver.DriverParameters extends java.lang.O @NotNull public final net.corda.core.node.NetworkParameters component12() @NotNull - public final java.util.Map component13() - public final boolean component14() - public final boolean component15() - @Nullable - public final java.util.Collection component16() - public final boolean component17() - @NotNull public final java.nio.file.Path component2() @NotNull public final net.corda.testing.driver.PortAllocation component3() @@ -6756,33 +5915,18 @@ public final class net.corda.testing.driver.DriverParameters extends java.lang.O public final java.util.List component9() @NotNull public final net.corda.testing.driver.DriverParameters copy(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters) - @NotNull - public final net.corda.testing.driver.DriverParameters copy(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Map, boolean, boolean, java.util.Collection, boolean) - @NotNull - public final net.corda.testing.driver.DriverParameters copy(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Set) - @NotNull - public final net.corda.testing.driver.DriverParameters copy(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, boolean) - @NotNull - public final net.corda.testing.driver.DriverParameters copy(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, boolean, java.util.Set) public boolean equals(Object) - @Nullable - public final java.util.Collection getCordappsForAllNodes() @NotNull public final net.corda.testing.driver.PortAllocation getDebugPortAllocation() @NotNull public final java.nio.file.Path getDriverDirectory() - public final boolean getEnableSNI() @NotNull public final java.util.List getExtraCordappPackagesToScan() - public final boolean getInMemoryDB() - public final boolean getInitialiseSerialization() @NotNull public final net.corda.testing.driver.JmxPolicy getJmxPolicy() @NotNull public final net.corda.core.node.NetworkParameters getNetworkParameters() @NotNull - public final java.util.Map getNotaryCustomOverrides() - @NotNull public final java.util.List getNotarySpecs() @NotNull public final net.corda.testing.driver.PortAllocation getPortAllocation() @@ -6793,29 +5937,20 @@ public final class net.corda.testing.driver.DriverParameters extends java.lang.O public final boolean getWaitForAllNodesToFinish() public int hashCode() public final boolean isDebug() - @NotNull public String toString() @NotNull - public final net.corda.testing.driver.DriverParameters withCordappsForAllNodes(java.util.Collection) - @NotNull public final net.corda.testing.driver.DriverParameters withDebugPortAllocation(net.corda.testing.driver.PortAllocation) @NotNull public final net.corda.testing.driver.DriverParameters withDriverDirectory(java.nio.file.Path) @NotNull public final net.corda.testing.driver.DriverParameters withExtraCordappPackagesToScan(java.util.List) @NotNull - public final net.corda.testing.driver.DriverParameters withInMemoryDB(boolean) - @NotNull - public final net.corda.testing.driver.DriverParameters withInitialiseSerialization(boolean) - @NotNull public final net.corda.testing.driver.DriverParameters withIsDebug(boolean) @NotNull public final net.corda.testing.driver.DriverParameters withJmxPolicy(net.corda.testing.driver.JmxPolicy) @NotNull public final net.corda.testing.driver.DriverParameters withNetworkParameters(net.corda.core.node.NetworkParameters) @NotNull - public final net.corda.testing.driver.DriverParameters withNotaryCustomOverrides(java.util.Map) - @NotNull public final net.corda.testing.driver.DriverParameters withNotarySpecs(java.util.List) @NotNull public final net.corda.testing.driver.DriverParameters withPortAllocation(net.corda.testing.driver.PortAllocation) @@ -6850,7 +5985,6 @@ public final class net.corda.testing.driver.JmxPolicy extends java.lang.Object public final net.corda.testing.driver.PortAllocation getJmxHttpServerPortAllocation() public final boolean getStartJmxHttpServer() public int hashCode() - @NotNull public String toString() ## @DoNotImplement @@ -6866,22 +6000,15 @@ public interface net.corda.testing.driver.NodeHandle extends java.lang.AutoClose @NotNull public abstract net.corda.core.utilities.NetworkHostAndPort getRpcAddress() @NotNull - public abstract net.corda.core.utilities.NetworkHostAndPort getRpcAdminAddress() - @NotNull public abstract java.util.List getRpcUsers() public abstract void stop() ## public final class net.corda.testing.driver.NodeParameters extends java.lang.Object public () public (net.corda.core.identity.CordaX500Name, java.util.List, net.corda.testing.driver.VerifierType, java.util.Map, Boolean, String) - public (net.corda.core.identity.CordaX500Name, java.util.List, net.corda.testing.driver.VerifierType, java.util.Map, Boolean, String, String) - public (net.corda.core.identity.CordaX500Name, java.util.List, net.corda.testing.driver.VerifierType, java.util.Map, Boolean, String, String, java.util.Collection, boolean, java.util.Map>, ? extends Class>>) - public (net.corda.core.identity.CordaX500Name, java.util.List, net.corda.testing.driver.VerifierType, java.util.Map, Boolean, String, java.util.Set, boolean) @Nullable public final net.corda.core.identity.CordaX500Name component1() @NotNull - public final java.util.Map>, Class>> component10() - @NotNull public final java.util.List component2() @NotNull public final net.corda.testing.driver.VerifierType component3() @@ -6891,31 +6018,15 @@ public final class net.corda.testing.driver.NodeParameters extends java.lang.Obj public final Boolean component5() @NotNull public final String component6() - @Nullable - public final String component7() - @NotNull - public final java.util.Collection component8() - public final boolean component9() @NotNull public final net.corda.testing.driver.NodeParameters copy(net.corda.core.identity.CordaX500Name, java.util.List, net.corda.testing.driver.VerifierType, java.util.Map, Boolean, String) - @NotNull - public final net.corda.testing.driver.NodeParameters copy(net.corda.core.identity.CordaX500Name, java.util.List, net.corda.testing.driver.VerifierType, java.util.Map, Boolean, String, String) - @NotNull - public final net.corda.testing.driver.NodeParameters copy(net.corda.core.identity.CordaX500Name, java.util.List, net.corda.testing.driver.VerifierType, java.util.Map, Boolean, String, String, java.util.Collection, boolean, java.util.Map>, ? extends Class>>) public boolean equals(Object) @NotNull - public final java.util.Collection getAdditionalCordapps() - @NotNull public final java.util.Map getCustomOverrides() @NotNull - public final java.util.Map>, Class>> getFlowOverrides() - @Nullable - public final String getLogLevel() - @NotNull public final String getMaximumHeapSize() @Nullable public final net.corda.core.identity.CordaX500Name getProvidedName() - public final boolean getRegenerateCordappsOnStart() @NotNull public final java.util.List getRpcUsers() @Nullable @@ -6923,17 +6034,10 @@ public final class net.corda.testing.driver.NodeParameters extends java.lang.Obj @NotNull public final net.corda.testing.driver.VerifierType getVerifierType() public int hashCode() - @NotNull public String toString() @NotNull - public final net.corda.testing.driver.NodeParameters withAdditionalCordapps(java.util.Set) - @NotNull public final net.corda.testing.driver.NodeParameters withCustomOverrides(java.util.Map) @NotNull - public final net.corda.testing.driver.NodeParameters withDeleteExistingCordappsDirectory(boolean) - @NotNull - public final net.corda.testing.driver.NodeParameters withLogLevel(String) - @NotNull public final net.corda.testing.driver.NodeParameters withMaximumHeapSize(String) @NotNull public final net.corda.testing.driver.NodeParameters withProvidedName(net.corda.core.identity.CordaX500Name) @@ -6960,7 +6064,6 @@ public final class net.corda.testing.driver.NotaryHandle extends java.lang.Objec public final net.corda.core.concurrent.CordaFuture> getNodeHandles() public final boolean getValidating() public int hashCode() - @NotNull public String toString() ## @DoNotImplement @@ -7001,7 +6104,6 @@ public final class net.corda.testing.driver.WebserverHandle extends java.lang.Ob @NotNull public final Process getProcess() public int hashCode() - @NotNull public String toString() ## @DoNotImplement @@ -7018,7 +6120,6 @@ public static final class net.corda.testing.node.ClusterSpec$Raft extends net.co public boolean equals(Object) public int getClusterSize() public int hashCode() - @NotNull public String toString() ## @ThreadSafe @@ -7108,51 +6209,10 @@ public static final class net.corda.testing.node.InMemoryMessagingNetwork$Servic public () public A pickNext(net.corda.testing.node.InMemoryMessagingNetwork$DistributedServiceHandle, java.util.List) ## -public final class net.corda.testing.node.MockNetFlowTimeOut extends java.lang.Object - public (java.time.Duration, int, double) - @NotNull - public final java.time.Duration component1() - public final int component2() - public final double component3() - @NotNull - public final net.corda.testing.node.MockNetFlowTimeOut copy(java.time.Duration, int, double) - public boolean equals(Object) - public final double getBackoffBase() - public final int getMaxRestartCount() - @NotNull - public final java.time.Duration getTimeout() - public int hashCode() - @NotNull - public String toString() -## -public final class net.corda.testing.node.MockNetNotaryConfig extends java.lang.Object - public (boolean, com.typesafe.config.Config, String, net.corda.core.identity.CordaX500Name) - public final boolean component1() - @Nullable - public final com.typesafe.config.Config component2() - @Nullable - public final String component3() - @Nullable - public final net.corda.core.identity.CordaX500Name component4() - @NotNull - public final net.corda.testing.node.MockNetNotaryConfig copy(boolean, com.typesafe.config.Config, String, net.corda.core.identity.CordaX500Name) - public boolean equals(Object) - @Nullable - public final String getClassName() - @Nullable - public final com.typesafe.config.Config getExtraConfig() - @Nullable - public final net.corda.core.identity.CordaX500Name getServiceLegalName() - public final boolean getValidating() - public int hashCode() - @NotNull - public String toString() -## public class net.corda.testing.node.MockNetwork extends java.lang.Object public (java.util.List) public (java.util.List, net.corda.testing.node.MockNetworkParameters) public (java.util.List, net.corda.testing.node.MockNetworkParameters, boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, java.util.List, net.corda.core.node.NetworkParameters) - public (java.util.List, net.corda.testing.node.MockNetworkParameters, boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, java.util.List, net.corda.core.node.NetworkParameters, java.util.Collection) @NotNull public final java.nio.file.Path baseDirectory(int) @NotNull @@ -7164,12 +6224,6 @@ public class net.corda.testing.node.MockNetwork extends java.lang.Object @NotNull public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger) @NotNull - public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, net.corda.testing.node.MockNodeConfigOverrides) - @NotNull - public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, net.corda.testing.node.MockNodeConfigOverrides, java.util.Collection) - @NotNull - public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, net.corda.testing.node.MockNodeConfigOverrides, java.util.List) - @NotNull public final net.corda.testing.node.StartedMockNode createNode(net.corda.testing.node.MockNodeParameters) @NotNull public final net.corda.testing.node.StartedMockNode createPartyNode(net.corda.core.identity.CordaX500Name) @@ -7182,18 +6236,10 @@ public class net.corda.testing.node.MockNetwork extends java.lang.Object @NotNull public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger) @NotNull - public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, net.corda.testing.node.MockNodeConfigOverrides) - @NotNull - public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, net.corda.testing.node.MockNodeConfigOverrides, java.util.Collection) - @NotNull - public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, net.corda.testing.node.MockNodeConfigOverrides, java.util.List) - @NotNull public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.testing.node.MockNodeParameters) @NotNull public final java.util.List getCordappPackages() @NotNull - public final java.util.Collection getCordappsForAllNodes() - @NotNull public final net.corda.core.identity.Party getDefaultNotaryIdentity() @NotNull public final net.corda.testing.node.StartedMockNode getDefaultNotaryNode() @@ -7216,8 +6262,6 @@ public class net.corda.testing.node.MockNetwork extends java.lang.Object public final void stopNodes() public final void waitQuiescent() ## -public final class net.corda.testing.node.MockNetworkKt extends java.lang.Object -## public final class net.corda.testing.node.MockNetworkNotarySpec extends java.lang.Object public (net.corda.core.identity.CordaX500Name) public (net.corda.core.identity.CordaX500Name, boolean) @@ -7231,7 +6275,6 @@ public final class net.corda.testing.node.MockNetworkNotarySpec extends java.lan public final net.corda.core.identity.CordaX500Name getName() public final boolean getValidating() public int hashCode() - @NotNull public String toString() ## public final class net.corda.testing.node.MockNetworkParameters extends java.lang.Object @@ -7257,7 +6300,6 @@ public final class net.corda.testing.node.MockNetworkParameters extends java.lan public final net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy getServicePeerAllocationStrategy() public final boolean getThreadPerNode() public int hashCode() - @NotNull public String toString() @NotNull public final net.corda.testing.node.MockNetworkParameters withNetworkParameters(net.corda.core.node.NetworkParameters) @@ -7270,65 +6312,24 @@ public final class net.corda.testing.node.MockNetworkParameters extends java.lan @NotNull public final net.corda.testing.node.MockNetworkParameters withThreadPerNode(boolean) ## -public final class net.corda.testing.node.MockNodeConfigOverrides extends java.lang.Object - public () - public (java.util.Map, net.corda.testing.node.MockNetNotaryConfig, net.corda.testing.node.MockNetFlowTimeOut) - @Nullable - public final java.util.Map component1() - @Nullable - public final net.corda.testing.node.MockNetNotaryConfig component2() - @Nullable - public final net.corda.testing.node.MockNetFlowTimeOut component3() - @NotNull - public final net.corda.testing.node.MockNodeConfigOverrides copy(java.util.Map, net.corda.testing.node.MockNetNotaryConfig, net.corda.testing.node.MockNetFlowTimeOut) - public boolean equals(Object) - @Nullable - public final java.util.Map getExtraDataSourceProperties() - @Nullable - public final net.corda.testing.node.MockNetFlowTimeOut getFlowTimeout() - @Nullable - public final net.corda.testing.node.MockNetNotaryConfig getNotary() - public int hashCode() - @NotNull - public String toString() -## public final class net.corda.testing.node.MockNodeParameters extends java.lang.Object public () - public (Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, net.corda.testing.node.MockNodeConfigOverrides) - public (Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, net.corda.testing.node.MockNodeConfigOverrides, java.util.Collection) @Nullable public final Integer component1() @Nullable public final net.corda.core.identity.CordaX500Name component2() @NotNull public final java.math.BigInteger component3() - @Nullable - public final net.corda.testing.node.MockNodeConfigOverrides component4() - @NotNull - public final java.util.Collection component5() - @NotNull - public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, net.corda.testing.node.MockNodeConfigOverrides) - @NotNull - public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, net.corda.testing.node.MockNodeConfigOverrides, java.util.Collection) public boolean equals(Object) @NotNull - public final java.util.Collection getAdditionalCordapps() - @Nullable - public final net.corda.testing.node.MockNodeConfigOverrides getConfigOverrides() - @NotNull public final java.math.BigInteger getEntropyRoot() @Nullable public final Integer getForcedID() @Nullable public final net.corda.core.identity.CordaX500Name getLegalName() public int hashCode() - @NotNull public String toString() @NotNull - public final net.corda.testing.node.MockNodeParameters withAdditionalCordapps(java.util.Collection) - @NotNull - public final net.corda.testing.node.MockNodeParameters withConfigOverrides(net.corda.testing.node.MockNodeConfigOverrides) - @NotNull public final net.corda.testing.node.MockNodeParameters withEntropyRoot(java.math.BigInteger) @NotNull public final net.corda.testing.node.MockNodeParameters withForcedID(Integer) @@ -7345,7 +6346,6 @@ public class net.corda.testing.node.MockServices extends java.lang.Object implem public (Iterable, net.corda.testing.core.TestIdentity, net.corda.core.node.services.IdentityService, net.corda.core.node.NetworkParameters, java.security.KeyPair...) public (Iterable, net.corda.testing.core.TestIdentity, net.corda.core.node.services.IdentityService, java.security.KeyPair...) public (Iterable, net.corda.testing.core.TestIdentity, java.security.KeyPair...) - public (java.util.List, net.corda.testing.core.TestIdentity, net.corda.testing.core.TestIdentity...) public (net.corda.core.identity.CordaX500Name) public (net.corda.core.identity.CordaX500Name, java.security.KeyPair, java.security.KeyPair...) public (net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService) @@ -7367,8 +6367,6 @@ public class net.corda.testing.node.MockServices extends java.lang.Object implem @NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey) @NotNull - public net.corda.core.cordapp.CordappContext getAppContext() - @NotNull public final net.corda.testing.services.MockAttachmentStorage getAttachments() @NotNull public net.corda.testing.node.TestClock getClock() @@ -7421,9 +6419,6 @@ public class net.corda.testing.node.MockServices extends java.lang.Object implem public net.corda.core.transactions.SignedTransaction signInitialTransaction(net.corda.core.transactions.TransactionBuilder, java.security.PublicKey) @NotNull public net.corda.core.contracts.StateAndRef toStateAndRef(net.corda.core.contracts.StateRef) - public void withEntityManager(java.util.function.Consumer) - @NotNull - public T withEntityManager(kotlin.jvm.functions.Function1) public static final net.corda.testing.node.MockServices$Companion Companion ## public static final class net.corda.testing.node.MockServices$Companion extends java.lang.Object @@ -7478,7 +6473,6 @@ public final class net.corda.testing.node.NotarySpec extends java.lang.Object @NotNull public final net.corda.testing.driver.VerifierType getVerifierType() public int hashCode() - @NotNull public String toString() ## public interface net.corda.testing.node.ResponderFlowFactory @@ -7498,8 +6492,6 @@ public final class net.corda.testing.node.StartedMockNode extends java.lang.Obje @NotNull public final rx.Observable registerInitiatedFlow(Class) @NotNull - public final net.corda.core.concurrent.CordaFuture registerResponderFlow(Class>, net.corda.testing.node.ResponderFlowFactory, Class) - @NotNull public final net.corda.core.concurrent.CordaFuture startFlow(net.corda.core.flows.FlowLogic) public final void stop() public final T transaction(kotlin.jvm.functions.Function0) @@ -7513,54 +6505,9 @@ public final class net.corda.testing.node.TestClock extends net.corda.node.Mutab public final synchronized void advanceBy(java.time.Duration) public final synchronized void setTo(java.time.Instant) ## -@DoNotImplement -public interface net.corda.testing.node.TestCordapp - @NotNull - public abstract java.util.Map getConfig() - @NotNull - public abstract String getName() - @NotNull - public abstract java.util.Set getPackages() - public abstract int getTargetVersion() - @NotNull - public abstract String getTitle() - @NotNull - public abstract String getVendor() - @NotNull - public abstract String getVersion() - @NotNull - public abstract net.corda.testing.node.TestCordapp withConfig(java.util.Map) - @NotNull - public abstract net.corda.testing.node.TestCordapp withName(String) - @NotNull - public abstract net.corda.testing.node.TestCordapp withTargetVersion(int) - @NotNull - public abstract net.corda.testing.node.TestCordapp withTitle(String) - @NotNull - public abstract net.corda.testing.node.TestCordapp withVendor(String) - @NotNull - public abstract net.corda.testing.node.TestCordapp withVersion(String) -## -public static final class net.corda.testing.node.TestCordapp$Factory extends java.lang.Object - public () - @NotNull - public static final net.corda.testing.node.TestCordapp fromPackages(java.util.Collection) - @NotNull - public static final net.corda.testing.node.TestCordapp fromPackages(String...) - public static final net.corda.testing.node.TestCordapp$Factory$Companion Companion -## -public static final class net.corda.testing.node.TestCordapp$Factory$Companion extends java.lang.Object - @NotNull - public final net.corda.testing.node.TestCordapp fromPackages(java.util.Collection) - @NotNull - public final net.corda.testing.node.TestCordapp fromPackages(String...) -## public final class net.corda.testing.node.UnstartedMockNode extends java.lang.Object public final int getId() @NotNull - public final net.corda.testing.node.StartedMockNode getStarted() - public final boolean isStarted() - @NotNull public final net.corda.testing.node.StartedMockNode start() public static final net.corda.testing.node.UnstartedMockNode$Companion Companion ## @@ -7584,13 +6531,8 @@ public final class net.corda.testing.node.User extends java.lang.Object @NotNull public final String getUsername() public int hashCode() - @NotNull public String toString() ## -public class net.corda.client.rpc.ConnectionFailureException extends net.corda.client.rpc.RPCException - public () - public (Throwable) -## public final class net.corda.client.rpc.CordaRPCClient extends java.lang.Object public (java.util.List) public (java.util.List, net.corda.client.rpc.CordaRPCClientConfiguration) @@ -7600,10 +6542,6 @@ public final class net.corda.client.rpc.CordaRPCClient extends java.lang.Object public final net.corda.client.rpc.CordaRPCConnection start(String, String) @NotNull public final net.corda.client.rpc.CordaRPCConnection start(String, String, net.corda.core.context.Trace, net.corda.core.context.Actor) - @NotNull - public final net.corda.client.rpc.CordaRPCConnection start(String, String, net.corda.core.context.Trace, net.corda.core.context.Actor, net.corda.core.identity.CordaX500Name) - @NotNull - public final net.corda.client.rpc.CordaRPCConnection start(String, String, net.corda.core.identity.CordaX500Name) public final A use(String, String, kotlin.jvm.functions.Function1) public static final net.corda.client.rpc.CordaRPCClient$Companion Companion ## @@ -7614,62 +6552,15 @@ public static final class net.corda.client.rpc.CordaRPCClient$Companion extends public final net.corda.client.rpc.CordaRPCClient createWithSsl(net.corda.core.utilities.NetworkHostAndPort, net.corda.core.messaging.ClientRpcSslOptions, net.corda.client.rpc.CordaRPCClientConfiguration) ## public class net.corda.client.rpc.CordaRPCClientConfiguration extends java.lang.Object - public () public (java.time.Duration) - public (java.time.Duration, int) - public (java.time.Duration, int, boolean) - public (java.time.Duration, int, boolean, java.time.Duration) - public (java.time.Duration, int, boolean, java.time.Duration, int) - public (java.time.Duration, int, boolean, java.time.Duration, int, int) - public (java.time.Duration, int, boolean, java.time.Duration, int, int, java.time.Duration) - public (java.time.Duration, int, boolean, java.time.Duration, int, int, java.time.Duration, double) - public (java.time.Duration, int, boolean, java.time.Duration, int, int, java.time.Duration, double, int) - public (java.time.Duration, int, boolean, java.time.Duration, int, int, java.time.Duration, double, int, int) - public (java.time.Duration, int, boolean, java.time.Duration, int, int, java.time.Duration, double, int, int, java.time.Duration) @NotNull public final java.time.Duration component1() @NotNull - public final net.corda.client.rpc.CordaRPCClientConfiguration copy() - @NotNull public final net.corda.client.rpc.CordaRPCClientConfiguration copy(java.time.Duration) - @NotNull - public final net.corda.client.rpc.CordaRPCClientConfiguration copy(java.time.Duration, int) - @NotNull - public final net.corda.client.rpc.CordaRPCClientConfiguration copy(java.time.Duration, int, boolean) - @NotNull - public final net.corda.client.rpc.CordaRPCClientConfiguration copy(java.time.Duration, int, boolean, java.time.Duration) - @NotNull - public final net.corda.client.rpc.CordaRPCClientConfiguration copy(java.time.Duration, int, boolean, java.time.Duration, int) - @NotNull - public final net.corda.client.rpc.CordaRPCClientConfiguration copy(java.time.Duration, int, boolean, java.time.Duration, int, int) - @NotNull - public final net.corda.client.rpc.CordaRPCClientConfiguration copy(java.time.Duration, int, boolean, java.time.Duration, int, int, java.time.Duration) - @NotNull - public final net.corda.client.rpc.CordaRPCClientConfiguration copy(java.time.Duration, int, boolean, java.time.Duration, int, int, java.time.Duration, double) - @NotNull - public final net.corda.client.rpc.CordaRPCClientConfiguration copy(java.time.Duration, int, boolean, java.time.Duration, int, int, java.time.Duration, double, int) - @NotNull - public final net.corda.client.rpc.CordaRPCClientConfiguration copy(java.time.Duration, int, boolean, java.time.Duration, int, int, java.time.Duration, double, int, int) - @NotNull - public final net.corda.client.rpc.CordaRPCClientConfiguration copy(java.time.Duration, int, boolean, java.time.Duration, int, int, java.time.Duration, double, int, int, java.time.Duration) public boolean equals(Object) - public int getCacheConcurrencyLevel() @NotNull public java.time.Duration getConnectionMaxRetryInterval() - @NotNull - public java.time.Duration getConnectionRetryInterval() - public double getConnectionRetryIntervalMultiplier() - @NotNull - public java.time.Duration getDeduplicationCacheExpiry() - public int getMaxFileSize() - public int getMaxReconnectAttempts() - public int getMinimumServerProtocolVersion() - public int getObservationExecutorPoolSize() - @NotNull - public java.time.Duration getReapInterval() - public boolean getTrackRpcCallSites() public int hashCode() - @NotNull public String toString() public static final net.corda.client.rpc.CordaRPCClientConfiguration$Companion Companion @NotNull @@ -7727,7 +6618,6 @@ public final class net.corda.testing.contracts.DummyContract extends java.lang.O public static final net.corda.core.transactions.TransactionBuilder move(java.util.List>, net.corda.core.identity.AbstractParty) @NotNull public static final net.corda.core.transactions.TransactionBuilder move(net.corda.core.contracts.StateAndRef, net.corda.core.identity.AbstractParty) - @NotNull public String toString() public void verify(net.corda.core.transactions.LedgerTransaction) public static final net.corda.testing.contracts.DummyContract$Companion Companion @@ -7765,7 +6655,6 @@ public static final class net.corda.testing.contracts.DummyContract$MultiOwnerSt @NotNull public java.util.List getParticipants() public int hashCode() - @NotNull public String toString() ## @DoNotImplement @@ -7783,7 +6672,6 @@ public static final class net.corda.testing.contracts.DummyContract$SingleOwnerS @NotNull public java.util.List getParticipants() public int hashCode() - @NotNull public String toString() @NotNull public net.corda.core.contracts.CommandAndState withNewOwner(net.corda.core.identity.AbstractParty) @@ -7829,67 +6717,19 @@ public static final class net.corda.testing.contracts.DummyContractV2$State exte @NotNull public java.util.List getParticipants() public int hashCode() - @NotNull public String toString() ## -public final class net.corda.testing.contracts.DummyContractV3 extends java.lang.Object implements net.corda.core.contracts.UpgradedContractWithLegacyConstraint - public () - @NotNull - public String getLegacyContract() - @NotNull - public net.corda.core.contracts.AttachmentConstraint getLegacyContractConstraint() - @NotNull - public net.corda.testing.contracts.DummyContractV3$State upgrade(net.corda.testing.contracts.DummyContractV2$State) - public void verify(net.corda.core.transactions.LedgerTransaction) - public static final net.corda.testing.contracts.DummyContractV3$Companion Companion - @NotNull - public static final String PROGRAM_ID = "net.corda.testing.contracts.DummyContractV3" -## -public static interface net.corda.testing.contracts.DummyContractV3$Commands extends net.corda.core.contracts.CommandData -## -public static final class net.corda.testing.contracts.DummyContractV3$Commands$Create extends net.corda.core.contracts.TypeOnlyCommandData implements net.corda.testing.contracts.DummyContractV3$Commands - public () -## -public static final class net.corda.testing.contracts.DummyContractV3$Commands$Move extends net.corda.core.contracts.TypeOnlyCommandData implements net.corda.testing.contracts.DummyContractV3$Commands - public () -## -public static final class net.corda.testing.contracts.DummyContractV3$Companion extends java.lang.Object -## -public static final class net.corda.testing.contracts.DummyContractV3$State extends java.lang.Object implements net.corda.core.contracts.ContractState - public (int, java.util.List) - public final int component1() - @NotNull - public final java.util.List component2() - @NotNull - public final net.corda.testing.contracts.DummyContractV3$State copy(int, java.util.List) - public boolean equals(Object) - public final int getMagicNumber() - @NotNull - public final java.util.List getOwners() - @NotNull - public java.util.List getParticipants() - public int hashCode() - @NotNull - public String toString() -## -@BelongsToContract public final class net.corda.testing.contracts.DummyState extends java.lang.Object implements net.corda.core.contracts.ContractState public () public (int) - public (int, java.util.List) public final int component1() @NotNull - public final java.util.List component2() - @NotNull public final net.corda.testing.contracts.DummyState copy(int) - @NotNull - public final net.corda.testing.contracts.DummyState copy(int, java.util.List) public boolean equals(Object) public final int getMagicNumber() @NotNull public java.util.List getParticipants() public int hashCode() - @NotNull public String toString() ## public final class net.corda.testing.core.DummyCommandData extends net.corda.core.contracts.TypeOnlyCommandData @@ -7913,7 +6753,6 @@ public final class net.corda.testing.core.Expect extends java.lang.Object @NotNull public final kotlin.jvm.functions.Function1 getMatch() public int hashCode() - @NotNull public String toString() ## @DoNotImplement @@ -7997,20 +6836,6 @@ public final class net.corda.testing.core.ExpectKt extends java.lang.Object @NotNull public static final net.corda.testing.core.ExpectCompose sequence(net.corda.testing.core.ExpectCompose...) ## -public final class net.corda.testing.core.JarSignatureTestUtils extends java.lang.Object - public final void addIndexList(java.nio.file.Path, String) - public final void createJar(java.nio.file.Path, String, String...) - public final void executeProcess(java.nio.file.Path, String...) - public final void generateKey(java.nio.file.Path, String, String, String, String, String, String) - @NotNull - public final java.nio.file.Path getBin() - @NotNull - public final java.util.List getJarSigners(java.nio.file.Path, String) - @NotNull - public final java.security.PublicKey signJar(java.nio.file.Path, String, String, String, String) - public final void updateJar(java.nio.file.Path, String, String...) - public static final net.corda.testing.core.JarSignatureTestUtils INSTANCE -## public final class net.corda.testing.core.SerializationEnvironmentRule extends java.lang.Object implements org.junit.rules.TestRule public () public (boolean) @@ -8047,13 +6872,7 @@ public final class net.corda.testing.core.TestConstants extends java.lang.Object public final class net.corda.testing.core.TestIdentity extends java.lang.Object public (net.corda.core.identity.CordaX500Name) public (net.corda.core.identity.CordaX500Name, long) - public (net.corda.core.identity.CordaX500Name, long, net.corda.core.crypto.SignatureScheme) public (net.corda.core.identity.CordaX500Name, java.security.KeyPair) - public (net.corda.core.identity.CordaX500Name, net.corda.core.crypto.SignatureScheme) - @NotNull - public static final net.corda.testing.core.TestIdentity fresh(String) - @NotNull - public static final net.corda.testing.core.TestIdentity fresh(String, net.corda.core.crypto.SignatureScheme) @NotNull public final net.corda.core.identity.PartyAndCertificate getIdentity() @NotNull @@ -8071,8 +6890,6 @@ public final class net.corda.testing.core.TestIdentity extends java.lang.Object public static final class net.corda.testing.core.TestIdentity$Companion extends java.lang.Object @NotNull public final net.corda.testing.core.TestIdentity fresh(String) - @NotNull - public final net.corda.testing.core.TestIdentity fresh(String, net.corda.core.crypto.SignatureScheme) ## public final class net.corda.testing.core.TestUtils extends java.lang.Object @NotNull @@ -8087,41 +6904,10 @@ public final class net.corda.testing.core.TestUtils extends java.lang.Object @NotNull public static final net.corda.core.identity.PartyAndCertificate getTestPartyAndCertificate(net.corda.core.identity.Party) @NotNull - public static final net.corda.core.identity.CordaX500Name makeUnique(net.corda.core.identity.CordaX500Name) - @NotNull - public static final java.util.Collection product(java.util.Collection, java.util.Collection) - @NotNull public static final net.corda.core.identity.Party singleIdentity(net.corda.core.node.NodeInfo) @NotNull public static final net.corda.core.identity.PartyAndCertificate singleIdentityAndCert(net.corda.core.node.NodeInfo) ## -public final class net.corda.testing.database.DatabaseConstants extends java.lang.Object - @NotNull - public static final String DATA_SOURCE_CLASSNAME = "dataSourceProperties.dataSourceClassName" - @NotNull - public static final String DATA_SOURCE_PASSWORD = "dataSourceProperties.dataSource.password" - @NotNull - public static final String DATA_SOURCE_URL = "dataSourceProperties.dataSource.url" - @NotNull - public static final String DATA_SOURCE_USER = "dataSourceProperties.dataSource.user" - public static final net.corda.testing.database.DatabaseConstants INSTANCE - @NotNull - public static final String SCHEMA = "database.schema" - @NotNull - public static final String TRANSACTION_ISOLATION_LEVEL = "database.transactionIsolationLevel" -## -public final class net.corda.testing.database.DbScriptRunner extends java.lang.Object - @NotNull - public final java.util.List merge(java.util.List, String) - @NotNull - public final java.util.List merge(java.util.List, java.util.List) - public final void runDbScript(String, String, java.util.List) - public static final net.corda.testing.database.DbScriptRunner INSTANCE -## -public final class net.corda.testing.database.ListPopulator extends java.lang.Object implements org.springframework.jdbc.datasource.init.DatabasePopulator - public (boolean, boolean, java.util.List) - public void populate(java.sql.Connection) -## public final class net.corda.testing.dsl.AttachmentResolutionException extends net.corda.core.flows.FlowException public (net.corda.core.crypto.SecureHash) ## @@ -8225,7 +7011,6 @@ public final class net.corda.testing.dsl.TestLedgerDSLInterpreter extends java.l public final String outputToLabel(net.corda.core.contracts.ContractState) @NotNull public net.corda.core.contracts.StateAndRef retrieveOutputStateAndRef(Class, String) - @NotNull public String toString() @Nullable public final String transactionName(net.corda.core.crypto.SecureHash) @@ -8259,14 +7044,12 @@ public static final class net.corda.testing.dsl.TestLedgerDSLInterpreter$WireTra @NotNull public final net.corda.core.transactions.WireTransaction getTransaction() public int hashCode() - @NotNull public String toString() ## @DoNotImplement public final class net.corda.testing.dsl.TestTransactionDSLInterpreter extends java.lang.Object implements net.corda.testing.dsl.TransactionDSLInterpreter, net.corda.testing.dsl.OutputStateLookup public (net.corda.testing.dsl.TestLedgerDSLInterpreter, net.corda.core.transactions.TransactionBuilder) public void _attachment(String) - public void _attachment(String, net.corda.core.crypto.SecureHash, java.util.List) @NotNull public net.corda.testing.dsl.EnforceVerifyOrFail _tweak(kotlin.jvm.functions.Function1) public void attachment(net.corda.core.crypto.SecureHash) @@ -8293,11 +7076,9 @@ public final class net.corda.testing.dsl.TestTransactionDSLInterpreter extends j public int hashCode() public void input(net.corda.core.contracts.StateRef) public void output(String, String, net.corda.core.identity.Party, Integer, net.corda.core.contracts.AttachmentConstraint, net.corda.core.contracts.ContractState) - public void reference(net.corda.core.contracts.StateRef) @NotNull public net.corda.core.contracts.StateAndRef retrieveOutputStateAndRef(Class, String) public void timeWindow(net.corda.core.contracts.TimeWindow) - @NotNull public String toString() @NotNull public net.corda.testing.dsl.EnforceVerifyOrFail verifies() @@ -8306,12 +7087,9 @@ public final class net.corda.testing.dsl.TestTransactionDSLInterpreter extends j public final class net.corda.testing.dsl.TransactionDSL extends java.lang.Object implements net.corda.testing.dsl.TransactionDSLInterpreter public (T, net.corda.core.identity.Party) public void _attachment(String) - public void _attachment(String, net.corda.core.crypto.SecureHash, java.util.List) @NotNull public net.corda.testing.dsl.EnforceVerifyOrFail _tweak(kotlin.jvm.functions.Function1) public final void attachment(String) - public final void attachment(String, net.corda.core.crypto.SecureHash) - public final void attachment(String, net.corda.core.crypto.SecureHash, java.util.List) public void attachment(net.corda.core.crypto.SecureHash) public final void attachments(String...) public final void command(java.security.PublicKey, net.corda.core.contracts.CommandData) @@ -8325,7 +7103,6 @@ public final class net.corda.testing.dsl.TransactionDSL extends java.lang.Object @NotNull public net.corda.testing.dsl.LedgerDSLInterpreter getLedgerInterpreter() public final void input(String) - public final void input(String, String) public final void input(String, net.corda.core.contracts.ContractState) public void input(net.corda.core.contracts.StateRef) public final void output(String, int, net.corda.core.contracts.ContractState) @@ -8335,9 +7112,6 @@ public final class net.corda.testing.dsl.TransactionDSL extends java.lang.Object public final void output(String, String, net.corda.core.identity.Party, net.corda.core.contracts.ContractState) public final void output(String, net.corda.core.contracts.ContractState) public final void output(String, net.corda.core.identity.Party, net.corda.core.contracts.ContractState) - public final void reference(String) - public final void reference(String, net.corda.core.contracts.ContractState) - public void reference(net.corda.core.contracts.StateRef) @NotNull public net.corda.core.contracts.StateAndRef retrieveOutputStateAndRef(Class, String) public final void timeWindow(java.time.Instant) @@ -8351,7 +7125,6 @@ public final class net.corda.testing.dsl.TransactionDSL extends java.lang.Object @DoNotImplement public interface net.corda.testing.dsl.TransactionDSLInterpreter extends net.corda.testing.dsl.OutputStateLookup, net.corda.testing.dsl.Verifies public abstract void _attachment(String) - public abstract void _attachment(String, net.corda.core.crypto.SecureHash, java.util.List) @NotNull public abstract net.corda.testing.dsl.EnforceVerifyOrFail _tweak(kotlin.jvm.functions.Function1) public abstract void attachment(net.corda.core.crypto.SecureHash) @@ -8360,7 +7133,6 @@ public interface net.corda.testing.dsl.TransactionDSLInterpreter extends net.cor public abstract net.corda.testing.dsl.LedgerDSLInterpreter getLedgerInterpreter() public abstract void input(net.corda.core.contracts.StateRef) public abstract void output(String, String, net.corda.core.identity.Party, Integer, net.corda.core.contracts.AttachmentConstraint, net.corda.core.contracts.ContractState) - public abstract void reference(net.corda.core.contracts.StateRef) public abstract void timeWindow(net.corda.core.contracts.TimeWindow) ## @DoNotImplement @@ -8411,10 +7183,6 @@ public final class net.corda.testing.services.MockAttachmentStorage extends net. @NotNull public final net.corda.core.crypto.SecureHash importContractAttachment(java.util.List, String, java.io.InputStream) @NotNull - public final net.corda.core.crypto.SecureHash importContractAttachment(java.util.List, String, java.io.InputStream, net.corda.core.crypto.SecureHash) - @NotNull - public final net.corda.core.crypto.SecureHash importContractAttachment(java.util.List, String, java.io.InputStream, net.corda.core.crypto.SecureHash, java.util.List) - @NotNull public net.corda.core.crypto.SecureHash importOrGetAttachment(java.io.InputStream) @Nullable public net.corda.core.contracts.Attachment openAttachment(net.corda.core.crypto.SecureHash)