ENT-11267: Introducing VerificationService, which implements VerificationSupport in terms of node-based services

This commit is contained in:
Shams Asari 2023-12-12 15:01:48 +00:00
parent b375c7da21
commit a34932e887
10 changed files with 196 additions and 158 deletions

View File

@ -10,6 +10,7 @@ import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.ZoneVersionTooLowException import net.corda.core.node.ZoneVersionTooLowException
import net.corda.core.node.services.TransactionStorage
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
@ -113,6 +114,8 @@ fun noPackageOverlap(packages: Collection<String>): Boolean {
return packages.all { outer -> packages.none { inner -> inner != outer && inner.startsWith("$outer.") } } return packages.all { outer -> packages.none { inner -> inner != outer && inner.startsWith("$outer.") } }
} }
fun ServiceHub.getRequiredTransaction(txhash: SecureHash): SignedTransaction { fun TransactionStorage.getRequiredTransaction(txhash: SecureHash): SignedTransaction {
return validatedTransactions.getTransaction(txhash) ?: throw TransactionResolutionException(txhash) return getTransaction(txhash) ?: throw TransactionResolutionException(txhash)
} }
fun ServiceHub.getRequiredTransaction(txhash: SecureHash): SignedTransaction = validatedTransactions.getRequiredTransaction(txhash)

View File

@ -3,11 +3,11 @@ package net.corda.core.internal.cordapp
import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappProvider import net.corda.core.cordapp.CordappProvider
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.node.services.AttachmentId import net.corda.core.internal.verification.AttachmentFixups
interface CordappProviderInternal : CordappProvider { interface CordappProviderInternal : CordappProvider {
val appClassLoader: ClassLoader val appClassLoader: ClassLoader
val attachmentFixups: AttachmentFixups
val cordapps: List<CordappImpl> val cordapps: List<CordappImpl>
fun getCordappForFlow(flowLogic: FlowLogic<*>): Cordapp? fun getCordappForFlow(flowLogic: FlowLogic<*>): Cordapp?
fun fixupAttachmentIds(attachmentIds: Collection<AttachmentId>): Set<AttachmentId>
} }

View File

