ENT-3219 Handle more exceptions for addMissingDependencies ()

CORDA-2692 Addressed code review comments.

CORDA-2692 Address code review comments

CORDA-2692 Address code review comments
This commit is contained in:
Tudor Malene 2019-03-05 13:25:04 +00:00 committed by Gavin Thomas
parent 8f45340276
commit 1337c35ee6
3 changed files with 84 additions and 36 deletions
core/src/main/kotlin/net/corda/core

@ -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<String>): 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
}

@ -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<CoreTransaction>,
// 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].

@ -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.