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 6aab69ae2b..dc3fd4d873 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt @@ -15,6 +15,8 @@ import net.corda.core.node.ServicesForResolution import net.corda.core.serialization.* import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder import net.corda.core.transactions.ContractUpgradeFilteredTransaction.FilteredComponent +import net.corda.core.transactions.ContractUpgradeLedgerTransaction.Companion.loadUpgradedContract +import net.corda.core.transactions.ContractUpgradeLedgerTransaction.Companion.retrieveAppClassLoader import net.corda.core.transactions.ContractUpgradeWireTransaction.Companion.calculateUpgradedState import net.corda.core.transactions.ContractUpgradeWireTransaction.Component.* import net.corda.core.transactions.WireTransaction.Companion.resolveStateRefBinaryComponent @@ -112,16 +114,16 @@ data class ContractUpgradeWireTransaction( ?: throw AttachmentResolutionException(upgradedContractAttachmentId) val hashToResolve = networkParametersHash ?: services.networkParametersService.defaultHash val resolvedNetworkParameters = services.networkParametersService.lookup(hashToResolve) ?: throw TransactionResolutionException(id) - return ContractUpgradeLedgerTransaction( + return ContractUpgradeLedgerTransaction.create( resolvedInputs, notary, legacyContractAttachment, - upgradedContractClassName, upgradedContractAttachment, id, privacySalt, sigs, - resolvedNetworkParameters + resolvedNetworkParameters, + loadUpgradedContract(upgradedContractClassName, retrieveAppClassLoader(services)) ) } @@ -231,22 +233,63 @@ data class ContractUpgradeFilteredTransaction( * *participants* fields, so full resolution is needed for signature verification. */ @KeepForDJVM -data class ContractUpgradeLedgerTransaction( +class ContractUpgradeLedgerTransaction +private constructor( override val inputs: List>, override val notary: Party, val legacyContractAttachment: Attachment, - val upgradedContractClassName: ContractClassName, val upgradedContractAttachment: Attachment, override val id: SecureHash, val privacySalt: PrivacySalt, override val sigs: List, - override val networkParameters: NetworkParameters + override val networkParameters: NetworkParameters, + private val upgradedContract: UpgradedContract ) : FullTransaction(), TransactionWithSignatures { /** ContractUpgradeLedgerTransactions do not contain reference input states. */ override val references: List> = emptyList() /** The legacy contract class name is determined by the first input state. */ private val legacyContractClassName = inputs.first().state.contract - private val upgradedContract: UpgradedContract = loadUpgradedContract() + + val upgradedContractClassName: ContractClassName + get() = upgradedContract::class.java.name + + companion object { + + @CordaInternal + internal fun create( + inputs: List>, + notary: Party, + legacyContractAttachment: Attachment, + upgradedContractAttachment: Attachment, + id: SecureHash, + privacySalt: PrivacySalt, + sigs: List, + networkParameters: NetworkParameters, + upgradedContract: UpgradedContract + ): ContractUpgradeLedgerTransaction { + return ContractUpgradeLedgerTransaction(inputs, notary, legacyContractAttachment, upgradedContractAttachment, id, privacySalt, sigs, networkParameters, upgradedContract) + } + + // TODO - this has to use a classloader created from the upgraded attachment. + @CordaInternal + internal fun loadUpgradedContract(upgradedContractClassName: ContractClassName, classLoader: ClassLoader): UpgradedContract { + @Suppress("UNCHECKED_CAST") + return classLoader + .loadClass(upgradedContractClassName) + .asSubclass(Contract::class.java) + .getConstructor() + .newInstance() as UpgradedContract + } + + // This is a "hack" to retrieve the CordappsClassloader from the services without having access to all classes. + @CordaInternal + internal fun retrieveAppClassLoader(services: ServicesForResolution): ClassLoader { + val cordappLoader = services.cordappProvider::class.java.getMethod("getCordappLoader").invoke(services.cordappProvider) + + @Suppress("UNCHECKED_CAST") + return cordappLoader::class.java.getMethod("getAppClassLoader").invoke(cordappLoader) as ClassLoader + } + } init { checkNotaryWhitelisted() @@ -297,13 +340,47 @@ data class ContractUpgradeLedgerTransaction( return keys.map { it.toBase58String() } } - // TODO: load contract from the CorDapp classloader - private fun loadUpgradedContract(): UpgradedContract { - @Suppress("UNCHECKED_CAST") - return this::class.java.classLoader - .loadClass(upgradedContractClassName) - .asSubclass(Contract::class.java) - .getConstructor() - .newInstance() as UpgradedContract + operator fun component1(): List> = inputs + operator fun component2(): Party = notary + operator fun component3(): Attachment = legacyContractAttachment + operator fun component4(): ContractClassName = upgradedContract::class.java.name + operator fun component5(): Attachment = upgradedContractAttachment + operator fun component6(): SecureHash = id + operator fun component7(): PrivacySalt = privacySalt + operator fun component8(): List = sigs + operator fun component9(): NetworkParameters = networkParameters + + override fun equals(other: Any?): Boolean = this === other || other is ContractUpgradeLedgerTransaction && this.id == other.id + + override fun hashCode(): Int = id.hashCode() + + override fun toString(): String { + return "ContractUpgradeLedgerTransaction(inputs=$inputs, notary=$notary, legacyContractAttachment=$legacyContractAttachment, upgradedContractAttachment=$upgradedContractAttachment, id=$id, privacySalt=$privacySalt, sigs=$sigs, networkParameters=$networkParameters, upgradedContract=$upgradedContract, references=$references, legacyContractClassName='$legacyContractClassName', outputs=$outputs)" } + + @Deprecated("ContractUpgradeLedgerTransaction should not be created directly, use ContractUpgradeWireTransaction.resolve instead.") + constructor( + inputs: List>, + notary: Party, + legacyContractAttachment: Attachment, + upgradedContractClassName: ContractClassName, + upgradedContractAttachment: Attachment, + id: SecureHash, + privacySalt: PrivacySalt, + sigs: List, + networkParameters: NetworkParameters + ) : this(inputs, notary, legacyContractAttachment, upgradedContractAttachment, id, privacySalt, sigs, networkParameters, loadUpgradedContract(upgradedContractClassName, ContractUpgradeLedgerTransaction::class.java.classLoader)) + + @Deprecated("ContractUpgradeLedgerTransaction should not be created directly, use ContractUpgradeWireTransaction.resolve instead.") + fun copy( + inputs: List> = this.inputs, + notary: Party = this.notary, + legacyContractAttachment: Attachment = this.legacyContractAttachment, + upgradedContractClassName: ContractClassName = this.upgradedContract::class.java.name, + upgradedContractAttachment: Attachment = this.upgradedContractAttachment, + id: SecureHash = this.id, + privacySalt: PrivacySalt = this.privacySalt, + sigs: List = this.sigs, + networkParameters: NetworkParameters = this.networkParameters + ) = ContractUpgradeLedgerTransaction(inputs, notary, legacyContractAttachment, upgradedContractClassName, upgradedContractAttachment, id, privacySalt, sigs, networkParameters) } \ No newline at end of file