@ -0,0 +1,157 @@
package net.corda.core.internal.verification
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ComponentGroupEnum
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionResolutionException
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.internal.AttachmentTrustCalculator
import net.corda.core.internal.SerializedTransactionState
import net.corda.core.internal.TRUSTED_UPLOADERS
import net.corda.core.internal.getRequiredTransaction
import net.corda.core.node.NetworkParameters
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.NetworkParametersService
import net.corda.core.node.services.TransactionStorage
import net.corda.core.node.services.vault.AttachmentQueryCriteria.AttachmentsQueryCriteria
import net.corda.core.node.services.vault.AttachmentSort
import net.corda.core.node.services.vault.AttachmentSort.AttachmentSortAttribute
import net.corda.core.node.services.vault.AttachmentSort.AttachmentSortColumn
import net.corda.core.node.services.vault.Builder
import net.corda.core.node.services.vault.Sort
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
import net.corda.core.serialization.serialize
import net.corda.core.transactions.ContractUpgradeLedgerTransaction
import net.corda.core.transactions.ContractUpgradeWireTransaction
import net.corda.core.transactions.MissingContractAttachments
import net.corda.core.transactions.NotaryChangeLedgerTransaction
import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.WireTransaction
import java.security.PublicKey
import java.util.jar.JarInputStream
/**
* Implements [VerificationSupport] in terms of node-based services.
*/
interface VerificationService : VerificationSupport {
val transactionStorage: TransactionStorage
val identityService: IdentityService
val attachmentStorage: AttachmentStorage
val networkParametersService: NetworkParametersService
val attachmentTrustCalculator: AttachmentTrustCalculator
val attachmentFixups: AttachmentFixups
// TODO Bulk party lookup?
override fun getParties(keys: Collection<PublicKey>): List<Party?> = keys.map(identityService::partyFromKey)
override fun getAttachment(id: SecureHash): Attachment? = attachmentStorage.openAttachment(id)
override fun getNetworkParameters(id: SecureHash?): NetworkParameters? {
return networkParametersService.lookup(id ?: networkParametersService.defaultHash)
}
/**
* This is the main logic that knows how to retrieve the binary representation of [StateRef]s.
*
* For [ContractUpgradeWireTransaction] or [NotaryChangeWireTransaction] it knows how to recreate the output state in the
* correct classloader independent of the node's classpath.
*/
override fun getSerializedState(stateRef: StateRef): SerializedTransactionState {
val coreTransaction = transactionStorage.getRequiredTransaction(stateRef.txhash).coreTransaction
return when (coreTransaction) {
is WireTransaction -> getRegularOutput(coreTransaction, stateRef.index)
is ContractUpgradeWireTransaction -> getContractUpdateOutput(coreTransaction, stateRef.index)
is NotaryChangeWireTransaction -> getNotaryChangeOutput(coreTransaction, stateRef.index)
else -> throw UnsupportedOperationException("Attempting to resolve input ${stateRef.index} of a ${coreTransaction.javaClass} " +
"transaction. This is not supported.")
}
}
private fun getRegularOutput(coreTransaction: WireTransaction, outputIndex: Int): SerializedTransactionState {
@Suppress("UNCHECKED_CAST")
return coreTransaction.componentGroups
.first { it.groupIndex == ComponentGroupEnum.OUTPUTS_GROUP.ordinal }
.components[outputIndex] as SerializedTransactionState
}
/**
* Creates a binary serialized component for a virtual output state serialised and executed with the attachments from the transaction.
*/
@Suppress("ThrowsCount")
private fun getContractUpdateOutput(wtx: ContractUpgradeWireTransaction, outputIndex: Int): SerializedTransactionState {
val binaryInput = getSerializedState(wtx.inputs[outputIndex])
val legacyContractAttachment = getAttachment(wtx.legacyContractAttachmentId) ?: throw MissingContractAttachments(emptyList())
val upgradedContractAttachment = getAttachment(wtx.upgradedContractAttachmentId) ?: throw MissingContractAttachments(emptyList())
val networkParameters = getNetworkParameters(wtx.networkParametersHash) ?: throw TransactionResolutionException(wtx.id)
return AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(
listOf(legacyContractAttachment, upgradedContractAttachment),
networkParameters,
wtx.id,
::isAttachmentTrusted,
attachmentsClassLoaderCache = attachmentsClassLoaderCache
) { serializationContext ->
val upgradedContract = ContractUpgradeLedgerTransaction.loadUpgradedContract(wtx.upgradedContractClassName, wtx.id, serializationContext.deserializationClassLoader)
val outputState = ContractUpgradeWireTransaction.calculateUpgradedState(binaryInput.deserialize(), upgradedContract, upgradedContractAttachment)
outputState.serialize()
}
}
/**
* This should return a serialized virtual output state, that will be used to verify spending transactions.
* The binary output should not depend on the classpath of the node that is verifying the transaction.
*
* Ideally the serialization engine would support partial deserialization so that only the Notary ( and the encumbrance can be replaced
* from the binary input state)
*/
// TODO - currently this uses the main classloader.
private fun getNotaryChangeOutput(wtx: NotaryChangeWireTransaction, outputIndex: Int): SerializedTransactionState {
val input = getStateAndRef(wtx.inputs[outputIndex])
val output = NotaryChangeLedgerTransaction.computeOutput(input, wtx.newNotary) { wtx.inputs }
return output.serialize()
}
/**
* Scans trusted (installed locally) attachments to find all that contain the [className].
* This is required as a workaround until explicit cordapp dependencies are implemented.
*
* @return the attachments with the highest version.
*/
// TODO Should throw when the class is found in multiple contract attachments (not different versions).
override fun getTrustedClassAttachment(className: String): Attachment? {
val allTrusted = attachmentStorage.queryAttachments(
AttachmentsQueryCriteria().withUploader(Builder.`in`(TRUSTED_UPLOADERS)),
AttachmentSort(listOf(AttachmentSortColumn(AttachmentSortAttribute.VERSION, Sort.Direction.DESC)))
)
// TODO - add caching if performance is affected.
for (attId in allTrusted) {
val attch = attachmentStorage.openAttachment(attId)!!
if (attch.openAsJAR().use { hasFile(it, "$className.class") }) return attch
}
return null
}
private fun hasFile(jarStream: JarInputStream, className: String): Boolean {
while (true) {
val e = jarStream.nextJarEntry ?: return false
if (e.name == className) {
return true
}
}
}
override fun isAttachmentTrusted(attachment: Attachment): Boolean = attachmentTrustCalculator.calculate(attachment)
override fun fixupAttachmentIds(attachmentIds: Collection<SecureHash>): Set<SecureHash> {
return attachmentFixups.fixupAttachmentIds(attachmentIds)
}
}

