Merge pull request #7608 from corda/shams-verification-service

ENT-11267: Introducing VerificationService, which implements VerificationSupport in terms of node-based services
This commit is contained in:
Adel El-Beik 2023-12-18 13:47:32 +00:00 committed by GitHub
commit 422786dccc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 193 additions and 156 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,9 +261,9 @@ 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)
val (legacyContractAttachment, upgradedContractAttachment) = verificationSupport.getAttachments(listOf( val (legacyContractAttachment, upgradedContractAttachment) = verificationSupport.getAttachments(listOf(
wtx.legacyContractAttachmentId, wtx.legacyContractAttachmentId,

View File

@ -132,9 +132,9 @@ 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)
val networkParameters = verificationSupport.getNetworkParameters(wireTx.networkParametersHash) val networkParameters = verificationSupport.getNetworkParameters(wireTx.networkParametersHash)
?: throw TransactionResolutionException(wireTx.id) ?: throw TransactionResolutionException(wireTx.id)

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

@ -19,7 +19,6 @@ import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
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
@ -29,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
@ -123,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)
} }
@ -134,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()
} }
@ -188,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() }
@ -213,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() }
@ -234,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