From a3bf4577f3ecadcbc1eb26da226946242501b161 Mon Sep 17 00:00:00 2001 From: Andrius Dagys Date: Thu, 8 Mar 2018 17:59:25 +0000 Subject: [PATCH] =?UTF-8?q?CORDA-696=20-=20Ensure=20deterministic=20transa?= =?UTF-8?q?ction=20id=20calculation=20for=20contra=E2=80=A6=20(#2676)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The problem with the previous implementation is that the transaction would be deserialized with the schema specified in the serialized form, but the calculation of the id would involve re-serializing properties using a local serialization context which might produce a different result. --- .ci/api-current.txt | 30 ++--- .../net/corda/core/crypto/CryptoUtils.kt | 6 +- .../net/corda/core/flows/NotaryChangeFlow.kt | 6 +- .../core/internal/ContractUpgradeUtils.kt | 6 +- .../corda/core/internal/TransactionUtils.kt | 45 ++++++++ .../ContractUpgradeTransactions.kt | 104 ++++++++++++++---- .../transactions/NotaryChangeTransactions.kt | 37 ++++++- .../kryo/DefaultKryoCustomizer.kt | 6 +- .../internal/serialization/kryo/Kryo.kt | 44 ++++---- .../services/vault/NodeVaultServiceTest.kt | 9 +- 10 files changed, 208 insertions(+), 85 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/internal/TransactionUtils.kt diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 20fb45af09..6962c89071 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -3001,17 +3001,15 @@ public static final class net.corda.core.serialization.SingletonSerializationTok @org.jetbrains.annotations.NotNull public final String getReason() ## @net.corda.core.DoNotImplement @net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.ContractUpgradeFilteredTransaction extends net.corda.core.transactions.CoreTransaction - public (List, net.corda.core.identity.Party, net.corda.core.crypto.SecureHash) - @org.jetbrains.annotations.NotNull public final List component1() - @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component2() - @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component3() - @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeFilteredTransaction copy(List, net.corda.core.identity.Party, net.corda.core.crypto.SecureHash) + public (Map, Map) + @org.jetbrains.annotations.NotNull public final Map component1() + @org.jetbrains.annotations.NotNull public final Map component2() + @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeFilteredTransaction copy(Map, Map) public boolean equals(Object) @org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId() @org.jetbrains.annotations.NotNull public List getInputs() @org.jetbrains.annotations.NotNull public net.corda.core.identity.Party getNotary() @org.jetbrains.annotations.NotNull public List getOutputs() - @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getRest() public int hashCode() public String toString() ## @@ -3038,8 +3036,8 @@ public static final class net.corda.core.serialization.SingletonSerializationTok @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt getPrivacySalt() @org.jetbrains.annotations.NotNull public Set getRequiredSigningKeys() @org.jetbrains.annotations.NotNull public List getSigs() - @org.jetbrains.annotations.NotNull public final String getUpgradeContractClassName() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.Attachment getUpgradedContractAttachment() + @org.jetbrains.annotations.NotNull public final String getUpgradedContractClassName() public int hashCode() public String toString() public void verifyRequiredSignatures() @@ -3047,15 +3045,11 @@ public static final class net.corda.core.serialization.SingletonSerializationTok public void verifySignaturesExcept(java.security.PublicKey...) ## @net.corda.core.DoNotImplement @net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.ContractUpgradeWireTransaction extends net.corda.core.transactions.CoreTransaction - public (List, net.corda.core.identity.Party, net.corda.core.crypto.SecureHash, String, net.corda.core.crypto.SecureHash, net.corda.core.contracts.PrivacySalt) + public (List, net.corda.core.contracts.PrivacySalt) @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeFilteredTransaction buildFilteredTransaction() @org.jetbrains.annotations.NotNull public final List component1() - @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component2() - @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component3() - @org.jetbrains.annotations.NotNull public final String component4() - @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component5() - @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt component6() - @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeWireTransaction copy(List, net.corda.core.identity.Party, net.corda.core.crypto.SecureHash, String, net.corda.core.crypto.SecureHash, net.corda.core.contracts.PrivacySalt) + @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt component2() + @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeWireTransaction copy(List, net.corda.core.contracts.PrivacySalt) public boolean equals(Object) @org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId() @org.jetbrains.annotations.NotNull public List getInputs() @@ -3063,8 +3057,8 @@ public static final class net.corda.core.serialization.SingletonSerializationTok @org.jetbrains.annotations.NotNull public net.corda.core.identity.Party getNotary() @org.jetbrains.annotations.NotNull public List getOutputs() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt getPrivacySalt() - @org.jetbrains.annotations.NotNull public final String getUpgradeContractClassName() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getUpgradedContractAttachmentId() + @org.jetbrains.annotations.NotNull public final String getUpgradedContractClassName() public int hashCode() @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeLedgerTransaction resolve(net.corda.core.node.ServicesForResolution, List) public String toString() @@ -3199,11 +3193,9 @@ public static final class net.corda.core.transactions.LedgerTransaction$InOutGro public void verifySignaturesExcept(java.security.PublicKey...) ## @net.corda.core.DoNotImplement @net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.NotaryChangeWireTransaction extends net.corda.core.transactions.CoreTransaction - public (List, net.corda.core.identity.Party, net.corda.core.identity.Party) + public (List) @org.jetbrains.annotations.NotNull public final List component1() - @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component2() - @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component3() - @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeWireTransaction copy(List, net.corda.core.identity.Party, net.corda.core.identity.Party) + @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeWireTransaction copy(List) public boolean equals(Object) @org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId() @org.jetbrains.annotations.NotNull public List getInputs() diff --git a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt index b586f5e562..873184c6eb 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt @@ -222,7 +222,11 @@ fun componentHash(opaqueBytes: OpaqueBytes, privacySalt: PrivacySalt, componentG /** Return the SHA256(SHA256(nonce || serializedComponent)). */ fun componentHash(nonce: SecureHash, opaqueBytes: OpaqueBytes): SecureHash = SecureHash.sha256Twice(nonce.bytes + opaqueBytes.bytes) -/** Serialise the object and return the hash of the serialized bytes. */ +/** + * Serialise the object and return the hash of the serialized bytes. Note that the resulting hash may not be deterministic + * across platform versions: serialization can produce different values if any of the types being serialized have changed, + * or if the version of serialization specified by the context changes. + */ fun serializedHash(x: T): SecureHash = x.serialize(context = SerializationDefaults.P2P_CONTEXT.withoutReferences()).bytes.sha256() /** diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryChangeFlow.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryChangeFlow.kt index 26525204aa..7690f01843 100644 --- a/core/src/main/kotlin/net/corda/core/flows/NotaryChangeFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryChangeFlow.kt @@ -7,7 +7,7 @@ import net.corda.core.crypto.Crypto import net.corda.core.crypto.SignableData import net.corda.core.crypto.SignatureMetadata import net.corda.core.identity.Party -import net.corda.core.transactions.NotaryChangeWireTransaction +import net.corda.core.internal.NotaryChangeTransactionBuilder import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.ProgressTracker @@ -30,11 +30,11 @@ class NotaryChangeFlow( override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx { val inputs = resolveEncumbrances(originalState) - val tx = NotaryChangeWireTransaction( + val tx = NotaryChangeTransactionBuilder( inputs.map { it.ref }, originalState.state.notary, modification - ) + ).build() val participantKeys = inputs.flatMap { it.state.data.participants }.map { it.owningKey }.toSet() // TODO: We need a much faster way of finding our key in the transaction diff --git a/core/src/main/kotlin/net/corda/core/internal/ContractUpgradeUtils.kt b/core/src/main/kotlin/net/corda/core/internal/ContractUpgradeUtils.kt index b085f51391..7b3283b28a 100644 --- a/core/src/main/kotlin/net/corda/core/internal/ContractUpgradeUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/ContractUpgradeUtils.kt @@ -21,18 +21,18 @@ object ContractUpgradeUtils { val upgradedContractAttachmentId = getContractAttachmentId(upgradedContractClass.name, services) val inputs = listOf(stateAndRef.ref) - return ContractUpgradeWireTransaction( + return ContractUpgradeTransactionBuilder( inputs, stateAndRef.state.notary, legacyContractAttachmentId, upgradedContractClass.name, upgradedContractAttachmentId, privacySalt - ) + ).build() } private fun getContractAttachmentId(name: ContractClassName, services: ServicesForResolution): AttachmentId { return services.cordappProvider.getContractAttachmentID(name) ?: throw IllegalStateException("Attachment not found for contract: $name") } -} +} \ 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 new file mode 100644 index 0000000000..3e774b4b6e --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/internal/TransactionUtils.kt @@ -0,0 +1,45 @@ +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.crypto.SecureHash +import net.corda.core.crypto.sha256 +import net.corda.core.identity.Party +import net.corda.core.serialization.serialize +import net.corda.core.transactions.ContractUpgradeWireTransaction +import net.corda.core.transactions.NotaryChangeWireTransaction +import java.io.ByteArrayOutputStream + +/** Constructs a [NotaryChangeWireTransaction]. */ +class NotaryChangeTransactionBuilder(val inputs: List, + val notary: Party, + val newNotary: Party) { + fun build(): NotaryChangeWireTransaction { + val components = listOf(inputs, notary, newNotary).map { it.serialize() } + return NotaryChangeWireTransaction(components) + } +} + +/** Constructs a [ContractUpgradeWireTransaction]. */ +class ContractUpgradeTransactionBuilder( + val inputs: List, + val notary: Party, + val legacyContractAttachmentId: SecureHash, + val upgradedContractClassName: ContractClassName, + val upgradedContractAttachmentId: SecureHash, + val privacySalt: PrivacySalt = PrivacySalt()) { + fun build(): ContractUpgradeWireTransaction { + val components = listOf(inputs, notary, legacyContractAttachmentId, upgradedContractClassName, upgradedContractAttachmentId).map { it.serialize() } + return ContractUpgradeWireTransaction(components, privacySalt) + } +} + +/** Concatenates the hash components into a single [ByteArray] and returns its hash. */ +fun combinedHash(components: Iterable): SecureHash { + val stream = ByteArrayOutputStream() + components.forEach { + stream.write(it.bytes) + } + return stream.toByteArray().sha256() +} \ No newline at end of file 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 33f15547d4..e85b50f82e 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt @@ -3,12 +3,18 @@ package net.corda.core.transactions import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.crypto.TransactionSignature -import net.corda.core.crypto.serializedHash +import net.corda.core.crypto.componentHash +import net.corda.core.crypto.computeNonce import net.corda.core.identity.Party 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.transactions.ContractUpgradeFilteredTransaction.FilteredComponent +import net.corda.core.transactions.ContractUpgradeWireTransaction.Component.* +import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.toBase58String import java.security.PublicKey @@ -18,13 +24,20 @@ import java.security.PublicKey /** A special transaction for upgrading the contract of a state. */ @CordaSerializable data class ContractUpgradeWireTransaction( - override val inputs: List, - override val notary: Party, - val legacyContractAttachmentId: SecureHash, - val upgradeContractClassName: ContractClassName, - val upgradedContractAttachmentId: SecureHash, + /** + * Contains all of the transaction components in serialized form. + * This is used for calculating the transaction id in a deterministic fashion, since re-serializing properties + * may result in a different byte sequence depending on the serialization context. + */ + val serializedComponents: List, + /** Required for hiding components in [ContractUpgradeFilteredTransaction]. */ val privacySalt: PrivacySalt = PrivacySalt() ) : CoreTransaction() { + 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() } + val upgradedContractClassName: ContractClassName by lazy { serializedComponents[UPGRADED_CONTRACT.ordinal].deserialize() } + val upgradedContractAttachmentId: SecureHash by lazy { serializedComponents[UPGRADED_ATTACHMENT.ordinal].deserialize() } init { check(inputs.isNotEmpty()) { "A contract upgrade transaction must have inputs" } @@ -39,11 +52,17 @@ data class ContractUpgradeWireTransaction( get() = throw UnsupportedOperationException("ContractUpgradeWireTransaction does not contain output states, " + "outputs can only be obtained from a resolved ContractUpgradeLedgerTransaction") - /** Hash of the list of components that are hidden in the [ContractUpgradeFilteredTransaction]. */ - private val hiddenComponentHash: SecureHash - get() = serializedHash(listOf(legacyContractAttachmentId, upgradeContractClassName, privacySalt)) + override val id: SecureHash by lazy { + val componentHashes =serializedComponents.mapIndexed { index, component -> + componentHash(nonces[index], component) + } + combinedHash(componentHashes) + } - override val id: SecureHash by lazy { serializedHash(inputs + notary).hashConcat(hiddenComponentHash) } + /** Required for filtering transaction components. */ + private val nonces = (0 until serializedComponents.size).map { + computeNonce(privacySalt, it, 0) + } /** Resolves input states and contract attachments, and builds a ContractUpgradeLedgerTransaction. */ fun resolve(services: ServicesForResolution, sigs: List): ContractUpgradeLedgerTransaction { @@ -56,7 +75,7 @@ data class ContractUpgradeWireTransaction( resolvedInputs, notary, legacyContractAttachment, - upgradeContractClassName, + upgradedContractClassName, upgradedContractAttachment, id, privacySalt, @@ -65,8 +84,23 @@ data class ContractUpgradeWireTransaction( ) } + /** Constructs a filtered transaction: the inputs and the notary party are always visible, while the rest are hidden. */ fun buildFilteredTransaction(): ContractUpgradeFilteredTransaction { - return ContractUpgradeFilteredTransaction(inputs, notary, hiddenComponentHash) + val totalComponents = (0 until serializedComponents.size).toSet() + val visibleComponents = mapOf( + INPUTS.ordinal to FilteredComponent(serializedComponents[INPUTS.ordinal], nonces[INPUTS.ordinal]), + NOTARY.ordinal to FilteredComponent(serializedComponents[NOTARY.ordinal], nonces[NOTARY.ordinal]) + ) + val hiddenComponents = (totalComponents - visibleComponents.keys).map { index -> + val hash = componentHash(nonces[index], serializedComponents[index]) + index to hash + }.toMap() + + return ContractUpgradeFilteredTransaction(visibleComponents, hiddenComponents) + } + + enum class Component { + INPUTS, NOTARY, LEGACY_ATTACHMENT, UPGRADED_CONTRACT, UPGRADED_ATTACHMENT } } @@ -74,19 +108,43 @@ data class ContractUpgradeWireTransaction( * A filtered version of the [ContractUpgradeWireTransaction]. In comparison with a regular [FilteredTransaction], there * is no flexibility on what parts of the transaction to reveal – the inputs and notary field are always visible and the * rest of the transaction is always hidden. Its only purpose is to hide transaction data when using a non-validating notary. - * - * @property inputs The inputs of this transaction. - * @property notary The notary for this transaction. - * @property rest Hash of the hidden components of the [ContractUpgradeWireTransaction]. */ @CordaSerializable data class ContractUpgradeFilteredTransaction( - override val inputs: List, - override val notary: Party, - val rest: SecureHash + /** Transaction components that are exposed. */ + val visibleComponents: Map, + /** + * Hashes of the transaction components that are not revealed in this transaction. + * Required for computing the transaction id. + */ + val hiddenComponents: Map ) : CoreTransaction() { - override val id: SecureHash get() = serializedHash(inputs + notary).hashConcat(rest) + override val inputs: List by lazy { + visibleComponents[INPUTS.ordinal]?.component?.deserialize>() + ?: throw IllegalArgumentException("Inputs not specified") + } + override val notary: Party by lazy { + visibleComponents[NOTARY.ordinal]?.component?.deserialize() + ?: throw IllegalArgumentException("Notary not specified") + } + override val id: SecureHash by lazy { + val totalComponents = visibleComponents.size + hiddenComponents.size + val hashList = (0 until totalComponents).map { i -> + when { + visibleComponents.containsKey(i) -> { + componentHash(visibleComponents[i]!!.nonce, visibleComponents[i]!!.component) + } + hiddenComponents.containsKey(i) -> hiddenComponents[i]!! + else -> throw IllegalStateException("Missing component hashes") + } + } + combinedHash(hashList) + } override val outputs: List> get() = emptyList() + + /** Contains the serialized component and the associated nonce for computing the transaction id. */ + @CordaSerializable + class FilteredComponent(val component: OpaqueBytes, val nonce: SecureHash) } /** @@ -103,7 +161,7 @@ data class ContractUpgradeLedgerTransaction( override val inputs: List>, override val notary: Party, val legacyContractAttachment: Attachment, - val upgradeContractClassName: ContractClassName, + val upgradedContractClassName: ContractClassName, val upgradedContractAttachment: Attachment, override val id: SecureHash, val privacySalt: PrivacySalt, @@ -165,7 +223,7 @@ data class ContractUpgradeLedgerTransaction( // TODO: re-map encumbrance pointers input.state.copy( data = upgradedState, - contract = upgradeContractClassName, + contract = upgradedContractClassName, constraint = outputConstraint ) } @@ -182,7 +240,7 @@ data class ContractUpgradeLedgerTransaction( private fun loadUpgradedContract(): UpgradedContract { @Suppress("UNCHECKED_CAST") return this::class.java.classLoader - .loadClass(upgradeContractClassName) + .loadClass(upgradedContractClassName) .asSubclass(Contract::class.java) .getConstructor() .newInstance() as UpgradedContract 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 e230adbff8..0704ec0db2 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt @@ -3,11 +3,15 @@ package net.corda.core.transactions import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.crypto.TransactionSignature -import net.corda.core.crypto.serializedHash +import net.corda.core.crypto.sha256 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.deserialize +import net.corda.core.serialization.serialize +import net.corda.core.transactions.NotaryChangeWireTransaction.Component.* +import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.toBase58String import java.security.PublicKey @@ -18,10 +22,18 @@ import java.security.PublicKey */ @CordaSerializable data class NotaryChangeWireTransaction( - override val inputs: List, - override val notary: Party, - val newNotary: Party + /** + * Contains all of the transaction components in serialized form. + * This is used for calculating the transaction id in a deterministic fashion, since re-serializing properties + * may result in a different byte sequence depending on the serialization context. + */ + val serializedComponents: List ) : CoreTransaction() { + override val inputs: List = serializedComponents[INPUTS.ordinal].deserialize() + override val notary: Party = serializedComponents[NOTARY.ordinal].deserialize() + /** Identity of the notary service to reassign the states to.*/ + val newNotary: Party = serializedComponents[NEW_NOTARY.ordinal].deserialize() + /** * This transaction does not contain any output states, outputs can be obtained by resolving a * [NotaryChangeLedgerTransaction] and applying the notary modification to inputs. @@ -39,16 +51,29 @@ data class NotaryChangeWireTransaction( * A privacy salt is not really required in this case, because we already used nonces in normal transactions and * thus input state refs will always be unique. Also, filtering doesn't apply on this type of transactions. */ - override val id: SecureHash by lazy { serializedHash(inputs + notary + newNotary) } + override val id: SecureHash by lazy { + serializedComponents.map { component -> + component.bytes.sha256() + }.reduce { combinedHash, componentHash -> + combinedHash.hashConcat(componentHash) + } + } /** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */ - fun resolve(services: ServicesForResolution, sigs: List) : NotaryChangeLedgerTransaction { + fun resolve(services: ServicesForResolution, sigs: List): NotaryChangeLedgerTransaction { val resolvedInputs = services.loadStates(inputs.toSet()).toList() return NotaryChangeLedgerTransaction(resolvedInputs, notary, newNotary, id, sigs) } /** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */ fun resolve(services: ServiceHub, sigs: List) = resolve(services as ServicesForResolution, sigs) + + enum class Component { + INPUTS, NOTARY, NEW_NOTARY + } + + @Deprecated("Required only for backwards compatibility purposes. This type of transaction should not be constructed outside Corda code.", ReplaceWith("NotaryChangeTransactionBuilder"), DeprecationLevel.WARNING) + constructor(inputs: List, notary: Party, newNotary: Party) : this(listOf(inputs, notary, newNotary).map { it.serialize() }) } /** diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt index 5bf8e1eac4..9bb6b81142 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt @@ -22,10 +22,7 @@ import net.corda.core.serialization.MissingAttachmentsException import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SerializedBytes -import net.corda.core.transactions.ContractUpgradeWireTransaction -import net.corda.core.transactions.NotaryChangeWireTransaction -import net.corda.core.transactions.SignedTransaction -import net.corda.core.transactions.WireTransaction +import net.corda.core.transactions.* import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.toNonEmptySet import net.corda.nodeapi.internal.serialization.CordaClassResolver @@ -129,6 +126,7 @@ object DefaultKryoCustomizer { register(java.lang.invoke.SerializedLambda::class.java) register(ClosureSerializer.Closure::class.java, CordaClosureBlacklistSerializer) register(ContractUpgradeWireTransaction::class.java, ContractUpgradeWireTransactionSerializer) + register(ContractUpgradeFilteredTransaction::class.java, ContractUpgradeFilteredTransactionSerializer) for (whitelistProvider in serializationWhitelists) { val types = whitelistProvider.whitelist diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt index fc0be0c8eb..e161768f65 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt @@ -8,14 +8,10 @@ import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer import com.esotericsoftware.kryo.serializers.FieldSerializer import com.esotericsoftware.kryo.util.MapReferenceResolver import net.corda.core.concurrent.CordaFuture -import net.corda.core.contracts.ContractState import net.corda.core.contracts.PrivacySalt -import net.corda.core.contracts.StateRef -import net.corda.core.contracts.TransactionState import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash import net.corda.core.crypto.TransactionSignature -import net.corda.core.identity.Party import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext.UseCase.Checkpoint @@ -25,6 +21,7 @@ import net.corda.core.serialization.SerializedBytes import net.corda.core.toFuture import net.corda.core.toObservable import net.corda.core.transactions.* +import net.corda.core.utilities.OpaqueBytes import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.serialization.CordaClassResolver import net.corda.nodeapi.internal.serialization.serializationContextKey @@ -254,40 +251,41 @@ object WireTransactionSerializer : Serializer() { @ThreadSafe object NotaryChangeWireTransactionSerializer : Serializer() { override fun write(kryo: Kryo, output: Output, obj: NotaryChangeWireTransaction) { - kryo.writeClassAndObject(output, obj.inputs) - kryo.writeClassAndObject(output, obj.notary) - kryo.writeClassAndObject(output, obj.newNotary) + kryo.writeClassAndObject(output, obj.serializedComponents) } override fun read(kryo: Kryo, input: Input, type: Class): NotaryChangeWireTransaction { - val inputs: List = uncheckedCast(kryo.readClassAndObject(input)) - val notary = kryo.readClassAndObject(input) as Party - val newNotary = kryo.readClassAndObject(input) as Party - - return NotaryChangeWireTransaction(inputs, notary, newNotary) + val components : List = uncheckedCast(kryo.readClassAndObject(input)) + return NotaryChangeWireTransaction(components) } } @ThreadSafe object ContractUpgradeWireTransactionSerializer : Serializer() { override fun write(kryo: Kryo, output: Output, obj: ContractUpgradeWireTransaction) { - kryo.writeClassAndObject(output, obj.inputs) - kryo.writeClassAndObject(output, obj.notary) - kryo.writeClassAndObject(output, obj.legacyContractAttachmentId) - kryo.writeClassAndObject(output, obj.upgradeContractClassName) - kryo.writeClassAndObject(output, obj.upgradedContractAttachmentId) + kryo.writeClassAndObject(output, obj.serializedComponents) kryo.writeClassAndObject(output, obj.privacySalt) } override fun read(kryo: Kryo, input: Input, type: Class): ContractUpgradeWireTransaction { - val inputs: List = uncheckedCast(kryo.readClassAndObject(input)) - val notary = kryo.readClassAndObject(input) as Party - val legacyContractAttachment = kryo.readClassAndObject(input) as SecureHash - val upgradeContractClassName = kryo.readClassAndObject(input) as String - val upgradedContractAttachment = kryo.readClassAndObject(input) as SecureHash + val components: List = uncheckedCast(kryo.readClassAndObject(input)) val privacySalt = kryo.readClassAndObject(input) as PrivacySalt - return ContractUpgradeWireTransaction(inputs, notary, legacyContractAttachment, upgradeContractClassName, upgradedContractAttachment, privacySalt) + return ContractUpgradeWireTransaction(components, privacySalt) + } +} + +@ThreadSafe +object ContractUpgradeFilteredTransactionSerializer : Serializer() { + override fun write(kryo: Kryo, output: Output, obj: ContractUpgradeFilteredTransaction) { + kryo.writeClassAndObject(output, obj.visibleComponents) + kryo.writeClassAndObject(output, obj.hiddenComponents) + } + + override fun read(kryo: Kryo, input: Input, type: Class): ContractUpgradeFilteredTransaction { + val visibleComponents: Map = uncheckedCast(kryo.readClassAndObject(input)) + val hiddenComponents: Map = uncheckedCast(kryo.readClassAndObject(input)) + return ContractUpgradeFilteredTransaction(visibleComponents, hiddenComponents) } } diff --git a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt index 9bdcd1f24c..0c513aea82 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt @@ -4,10 +4,14 @@ import co.paralleluniverse.fibers.Suspendable import com.nhaarman.mockito_kotlin.argThat import com.nhaarman.mockito_kotlin.doNothing import com.nhaarman.mockito_kotlin.whenever -import net.corda.core.contracts.* +import net.corda.core.contracts.Amount +import net.corda.core.contracts.Issued +import net.corda.core.contracts.StateAndRef +import net.corda.core.contracts.StateRef import net.corda.core.crypto.NullKeys import net.corda.core.crypto.generateKeyPair import net.corda.core.identity.* +import net.corda.core.internal.NotaryChangeTransactionBuilder import net.corda.core.internal.packageName import net.corda.core.node.StatesToRecord import net.corda.core.node.services.StatesNotAvailableException @@ -17,7 +21,6 @@ import net.corda.core.node.services.queryBy import net.corda.core.node.services.vault.PageSpecification import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.QueryCriteria.* -import net.corda.core.transactions.NotaryChangeWireTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.NonEmptySet @@ -607,7 +610,7 @@ class NodeVaultServiceTest { // Change notary services.identityService.verifyAndRegisterIdentity(DUMMY_NOTARY_IDENTITY) val newNotary = DUMMY_NOTARY - val changeNotaryTx = NotaryChangeWireTransaction(listOf(initialCashState.ref), issueStx.notary!!, newNotary) + val changeNotaryTx = NotaryChangeTransactionBuilder(listOf(initialCashState.ref), issueStx.notary!!, newNotary).build() val cashStateWithNewNotary = StateAndRef(initialCashState.state.copy(notary = newNotary), StateRef(changeNotaryTx.id, 0)) database.transaction {