View File

@ -2,50 +2,36 @@ package net.corda.core.internal.verification
import net.corda.core.contracts.Attachment import net.corda.core.contracts.Attachment
import net.corda.core.contracts.AttachmentResolutionException import net.corda.core.contracts.AttachmentResolutionException
import net.corda.core.contracts.ComponentGroupEnum
import net.corda.core.contracts.ContractAttachment import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.ContractState import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionResolutionException
import net.corda.core.contracts.TransactionState import net.corda.core.contracts.TransactionState
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.internal.AttachmentTrustCalculator
import net.corda.core.internal.SerializedTransactionState
import net.corda.core.internal.TRUSTED_UPLOADERS
import net.corda.core.internal.cordapp.CordappProviderInternal import net.corda.core.internal.cordapp.CordappProviderInternal
import net.corda.core.internal.getRequiredTransaction import net.corda.core.internal.getRequiredTransaction
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.vault.AttachmentQueryCriteria import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.vault.AttachmentSort import net.corda.core.node.services.TransactionStorage
import net.corda.core.node.services.vault.AttachmentSort.AttachmentSortAttribute
import net.corda.core.node.services.vault.Builder
import net.corda.core.node.services.vault.Sort
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
import net.corda.core.serialization.serialize
import net.corda.core.transactions.ContractUpgradeLedgerTransaction.Companion.loadUpgradedContract
import net.corda.core.transactions.ContractUpgradeWireTransaction import net.corda.core.transactions.ContractUpgradeWireTransaction
import net.corda.core.transactions.ContractUpgradeWireTransaction.Companion.calculateUpgradedState
import net.corda.core.transactions.MissingContractAttachments
import net.corda.core.transactions.NotaryChangeLedgerTransaction
import net.corda.core.transactions.NotaryChangeWireTransaction import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import java.security.PublicKey
import java.util.jar.JarInputStream
@Suppress("TooManyFunctions", "ThrowsCount") @Suppress("TooManyFunctions", "ThrowsCount")
interface VerifyingServiceHub : ServiceHub, VerificationSupport { interface VerifyingServiceHub : ServiceHub, VerificationService {
override val cordappProvider: CordappProviderInternal override val cordappProvider: CordappProviderInternal
val attachmentTrustCalculator: AttachmentTrustCalculator override val transactionStorage: TransactionStorage get() = validatedTransactions
override val attachmentStorage: AttachmentStorage get() = attachments
override val appClassLoader: ClassLoader get() = cordappProvider.appClassLoader override val appClassLoader: ClassLoader get() = cordappProvider.appClassLoader
override val attachmentFixups: AttachmentFixups get() = cordappProvider.attachmentFixups
override fun loadContractAttachment(stateRef: StateRef): Attachment { override fun loadContractAttachment(stateRef: StateRef): Attachment {
// We may need to recursively chase transactions if there are notary changes. // We may need to recursively chase transactions if there are notary changes.
return loadContractAttachment(stateRef, null) return loadContractAttachment(stateRef, null)
@ -86,111 +72,6 @@ interface VerifyingServiceHub : ServiceHub, VerificationSupport {
return input.mapTo(output, ::toStateAndRef) return input.mapTo(output, ::toStateAndRef)
} }
// TODO Bulk party lookup?
override fun getParties(keys: Collection<PublicKey>): List<Party?> = keys.map(identityService::partyFromKey)
override fun getAttachment(id: SecureHash): Attachment? = attachments.openAttachment(id)
override fun getNetworkParameters(id: SecureHash?): NetworkParameters? {
return networkParametersService.lookup(id ?: networkParametersService.defaultHash)
}
/**
* This is the main logic that knows how to retrieve the binary representation of [StateRef]s.
*
* For [ContractUpgradeWireTransaction] or [NotaryChangeWireTransaction] it knows how to recreate the output state in the
* correct classloader independent of the node's classpath.
*/
override fun getSerializedState(stateRef: StateRef): SerializedTransactionState {
val coreTransaction = getRequiredTransaction(stateRef.txhash).coreTransaction
return when (coreTransaction) {
is WireTransaction -> getRegularOutput(coreTransaction, stateRef.index)
is ContractUpgradeWireTransaction -> getContractUpdateOutput(coreTransaction, stateRef.index)
is NotaryChangeWireTransaction -> getNotaryChangeOutput(coreTransaction, stateRef.index)
else -> throw UnsupportedOperationException("Attempting to resolve input ${stateRef.index} of a ${coreTransaction.javaClass} " +
"transaction. This is not supported.")
}
}
private fun getRegularOutput(coreTransaction: WireTransaction, outputIndex: Int): SerializedTransactionState {
@Suppress("UNCHECKED_CAST")
return coreTransaction.componentGroups
.first { it.groupIndex == ComponentGroupEnum.OUTPUTS_GROUP.ordinal }
.components[outputIndex] as SerializedTransactionState
}
/**
* Creates a binary serialized component for a virtual output state serialised and executed with the attachments from the transaction.
*/
private fun getContractUpdateOutput(wtx: ContractUpgradeWireTransaction, outputIndex: Int): SerializedTransactionState {
val binaryInput = getSerializedState(wtx.inputs[outputIndex])
val legacyContractAttachment = getAttachment(wtx.legacyContractAttachmentId) ?: throw MissingContractAttachments(emptyList())
val upgradedContractAttachment = getAttachment(wtx.upgradedContractAttachmentId) ?: throw MissingContractAttachments(emptyList())
val networkParameters = getNetworkParameters(wtx.networkParametersHash) ?: throw TransactionResolutionException(wtx.id)
return AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(
listOf(legacyContractAttachment, upgradedContractAttachment),
networkParameters,
wtx.id,
::isAttachmentTrusted,
attachmentsClassLoaderCache = attachmentsClassLoaderCache
) { serializationContext ->
val upgradedContract = loadUpgradedContract(wtx.upgradedContractClassName, wtx.id, serializationContext.deserializationClassLoader)
val outputState = calculateUpgradedState(binaryInput.deserialize(), upgradedContract, upgradedContractAttachment)
outputState.serialize()
}
}
/**
* This should return a serialized virtual output state, that will be used to verify spending transactions.
* The binary output should not depend on the classpath of the node that is verifying the transaction.
*
* Ideally the serialization engine would support partial deserialization so that only the Notary ( and the encumbrance can be replaced
* from the binary input state)
*/
// TODO - currently this uses the main classloader.
private fun getNotaryChangeOutput(wtx: NotaryChangeWireTransaction, outputIndex: Int): SerializedTransactionState {
val input = getStateAndRef(wtx.inputs[outputIndex])
val output = NotaryChangeLedgerTransaction.computeOutput(input, wtx.newNotary) { wtx.inputs }
return output.serialize()
}
/**
* Scans trusted (installed locally) attachments to find all that contain the [className].
* This is required as a workaround until explicit cordapp dependencies are implemented.
*
* @return the attachments with the highest version.
*/
// TODO Should throw when the class is found in multiple contract attachments (not different versions).
override fun getTrustedClassAttachment(className: String): Attachment? {
val allTrusted = attachments.queryAttachments(
AttachmentQueryCriteria.AttachmentsQueryCriteria().withUploader(Builder.`in`(TRUSTED_UPLOADERS)),
AttachmentSort(listOf(AttachmentSort.AttachmentSortColumn(AttachmentSortAttribute.VERSION, Sort.Direction.DESC)))
)
// TODO - add caching if performance is affected.
for (attId in allTrusted) {
val attch = attachments.openAttachment(attId)!!
if (attch.openAsJAR().use { hasFile(it, "$className.class") }) return attch
}
return null
}
private fun hasFile(jarStream: JarInputStream, className: String): Boolean {
while (true) {
val e = jarStream.nextJarEntry ?: return false
if (e.name == className) {
return true
}
}
}
override fun isAttachmentTrusted(attachment: Attachment): Boolean = attachmentTrustCalculator.calculate(attachment)
override fun fixupAttachmentIds(attachmentIds: Collection<SecureHash>): Set<SecureHash> {
return cordappProvider.fixupAttachmentIds(attachmentIds)
}
/** /**
* Try to verify the given transaction on the external verifier, assuming it is available. It is not required to verify externally even * Try to verify the given transaction on the external verifier, assuming it is available. It is not required to verify externally even
* if the verifier is available. * if the verifier is available.

View File

@ -261,7 +261,7 @@ private constructor(
@CordaInternal @CordaInternal
@JvmSynthetic @JvmSynthetic
@Suppress("ThrowsCount") @Suppress("ThrowsCount")
internal fun resolve(verificationSupport: VerificationSupport, fun resolve(verificationSupport: VerificationSupport,
wtx: ContractUpgradeWireTransaction, wtx: ContractUpgradeWireTransaction,
sigs: List<TransactionSignature>): ContractUpgradeLedgerTransaction { sigs: List<TransactionSignature>): ContractUpgradeLedgerTransaction {
val inputs = wtx.inputs.map(verificationSupport::getStateAndRef) val inputs = wtx.inputs.map(verificationSupport::getStateAndRef)

View File

@ -132,7 +132,7 @@ private constructor(
companion object { companion object {
@CordaInternal @CordaInternal
@JvmSynthetic @JvmSynthetic
internal fun resolve(verificationSupport: VerificationSupport, fun resolve(verificationSupport: VerificationSupport,
wireTx: NotaryChangeWireTransaction, wireTx: NotaryChangeWireTransaction,
sigs: List<TransactionSignature>): NotaryChangeLedgerTransaction { sigs: List<TransactionSignature>): NotaryChangeLedgerTransaction {
val inputs = wireTx.inputs.map(verificationSupport::getStateAndRef) val inputs = wireTx.inputs.map(verificationSupport::getStateAndRef)

View File

@ -160,7 +160,9 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
return toLedgerTransactionInternal(services.toVerifyingServiceHub(), checkSufficientSignatures) return toLedgerTransactionInternal(services.toVerifyingServiceHub(), checkSufficientSignatures)
} }
private fun toLedgerTransactionInternal(verificationSupport: VerificationSupport, checkSufficientSignatures: Boolean): LedgerTransaction { @JvmSynthetic
@CordaInternal
fun toLedgerTransactionInternal(verificationSupport: VerificationSupport, checkSufficientSignatures: Boolean): LedgerTransaction {
// TODO: We could probably optimise the below by // TODO: We could probably optimise the below by
// a) not throwing if threshold is eventually satisfied, but some of the rest of the signatures are failing. // a) not throwing if threshold is eventually satisfied, but some of the rest of the signatures are failing.
// b) omit verifying signatures when threshold requirement is met. // b) omit verifying signatures when threshold requirement is met.

View File

@ -160,7 +160,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
@CordaInternal @CordaInternal
@JvmSynthetic @JvmSynthetic
internal fun toLedgerTransactionInternal(verificationSupport: VerificationSupport): LedgerTransaction { fun toLedgerTransactionInternal(verificationSupport: VerificationSupport): LedgerTransaction {
// Look up public keys to authenticated identities. // Look up public keys to authenticated identities.
val authenticatedCommands = if (verificationSupport.isResolutionLazy) { val authenticatedCommands = if (verificationSupport.isResolutionLazy) {
commands.lazyMapped { cmd, _ -> commands.lazyMapped { cmd, _ ->

View File

@ -27,7 +27,8 @@ open class CordappProviderImpl(val cordappLoader: CordappLoader,
private val attachmentStorage: AttachmentStorage) : SingletonSerializeAsToken(), CordappProviderInternal { private val attachmentStorage: AttachmentStorage) : SingletonSerializeAsToken(), CordappProviderInternal {
private val contextCache = ConcurrentHashMap<Cordapp, CordappContext>() private val contextCache = ConcurrentHashMap<Cordapp, CordappContext>()
private val cordappAttachments = HashBiMap.create<SecureHash, URL>() private val cordappAttachments = HashBiMap.create<SecureHash, URL>()
private val attachmentFixups = AttachmentFixups()
override val attachmentFixups = AttachmentFixups()
override val appClassLoader: ClassLoader get() = cordappLoader.appClassLoader override val appClassLoader: ClassLoader get() = cordappLoader.appClassLoader
@ -99,10 +100,6 @@ open class CordappProviderImpl(val cordappLoader: CordappLoader,
} }
} }
override fun fixupAttachmentIds(attachmentIds: Collection<AttachmentId>): Set<AttachmentId> {
return attachmentFixups.fixupAttachmentIds(attachmentIds)
}
/** /**
* Get the current cordapp context for the given CorDapp * Get the current cordapp context for the given CorDapp
* *

View File

@ -10,15 +10,15 @@ import net.corda.testing.core.internal.SelfCleaningDir
import net.corda.testing.internal.MockCordappConfigProvider import net.corda.testing.internal.MockCordappConfigProvider
import net.corda.testing.services.MockAttachmentStorage import net.corda.testing.services.MockAttachmentStorage
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Assert.* import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.lang.IllegalStateException
import java.net.URL import java.net.URL
import java.nio.file.Files import java.nio.file.Files
import java.util.Arrays.asList
import java.util.jar.JarOutputStream import java.util.jar.JarOutputStream
import java.util.zip.Deflater.NO_COMPRESSION import java.util.zip.Deflater.NO_COMPRESSION
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
@ -28,10 +28,10 @@ import kotlin.test.assertFailsWith
class CordappProviderImplTests { class CordappProviderImplTests {
private companion object { private companion object {
val isolatedJAR: URL = this::class.java.getResource("/isolated.jar") val isolatedJAR: URL = this::class.java.getResource("/isolated.jar")!!
// TODO: Cordapp name should differ from the JAR name // TODO: Cordapp name should differ from the JAR name
const val isolatedCordappName = "isolated" const val isolatedCordappName = "isolated"
val emptyJAR: URL = this::class.java.getResource("empty.jar") val emptyJAR: URL = this::class.java.getResource("empty.jar")!!
val validConfig: Config = ConfigFactory.parseString("key=value") val validConfig: Config = ConfigFactory.parseString("key=value")
@JvmField @JvmField
@ -122,7 +122,7 @@ class CordappProviderImplTests {
.writeFixupRules("$ID1 => $ID2, $ID3") .writeFixupRules("$ID1 => $ID2, $ID3")
val fixedIDs = with(newCordappProvider(fixupJar.toURI().toURL())) { val fixedIDs = with(newCordappProvider(fixupJar.toURI().toURL())) {
start() start()
fixupAttachmentIds(listOf(ID1)) attachmentFixups.fixupAttachmentIds(listOf(ID1))
} }
assertThat(fixedIDs).containsExactly(ID2, ID3) assertThat(fixedIDs).containsExactly(ID2, ID3)
} }
@ -133,7 +133,7 @@ class CordappProviderImplTests {
.writeFixupRules("$ID1 =>") .writeFixupRules("$ID1 =>")
val fixedIDs = with(newCordappProvider(fixupJar.toURI().toURL())) { val fixedIDs = with(newCordappProvider(fixupJar.toURI().toURL())) {
start() start()
fixupAttachmentIds(listOf(ID1)) attachmentFixups.fixupAttachmentIds(listOf(ID1))
} }
assertThat(fixedIDs).isEmpty() assertThat(fixedIDs).isEmpty()
} }
@ -187,21 +187,20 @@ class CordappProviderImplTests {
) )
val fixedIDs = with(newCordappProvider(fixupJar.toURI().toURL())) { val fixedIDs = with(newCordappProvider(fixupJar.toURI().toURL())) {
start() start()
fixupAttachmentIds(listOf(ID2, ID1)) attachmentFixups.fixupAttachmentIds(listOf(ID2, ID1))
} }
assertThat(fixedIDs).containsExactlyInAnyOrder(ID2, ID4) assertThat(fixedIDs).containsExactlyInAnyOrder(ID2, ID4)
} }
@Test(timeout=300_000) @Test(timeout=300_000)
fun `test an exception is raised when we have two jars with the same hash`() { fun `test an exception is raised when we have two jars with the same hash`() {
SelfCleaningDir().use { file -> SelfCleaningDir().use { file ->
val jarAndSigner = ContractJarTestUtils.makeTestSignedContractJar(file.path, "com.example.MyContract") val jarAndSigner = ContractJarTestUtils.makeTestSignedContractJar(file.path, "com.example.MyContract")
val signedJarPath = jarAndSigner.first val signedJarPath = jarAndSigner.first
val duplicateJarPath = signedJarPath.parent.resolve("duplicate-" + signedJarPath.fileName) val duplicateJarPath = signedJarPath.parent.resolve("duplicate-" + signedJarPath.fileName)
Files.copy(signedJarPath, duplicateJarPath) Files.copy(signedJarPath, duplicateJarPath)
val urls = asList(signedJarPath.toUri().toURL(), duplicateJarPath.toUri().toURL()) val urls = listOf(signedJarPath.toUri().toURL(), duplicateJarPath.toUri().toURL())
JarScanningCordappLoader.fromJarUrls(urls, VersionInfo.UNKNOWN).use { JarScanningCordappLoader.fromJarUrls(urls, VersionInfo.UNKNOWN).use {
assertFailsWith<DuplicateCordappsInstalledException> { assertFailsWith<DuplicateCordappsInstalledException> {
CordappProviderImpl(it, stubConfigProvider, attachmentStore).apply { start() } CordappProviderImpl(it, stubConfigProvider, attachmentStore).apply { start() }
@ -212,11 +211,10 @@ class CordappProviderImplTests {
@Test(timeout=300_000) @Test(timeout=300_000)
fun `test an exception is raised when two jars share a contract`() { fun `test an exception is raised when two jars share a contract`() {
SelfCleaningDir().use { file -> SelfCleaningDir().use { file ->
val jarA = ContractJarTestUtils.makeTestContractJar(file.path, listOf("com.example.MyContract", "com.example.AnotherContractForA"), generateManifest = false, jarFileName = "sampleA.jar") val jarA = ContractJarTestUtils.makeTestContractJar(file.path, listOf("com.example.MyContract", "com.example.AnotherContractForA"), generateManifest = false, jarFileName = "sampleA.jar")
val jarB = ContractJarTestUtils.makeTestContractJar(file.path, listOf("com.example.MyContract", "com.example.AnotherContractForB"), generateManifest = false, jarFileName = "sampleB.jar") val jarB = ContractJarTestUtils.makeTestContractJar(file.path, listOf("com.example.MyContract", "com.example.AnotherContractForB"), generateManifest = false, jarFileName = "sampleB.jar")
val urls = asList(jarA.toUri().toURL(), jarB.toUri().toURL()) val urls = listOf(jarA.toUri().toURL(), jarB.toUri().toURL())
JarScanningCordappLoader.fromJarUrls(urls, VersionInfo.UNKNOWN).use { JarScanningCordappLoader.fromJarUrls(urls, VersionInfo.UNKNOWN).use {
assertFailsWith<IllegalStateException> { assertFailsWith<IllegalStateException> {
CordappProviderImpl(it, stubConfigProvider, attachmentStore).apply { start() } CordappProviderImpl(it, stubConfigProvider, attachmentStore).apply { start() }
@ -233,8 +231,8 @@ class CordappProviderImplTests {
jar.putNextEntry(fileEntry("META-INF/Corda-Fixups")) jar.putNextEntry(fileEntry("META-INF/Corda-Fixups"))
for (line in lines) { for (line in lines) {
jar.write(line.toByteArray()) jar.write(line.toByteArray())
jar.write('\r'.toInt()) jar.write('\r'.code)
jar.write('\n'.toInt()) jar.write('\n'.code)
} }
} }
return this return this