mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
CORDA-2847: Support custom serialisers when attaching missing attachments to txs (#5046)
This commit is contained in:
parent
555abc193e
commit
2e4c5d79f6
@ -17,11 +17,13 @@ import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializationFactory
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import java.io.NotSerializableException
|
||||
import java.lang.Exception
|
||||
import java.security.PublicKey
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.ArrayDeque
|
||||
import java.util.UUID
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.component1
|
||||
import kotlin.collections.component2
|
||||
@ -67,6 +69,10 @@ open class TransactionBuilder(
|
||||
private fun defaultLockId() = (Strand.currentStrand() as? FlowStateMachine<*>)?.id?.uuid ?: UUID.randomUUID()
|
||||
private val log = contextLogger()
|
||||
private const val CORDA_VERSION_THAT_INTRODUCED_FLATTENED_COMMANDS = 4
|
||||
|
||||
private val ID_PATTERN = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"
|
||||
private val FQCP = Pattern.compile("$ID_PATTERN(/$ID_PATTERN)+")
|
||||
private fun isValidJavaClass(identifier: String) = FQCP.matcher(identifier).matches()
|
||||
}
|
||||
|
||||
private val inputsWithTransactionState = arrayListOf<StateAndRef<ContractState>>()
|
||||
@ -168,42 +174,52 @@ open class TransactionBuilder(
|
||||
* @return true if a new dependency was successfully added.
|
||||
*/
|
||||
private fun addMissingDependency(services: ServicesForResolution, wireTx: WireTransaction): Boolean {
|
||||
try {
|
||||
return try {
|
||||
wireTx.toLedgerTransaction(services).verify()
|
||||
} catch (e: NoClassDefFoundError) {
|
||||
val missingClass = e.message ?: throw e
|
||||
addMissingAttachment(missingClass, services)
|
||||
return true
|
||||
} 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
|
||||
// The transaction verified successfully without adding any extra dependency.
|
||||
false
|
||||
} catch (e: Throwable) {
|
||||
val rootError = e.rootCause
|
||||
when {
|
||||
// Handle various exceptions that can be thrown during verification and drill down the wrappings.
|
||||
// Note: this is a best effort to preserve backwards compatibility.
|
||||
rootError is ClassNotFoundException -> addMissingAttachment((rootError.message ?: throw e).replace(".", "/"), services, e)
|
||||
rootError is NoClassDefFoundError -> addMissingAttachment(rootError.message ?: throw e, services, e)
|
||||
|
||||
// Ignore these exceptions as they will break unit tests.
|
||||
// The point here is only to detect missing dependencies. The other exceptions are irrelevant.
|
||||
e is TransactionVerificationException -> false
|
||||
e is TransactionResolutionException -> false
|
||||
e is IllegalStateException -> false
|
||||
e is IllegalArgumentException -> false
|
||||
|
||||
// Fail early if none of the expected scenarios were hit.
|
||||
else -> {
|
||||
log.error("""The transaction currently built will not validate because of an unknown error most likely caused by a
|
||||
missing dependency in the transaction attachments.
|
||||
Please contact the developer of the CorDapp for further instructions.
|
||||
""".trimIndent(), e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
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) {
|
||||
} catch (ise: IllegalArgumentException) {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun addMissingAttachment(missingClass: String, services: ServicesForResolution) {
|
||||
private fun addMissingAttachment(missingClass: String, services: ServicesForResolution, originalException: Throwable): Boolean {
|
||||
if (!isValidJavaClass(missingClass)) {
|
||||
log.warn("Could not autodetect a valid attachment for the transaction being built.")
|
||||
throw originalException
|
||||
}
|
||||
|
||||
val attachment = services.attachments.internalFindTrustedAttachmentForClass(missingClass)
|
||||
?: throw IllegalArgumentException("""The transaction currently built is missing an attachment for class: $missingClass.
|
||||
|
||||
if (attachment == null) {
|
||||
log.error("""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())
|
||||
throw originalException
|
||||
}
|
||||
|
||||
log.warnOnce("""The transaction currently built is missing an attachment for class: $missingClass.
|
||||
Automatically attaching contract dependency $attachment.
|
||||
@ -211,6 +227,7 @@ open class TransactionBuilder(
|
||||
""".trimIndent())
|
||||
|
||||
addAttachment(attachment.id)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,27 @@
|
||||
package net.corda.vega.plugin.customserializers
|
||||
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.schemas.CashSchema
|
||||
|
||||
/**
|
||||
* This just references a random class from the finance Cordapp for testing purposes.
|
||||
*/
|
||||
@Suppress("UNUSED")
|
||||
class UnusedFinanceSerializer : SerializationCustomSerializer<CashSchema, UnusedFinanceSerializer.Proxy> {
|
||||
class Proxy
|
||||
override fun toProxy(obj: CashSchema): Proxy =Proxy()
|
||||
override fun fromProxy(proxy: Proxy): CashSchema = CashSchema
|
||||
}
|
||||
|
||||
class Unused
|
||||
@Suppress("UNUSED")
|
||||
class UnusedFinanceSerializer1 : SerializationCustomSerializer<Unused, UnusedFinanceSerializer1.Proxy> {
|
||||
init {
|
||||
// Just instantiate some finance class.
|
||||
Cash()
|
||||
}
|
||||
class Proxy
|
||||
override fun toProxy(obj: Unused): Proxy =Proxy()
|
||||
override fun fromProxy(proxy: Proxy): Unused = Unused()
|
||||
}
|
Loading…
Reference in New Issue
Block a user