mirror of
https://github.com/corda/corda.git
synced 2025-04-14 14:37:22 +00:00
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
This commit is contained in:
parent
8f45340276
commit
1337c35ee6
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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user