From 1337c35ee65d4c36dcd3cbfc73340fb666914e70 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 5 Mar 2019 13:25:04 +0000 Subject: [PATCH] ENT-3219 Handle more exceptions for addMissingDependencies (#4839) CORDA-2692 Addressed code review comments. CORDA-2692 Address code review comments CORDA-2692 Address code review comments --- .../net/corda/core/internal/CordaUtils.kt | 9 +-- .../core/transactions/SignedTransaction.kt | 62 ++++++++++++++----- .../core/transactions/TransactionBuilder.kt | 49 ++++++++++----- 3 files changed, 84 insertions(+), 36 deletions(-) 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 2291f5e6b9..e802ecdb06 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,7 @@ package net.corda.core.internal import net.corda.core.DeleteForDJVM +import net.corda.core.contracts.Attachment import net.corda.core.contracts.ContractAttachment import net.corda.core.contracts.ContractClassName import net.corda.core.flows.DataVendingFlow @@ -109,15 +110,15 @@ fun noPackageOverlap(packages: Collection): Boolean { } /** - * Scans trusted (installed locally) contract attachments to find all that contain the [className]. + * Scans trusted (installed locally) attachments to find all that contain the [className]. * This is required as a workaround until explicit cordapp dependencies are implemented. * DO NOT USE IN CLIENT code. * - * @return the contract attachments with the highest version. + * @return the attachments with the highest version. * * TODO: Should throw when the class is found in multiple contract attachments (not different versions). */ -fun AttachmentStorage.internalFindTrustedAttachmentForClass(className: String): ContractAttachment?{ +fun AttachmentStorage.internalFindTrustedAttachmentForClass(className: String): Attachment? { val allTrusted = queryAttachments( AttachmentQueryCriteria.AttachmentsQueryCriteria().withUploader(Builder.`in`(TRUSTED_UPLOADERS)), AttachmentSort(listOf(AttachmentSort.AttachmentSortColumn(AttachmentSort.AttachmentSortAttribute.VERSION, Sort.Direction.DESC)))) @@ -125,7 +126,7 @@ fun AttachmentStorage.internalFindTrustedAttachmentForClass(className: String): // TODO - add caching if performance is affected. for (attId in allTrusted) { val attch = openAttachment(attId)!! - if (attch is ContractAttachment && attch.openAsJAR().use { hasFile(it, "$className.class") }) return attch + if (attch.openAsJAR().use { hasFile(it, "$className.class") }) return attch } return null } diff --git a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt index cf92239e2d..6b5be2392a 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt @@ -7,6 +7,7 @@ import net.corda.core.KeepForDJVM import net.corda.core.contracts.* import net.corda.core.crypto.* import net.corda.core.identity.Party +import net.corda.core.internal.TransactionDeserialisationException import net.corda.core.internal.TransactionVerifierServiceInternal import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.internalFindTrustedAttachmentForClass @@ -18,6 +19,7 @@ import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow +import java.io.NotSerializableException import java.security.KeyPair import java.security.PublicKey import java.security.SignatureException @@ -226,27 +228,53 @@ data class SignedTransaction(val txBits: SerializedBytes, // TODO: allow non-blocking verification. services.transactionVerifierService.verify(ltx).getOrThrow() } catch (e: NoClassDefFoundError) { - // Transactions created before Corda 4 can be missing dependencies on other cordapps. - // This code attempts to find the missing dependency in the attachment storage among the trusted contract attachments. - // When it finds one, it instructs the verifier to use it to create the transaction classloader. - // TODO - add check that transaction was created before Corda 4. - - // TODO - should this be a [TransactionVerificationException]? - val missingClass = requireNotNull(e.message) { "Transaction $ltx is incorrectly formed." } - - val attachment = requireNotNull(services.attachments.internalFindTrustedAttachmentForClass(missingClass)) { - "Transaction $ltx is incorrectly formed. Could not find local dependency for class: $missingClass." + if (e.message != null) { + verifyWithExtraDependency(e.message!!, ltx, services, e) + } else { + throw e + } + } catch (e: NotSerializableException) { + if (e.cause is ClassNotFoundException && e.cause!!.message != null) { + verifyWithExtraDependency(e.cause!!.message!!.replace(".", "/"), ltx, services, e) + } else { + throw e + } + } catch (e: TransactionDeserialisationException) { + if (e.cause is NotSerializableException && e.cause.cause is ClassNotFoundException && e.cause.cause!!.message != null) { + verifyWithExtraDependency(e.cause.cause!!.message!!.replace(".", "/"), ltx, services, e) + } else { + throw e } - - log.warn("""Detected that transaction ${this.id} does not contain all cordapp dependencies. - |This may be the result of a bug in a previous version of Corda. - |Attempting to verify using the additional dependency: $attachment. - |Please check with the originator that this is a valid transaction.""".trimMargin()) - - (services.transactionVerifierService as TransactionVerifierServiceInternal).verify(ltx, listOf(attachment)).getOrThrow() } } + // Transactions created before Corda 4 can be missing dependencies on other CorDapps. + // This code attempts to find the missing dependency in the attachment storage among the trusted attachments. + // When it finds one, it instructs the verifier to use it to create the transaction classloader. + private fun verifyWithExtraDependency(missingClass: String, ltx: LedgerTransaction, services: ServiceHub, exception: Throwable) { + // If that transaction was created with and after Corda 4 then just fail. + // The lenient dependency verification is only supported for Corda 3 transactions. + // To detect if the transaction was created before Corda 4 we check if the transaction has the NetworkParameters component group. + if (this.networkParametersHash != null) { + throw exception + } + + val attachment = requireNotNull(services.attachments.internalFindTrustedAttachmentForClass(missingClass)) { + """Transaction $ltx is incorrectly formed. Most likely it was created during version 3 of Corda when the verification logic was more lenient. + |Attempted to find local dependency for class: $missingClass, but could not find one. + |If you wish to verify this transaction, please contact the originator of the transaction and install the provided missing JAR. + |You can install it using the RPC command: `uploadAttachment` without restarting the node. + |""".trimMargin() + } + + log.warn("""Detected that transaction ${this.id} does not contain all cordapp dependencies. + |This may be the result of a bug in a previous version of Corda. + |Attempting to verify using the additional trusted dependency: $attachment for class $missingClass. + |Please check with the originator that this is a valid transaction.""".trimMargin()) + + (services.transactionVerifierService as TransactionVerifierServiceInternal).verify(ltx, listOf(attachment)).getOrThrow() + } + /** * Resolves the underlying base transaction and then returns it, handling any special case transactions such as * [NotaryChangeWireTransaction]. 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 2eb82bac9d..2551fade8b 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt @@ -7,7 +7,6 @@ import net.corda.core.contracts.* import net.corda.core.crypto.* import net.corda.core.identity.Party import net.corda.core.internal.* -import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION import net.corda.core.internal.cordapp.CordappResolver import net.corda.core.node.NetworkParameters import net.corda.core.node.ServiceHub @@ -18,6 +17,7 @@ import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationFactory import net.corda.core.utilities.contextLogger +import java.io.NotSerializableException import java.security.PublicKey import java.time.Duration import java.time.Instant @@ -172,21 +172,25 @@ open class TransactionBuilder( try { wireTx.toLedgerTransaction(services).verify() } catch (e: NoClassDefFoundError) { - val missingClass = e.message - requireNotNull(missingClass) { "Transaction is incorrectly formed." } - - val attachment = services.attachments.internalFindTrustedAttachmentForClass(missingClass!!) - ?: throw IllegalArgumentException("Attempted to find dependent attachment for class $missingClass, but could not find a suitable candidate.") - - log.warnOnce("""The transaction currently built is missing an attachment for class: $missingClass. - Automatically attaching contract dependency $attachment. - It is strongly recommended to check that this is the desired attachment, and to manually add it to the transaction builder. - """.trimIndent()) - - addAttachment(attachment.id) + val missingClass = e.message ?: throw e + addMissingAttachment(missingClass, services) return true - // Ignore these exceptions as they will break unit tests. - // The point here is only to detect missing dependencies. The other exceptions are irrelevant. + } catch (e: TransactionDeserialisationException) { + if (e.cause is NotSerializableException && e.cause.cause is ClassNotFoundException) { + val missingClass = e.cause.cause!!.message ?: throw e + addMissingAttachment(missingClass.replace(".", "/"), services) + return true + } + return false + } catch (e: NotSerializableException) { + if (e.cause is ClassNotFoundException) { + val missingClass = e.cause!!.message ?: throw e + addMissingAttachment(missingClass.replace(".", "/"), services) + return true + } + return false + // Ignore these exceptions as they will break unit tests. + // The point here is only to detect missing dependencies. The other exceptions are irrelevant. } catch (tve: TransactionVerificationException) { } catch (tre: TransactionResolutionException) { } catch (ise: IllegalStateException) { @@ -195,6 +199,21 @@ open class TransactionBuilder( return false } + private fun addMissingAttachment(missingClass: String, services: ServicesForResolution) { + val attachment = services.attachments.internalFindTrustedAttachmentForClass(missingClass) + ?: throw IllegalArgumentException("""The transaction currently built is missing an attachment for class: $missingClass. + Attempted to find a suitable attachment but could not find any in the storage. + Please contact the developer of the CorDapp for further instructions. + """.trimIndent()) + + log.warnOnce("""The transaction currently built is missing an attachment for class: $missingClass. + Automatically attaching contract dependency $attachment. + Please contact the developer of the CorDapp and install the latest version, as this approach might be insecure. + """.trimIndent()) + + addAttachment(attachment.id) + } + /** * This method is responsible for selecting the contract versions to be used for the current transaction and resolve the output state [AutomaticPlaceholderConstraint]s. * The contract attachments are used to create a deterministic Classloader to deserialise the transaction and to run the contract verification.