mirror of
https://github.com/corda/corda.git
synced 2025-06-11 11:51:44 +00:00
ENT-11384: Cleanup JarScanningCordappLoader (#7664)
* It uses URLs when in fact CorDapps are jar files, and so should being Path. It also does URL equality, which is not recommended * Address (very old) TODO of removing RestrictedURL, which is not needed Also, back-ported some minor changes from https://github.com/corda/enterprise/pull/5057.
This commit is contained in:
parent
d642ebfbd7
commit
a7d0684fe7
@ -53,7 +53,6 @@ import kotlin.test.assertFalse
|
|||||||
import kotlin.test.assertNull
|
import kotlin.test.assertNull
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
|
||||||
class CordaRPCClientReconnectionTest {
|
class CordaRPCClientReconnectionTest {
|
||||||
|
|
||||||
private val portAllocator = incrementalPortAllocation()
|
private val portAllocator = incrementalPortAllocation()
|
||||||
|
@ -109,9 +109,9 @@ dependencies {
|
|||||||
smokeTestImplementation project(":client:rpc")
|
smokeTestImplementation project(":client:rpc")
|
||||||
smokeTestImplementation project(':smoke-test-utils')
|
smokeTestImplementation project(':smoke-test-utils')
|
||||||
smokeTestImplementation project(':core-test-utils')
|
smokeTestImplementation project(':core-test-utils')
|
||||||
smokeTestImplementation project(":finance:contracts")
|
|
||||||
smokeTestImplementation project(":finance:workflows")
|
smokeTestImplementation project(":finance:workflows")
|
||||||
smokeTestImplementation project(":testing:cordapps:4.11-workflows")
|
smokeTestImplementation project(":testing:cordapps:4.11-workflows")
|
||||||
|
smokeTestImplementation project(":finance:contracts")
|
||||||
smokeTestImplementation "org.assertj:assertj-core:${assertj_version}"
|
smokeTestImplementation "org.assertj:assertj-core:${assertj_version}"
|
||||||
smokeTestImplementation "org.bouncycastle:bcprov-jdk18on:${bouncycastle_version}"
|
smokeTestImplementation "org.bouncycastle:bcprov-jdk18on:${bouncycastle_version}"
|
||||||
smokeTestImplementation "co.paralleluniverse:quasar-core:$quasar_version"
|
smokeTestImplementation "co.paralleluniverse:quasar-core:$quasar_version"
|
||||||
|
@ -6,7 +6,6 @@ import net.corda.node.VersionInfo
|
|||||||
import net.corda.node.internal.cordapp.JarScanningCordappLoader
|
import net.corda.node.internal.cordapp.JarScanningCordappLoader
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.net.URL
|
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.jar.JarOutputStream
|
import java.util.jar.JarOutputStream
|
||||||
@ -31,7 +30,7 @@ class AttachmentFixupsTest {
|
|||||||
fun `test fixup rule that adds attachment`() {
|
fun `test fixup rule that adds attachment`() {
|
||||||
val fixupJar = Files.createTempFile("fixup", ".jar")
|
val fixupJar = Files.createTempFile("fixup", ".jar")
|
||||||
.writeFixupRules("$ID1 => $ID2, $ID3")
|
.writeFixupRules("$ID1 => $ID2, $ID3")
|
||||||
val fixedIDs = with(newFixupService(fixupJar.toUri().toURL())) {
|
val fixedIDs = with(newFixupService(fixupJar)) {
|
||||||
fixupAttachmentIds(listOf(ID1))
|
fixupAttachmentIds(listOf(ID1))
|
||||||
}
|
}
|
||||||
assertThat(fixedIDs).containsExactly(ID2, ID3)
|
assertThat(fixedIDs).containsExactly(ID2, ID3)
|
||||||
@ -41,7 +40,7 @@ class AttachmentFixupsTest {
|
|||||||
fun `test fixup rule that deletes attachment`() {
|
fun `test fixup rule that deletes attachment`() {
|
||||||
val fixupJar = Files.createTempFile("fixup", ".jar")
|
val fixupJar = Files.createTempFile("fixup", ".jar")
|
||||||
.writeFixupRules("$ID1 =>")
|
.writeFixupRules("$ID1 =>")
|
||||||
val fixedIDs = with(newFixupService(fixupJar.toUri().toURL())) {
|
val fixedIDs = with(newFixupService(fixupJar)) {
|
||||||
fixupAttachmentIds(listOf(ID1))
|
fixupAttachmentIds(listOf(ID1))
|
||||||
}
|
}
|
||||||
assertThat(fixedIDs).isEmpty()
|
assertThat(fixedIDs).isEmpty()
|
||||||
@ -52,7 +51,7 @@ class AttachmentFixupsTest {
|
|||||||
val fixupJar = Files.createTempFile("fixup", ".jar")
|
val fixupJar = Files.createTempFile("fixup", ".jar")
|
||||||
.writeFixupRules(" => $ID2")
|
.writeFixupRules(" => $ID2")
|
||||||
val ex = assertFailsWith<IllegalArgumentException> {
|
val ex = assertFailsWith<IllegalArgumentException> {
|
||||||
newFixupService(fixupJar.toUri().toURL())
|
newFixupService(fixupJar)
|
||||||
}
|
}
|
||||||
assertThat(ex).hasMessageContaining(
|
assertThat(ex).hasMessageContaining(
|
||||||
"Forbidden empty list of source attachment IDs in '$fixupJar'"
|
"Forbidden empty list of source attachment IDs in '$fixupJar'"
|
||||||
@ -65,7 +64,7 @@ class AttachmentFixupsTest {
|
|||||||
val fixupJar = Files.createTempFile("fixup", ".jar")
|
val fixupJar = Files.createTempFile("fixup", ".jar")
|
||||||
.writeFixupRules(rule)
|
.writeFixupRules(rule)
|
||||||
val ex = assertFailsWith<IllegalArgumentException> {
|
val ex = assertFailsWith<IllegalArgumentException> {
|
||||||
newFixupService(fixupJar.toUri().toURL())
|
newFixupService(fixupJar)
|
||||||
}
|
}
|
||||||
assertThat(ex).hasMessageContaining(
|
assertThat(ex).hasMessageContaining(
|
||||||
"Invalid fix-up line '${rule.trim()}' in '$fixupJar'"
|
"Invalid fix-up line '${rule.trim()}' in '$fixupJar'"
|
||||||
@ -78,7 +77,7 @@ class AttachmentFixupsTest {
|
|||||||
val fixupJar = Files.createTempFile("fixup", ".jar")
|
val fixupJar = Files.createTempFile("fixup", ".jar")
|
||||||
.writeFixupRules(rule)
|
.writeFixupRules(rule)
|
||||||
val ex = assertFailsWith<IllegalArgumentException> {
|
val ex = assertFailsWith<IllegalArgumentException> {
|
||||||
newFixupService(fixupJar.toUri().toURL())
|
newFixupService(fixupJar)
|
||||||
}
|
}
|
||||||
assertThat(ex).hasMessageContaining(
|
assertThat(ex).hasMessageContaining(
|
||||||
"Invalid fix-up line '${rule.trim()}' in '$fixupJar'"
|
"Invalid fix-up line '${rule.trim()}' in '$fixupJar'"
|
||||||
@ -94,7 +93,7 @@ class AttachmentFixupsTest {
|
|||||||
"",
|
"",
|
||||||
"$ID3 => $ID4"
|
"$ID3 => $ID4"
|
||||||
)
|
)
|
||||||
val fixedIDs = with(newFixupService(fixupJar.toUri().toURL())) {
|
val fixedIDs = with(newFixupService(fixupJar)) {
|
||||||
fixupAttachmentIds(listOf(ID2, ID1))
|
fixupAttachmentIds(listOf(ID2, ID1))
|
||||||
}
|
}
|
||||||
assertThat(fixedIDs).containsExactlyInAnyOrder(ID2, ID4)
|
assertThat(fixedIDs).containsExactlyInAnyOrder(ID2, ID4)
|
||||||
@ -130,8 +129,8 @@ class AttachmentFixupsTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun newFixupService(vararg urls: URL): AttachmentFixups {
|
private fun newFixupService(vararg paths: Path): AttachmentFixups {
|
||||||
val loader = JarScanningCordappLoader.fromJarUrls(urls.toList(), VersionInfo.UNKNOWN)
|
val loader = JarScanningCordappLoader.fromJarUrls(paths.toSet(), VersionInfo.UNKNOWN)
|
||||||
return AttachmentFixups().apply { load(loader.appClassLoader) }
|
return AttachmentFixups().apply { load(loader.appClassLoader) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,27 +138,19 @@ fun <T> List<T>.indexOfOrThrow(item: T): Int {
|
|||||||
* Similar to [Iterable.map] except it maps to a [Set] which preserves the iteration order.
|
* Similar to [Iterable.map] except it maps to a [Set] which preserves the iteration order.
|
||||||
*/
|
*/
|
||||||
@Suppress("INVISIBLE_MEMBER", "RemoveExplicitTypeArguments") // Because the external verifier uses Kotlin 1.2
|
@Suppress("INVISIBLE_MEMBER", "RemoveExplicitTypeArguments") // Because the external verifier uses Kotlin 1.2
|
||||||
inline fun <T, R> Iterable<T>.mapToSet(transform: (T) -> R): Set<R> {
|
inline fun <T, R> Collection<T>.mapToSet(transform: (T) -> R): Set<R> {
|
||||||
return if (this is Collection) {
|
return when (size) {
|
||||||
when (size) {
|
|
||||||
0 -> return emptySet()
|
0 -> return emptySet()
|
||||||
1 -> return setOf(transform(first()))
|
1 -> return setOf(transform(first()))
|
||||||
else -> mapTo(LinkedHashSet<R>(mapCapacity(size)), transform)
|
else -> mapTo(LinkedHashSet<R>(mapCapacity(size)), transform)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
mapTo(LinkedHashSet<R>(), transform)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Similar to [Iterable.flatMap] except it maps to a [Set] which preserves the iteration order.
|
* Similar to [Iterable.flatMap] except it maps to a [Set] which preserves the iteration order.
|
||||||
*/
|
*/
|
||||||
inline fun <T, R> Iterable<T>.flatMapToSet(transform: (T) -> Iterable<R>): Set<R> {
|
inline fun <T, R> Collection<T>.flatMapToSet(transform: (T) -> Iterable<R>): Set<R> {
|
||||||
return if (this is Collection && isEmpty()) {
|
return if (isEmpty()) emptySet() else flatMapTo(LinkedHashSet(), transform)
|
||||||
emptySet()
|
|
||||||
} else {
|
|
||||||
flatMapTo(LinkedHashSet(), transform)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.copy(this, target, *options)
|
fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.copy(this, target, *options)
|
||||||
|
@ -6,7 +6,6 @@ import net.corda.core.flows.FlowLogic
|
|||||||
import net.corda.core.internal.PLATFORM_VERSION
|
import net.corda.core.internal.PLATFORM_VERSION
|
||||||
import net.corda.core.internal.VisibleForTesting
|
import net.corda.core.internal.VisibleForTesting
|
||||||
import net.corda.core.internal.notary.NotaryService
|
import net.corda.core.internal.notary.NotaryService
|
||||||
import net.corda.core.internal.toPath
|
|
||||||
import net.corda.core.internal.telemetry.TelemetryComponent
|
import net.corda.core.internal.telemetry.TelemetryComponent
|
||||||
import net.corda.core.schemas.MappedSchema
|
import net.corda.core.schemas.MappedSchema
|
||||||
import net.corda.core.serialization.CheckpointCustomSerializer
|
import net.corda.core.serialization.CheckpointCustomSerializer
|
||||||
@ -14,10 +13,12 @@ import net.corda.core.serialization.SerializationCustomSerializer
|
|||||||
import net.corda.core.serialization.SerializationWhitelist
|
import net.corda.core.serialization.SerializationWhitelist
|
||||||
import net.corda.core.serialization.SerializeAsToken
|
import net.corda.core.serialization.SerializeAsToken
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import kotlin.io.path.name
|
import kotlin.io.path.name
|
||||||
|
|
||||||
data class CordappImpl(
|
data class CordappImpl(
|
||||||
|
val jarFile: Path,
|
||||||
override val contractClassNames: List<String>,
|
override val contractClassNames: List<String>,
|
||||||
override val initiatedFlows: List<Class<out FlowLogic<*>>>,
|
override val initiatedFlows: List<Class<out FlowLogic<*>>>,
|
||||||
override val rpcFlows: List<Class<out FlowLogic<*>>>,
|
override val rpcFlows: List<Class<out FlowLogic<*>>>,
|
||||||
@ -30,7 +31,6 @@ data class CordappImpl(
|
|||||||
override val checkpointCustomSerializers: List<CheckpointCustomSerializer<*, *>>,
|
override val checkpointCustomSerializers: List<CheckpointCustomSerializer<*, *>>,
|
||||||
override val customSchemas: Set<MappedSchema>,
|
override val customSchemas: Set<MappedSchema>,
|
||||||
override val allFlows: List<Class<out FlowLogic<*>>>,
|
override val allFlows: List<Class<out FlowLogic<*>>>,
|
||||||
override val jarPath: URL,
|
|
||||||
override val info: Cordapp.Info,
|
override val info: Cordapp.Info,
|
||||||
override val jarHash: SecureHash.SHA256,
|
override val jarHash: SecureHash.SHA256,
|
||||||
override val minimumPlatformVersion: Int,
|
override val minimumPlatformVersion: Int,
|
||||||
@ -41,7 +41,11 @@ data class CordappImpl(
|
|||||||
private val explicitCordappClasses: List<String> = emptyList(),
|
private val explicitCordappClasses: List<String> = emptyList(),
|
||||||
val isVirtual: Boolean = false
|
val isVirtual: Boolean = false
|
||||||
) : Cordapp {
|
) : Cordapp {
|
||||||
override val name: String = jarName(jarPath)
|
override val jarPath: URL
|
||||||
|
get() = jarFile.toUri().toURL()
|
||||||
|
|
||||||
|
override val name: String
|
||||||
|
get() = jarName(jarFile)
|
||||||
|
|
||||||
// TODO: Also add [SchedulableFlow] as a Cordapp class
|
// TODO: Also add [SchedulableFlow] as a Cordapp class
|
||||||
override val cordappClasses: List<String> = run {
|
override val cordappClasses: List<String> = run {
|
||||||
@ -50,7 +54,7 @@ data class CordappImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun jarName(url: URL): String = url.toPath().name.removeSuffix(".jar")
|
fun jarName(url: Path): String = url.name.removeSuffix(".jar")
|
||||||
|
|
||||||
/** CorDapp manifest entries */
|
/** CorDapp manifest entries */
|
||||||
const val CORDAPP_CONTRACT_NAME = "Cordapp-Contract-Name"
|
const val CORDAPP_CONTRACT_NAME = "Cordapp-Contract-Name"
|
||||||
@ -74,6 +78,7 @@ data class CordappImpl(
|
|||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
val TEST_INSTANCE = CordappImpl(
|
val TEST_INSTANCE = CordappImpl(
|
||||||
|
jarFile = Paths.get(""),
|
||||||
contractClassNames = emptyList(),
|
contractClassNames = emptyList(),
|
||||||
initiatedFlows = emptyList(),
|
initiatedFlows = emptyList(),
|
||||||
rpcFlows = emptyList(),
|
rpcFlows = emptyList(),
|
||||||
@ -85,7 +90,6 @@ data class CordappImpl(
|
|||||||
serializationCustomSerializers = emptyList(),
|
serializationCustomSerializers = emptyList(),
|
||||||
checkpointCustomSerializers = emptyList(),
|
checkpointCustomSerializers = emptyList(),
|
||||||
customSchemas = emptySet(),
|
customSchemas = emptySet(),
|
||||||
jarPath = Paths.get("").toUri().toURL(),
|
|
||||||
info = UNKNOWN_INFO,
|
info = UNKNOWN_INFO,
|
||||||
allFlows = emptyList(),
|
allFlows = emptyList(),
|
||||||
jarHash = SecureHash.allOnesHash,
|
jarHash = SecureHash.allOnesHash,
|
||||||
|
@ -4,6 +4,7 @@ import net.corda.core.contracts.ContractState
|
|||||||
import net.corda.core.contracts.TransactionState
|
import net.corda.core.contracts.TransactionState
|
||||||
import net.corda.core.flows.FlowException
|
import net.corda.core.flows.FlowException
|
||||||
import net.corda.core.internal.Version
|
import net.corda.core.internal.Version
|
||||||
|
import net.corda.core.internal.mapToSet
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,6 +15,8 @@ import net.corda.core.serialization.CordaSerializable
|
|||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
class MissingContractAttachments
|
class MissingContractAttachments
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(val states: List<TransactionState<ContractState>>, contractsClassName: String? = null, minimumRequiredContractClassVersion: Version? = null) : FlowException(
|
constructor(val states: List<TransactionState<ContractState>>,
|
||||||
"Cannot find contract attachments for " +
|
contractsClassName: String? = null,
|
||||||
"${contractsClassName ?: states.map { it.contract }.distinct()}${minimumRequiredContractClassVersion?.let { ", minimum required contract class version $minimumRequiredContractClassVersion"}}.")
|
minimumRequiredContractClassVersion: Version? = null
|
||||||
|
) : FlowException("Cannot find contract attachments for " +
|
||||||
|
"${contractsClassName ?: states.mapToSet { it.contract }}${minimumRequiredContractClassVersion?.let { ", minimum required contract class version $minimumRequiredContractClassVersion"} ?: ""}.")
|
||||||
|
@ -25,12 +25,13 @@ import net.corda.core.internal.hash
|
|||||||
import net.corda.core.internal.isAbstractClass
|
import net.corda.core.internal.isAbstractClass
|
||||||
import net.corda.core.internal.loadClassOfType
|
import net.corda.core.internal.loadClassOfType
|
||||||
import net.corda.core.internal.location
|
import net.corda.core.internal.location
|
||||||
|
import net.corda.core.internal.mapToSet
|
||||||
import net.corda.core.internal.notary.NotaryService
|
import net.corda.core.internal.notary.NotaryService
|
||||||
import net.corda.core.internal.notary.SinglePartyNotaryService
|
import net.corda.core.internal.notary.SinglePartyNotaryService
|
||||||
import net.corda.core.internal.objectOrNewInstance
|
import net.corda.core.internal.objectOrNewInstance
|
||||||
import net.corda.core.internal.pooledScan
|
import net.corda.core.internal.pooledScan
|
||||||
import net.corda.core.internal.readFully
|
|
||||||
import net.corda.core.internal.telemetry.TelemetryComponent
|
import net.corda.core.internal.telemetry.TelemetryComponent
|
||||||
|
import net.corda.core.internal.toPath
|
||||||
import net.corda.core.internal.toTypedArray
|
import net.corda.core.internal.toTypedArray
|
||||||
import net.corda.core.internal.warnContractWithoutConstraintPropagation
|
import net.corda.core.internal.warnContractWithoutConstraintPropagation
|
||||||
import net.corda.core.node.services.CordaService
|
import net.corda.core.node.services.CordaService
|
||||||
@ -46,7 +47,6 @@ import net.corda.nodeapi.internal.coreContractClasses
|
|||||||
import net.corda.serialization.internal.DefaultWhitelist
|
import net.corda.serialization.internal.DefaultWhitelist
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.net.URL
|
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.Random
|
import java.util.Random
|
||||||
@ -55,30 +55,36 @@ import java.util.concurrent.ConcurrentHashMap
|
|||||||
import java.util.jar.JarInputStream
|
import java.util.jar.JarInputStream
|
||||||
import java.util.jar.Manifest
|
import java.util.jar.Manifest
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
|
import kotlin.io.path.absolutePathString
|
||||||
import kotlin.io.path.exists
|
import kotlin.io.path.exists
|
||||||
import kotlin.io.path.useDirectoryEntries
|
import kotlin.io.path.inputStream
|
||||||
|
import kotlin.io.path.isSameFileAs
|
||||||
|
import kotlin.io.path.listDirectoryEntries
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles CorDapp loading and classpath scanning of CorDapp JARs
|
* Handles CorDapp loading and classpath scanning of CorDapp JARs
|
||||||
*
|
*
|
||||||
* @property cordappJarPaths The classpath of cordapp JARs
|
* @property cordappJars The classpath of cordapp JARs
|
||||||
*/
|
*/
|
||||||
class JarScanningCordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>,
|
class JarScanningCordappLoader private constructor(private val cordappJars: Set<Path>,
|
||||||
private val versionInfo: VersionInfo = VersionInfo.UNKNOWN,
|
private val versionInfo: VersionInfo = VersionInfo.UNKNOWN,
|
||||||
extraCordapps: List<CordappImpl>,
|
extraCordapps: List<CordappImpl>,
|
||||||
private val signerKeyFingerprintBlacklist: List<SecureHash> = emptyList()) : CordappLoaderTemplate() {
|
private val signerKeyFingerprintBlacklist: List<SecureHash> = emptyList()) : CordappLoaderTemplate() {
|
||||||
init {
|
init {
|
||||||
if (cordappJarPaths.isEmpty()) {
|
if (cordappJars.isEmpty()) {
|
||||||
logger.info("No CorDapp paths provided")
|
logger.info("No CorDapp paths provided")
|
||||||
} else {
|
} else {
|
||||||
logger.info("Loading CorDapps from ${cordappJarPaths.joinToString()}")
|
logger.info("Loading CorDapps from ${cordappJars.joinToString()}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private val cordappClasses: ConcurrentHashMap<String, Set<Cordapp>> = ConcurrentHashMap()
|
private val cordappClasses: ConcurrentHashMap<String, Set<Cordapp>> = ConcurrentHashMap()
|
||||||
override val cordapps: List<CordappImpl> by lazy { loadCordapps() + extraCordapps }
|
override val cordapps: List<CordappImpl> by lazy { loadCordapps() + extraCordapps }
|
||||||
|
|
||||||
override val appClassLoader: URLClassLoader = URLClassLoader(cordappJarPaths.stream().map { it.url }.toTypedArray(), javaClass.classLoader)
|
override val appClassLoader: URLClassLoader = URLClassLoader(
|
||||||
|
cordappJars.stream().map { it.toUri().toURL() }.toTypedArray(),
|
||||||
|
javaClass.classLoader
|
||||||
|
)
|
||||||
|
|
||||||
override fun close() = appClassLoader.close()
|
override fun close() = appClassLoader.close()
|
||||||
|
|
||||||
@ -95,7 +101,10 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
extraCordapps: List<CordappImpl> = emptyList(),
|
extraCordapps: List<CordappImpl> = emptyList(),
|
||||||
signerKeyFingerprintBlacklist: List<SecureHash> = emptyList()): JarScanningCordappLoader {
|
signerKeyFingerprintBlacklist: List<SecureHash> = emptyList()): JarScanningCordappLoader {
|
||||||
logger.info("Looking for CorDapps in ${cordappDirs.distinct().joinToString(", ", "[", "]")}")
|
logger.info("Looking for CorDapps in ${cordappDirs.distinct().joinToString(", ", "[", "]")}")
|
||||||
val paths = cordappDirs.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() }
|
val paths = cordappDirs
|
||||||
|
.asSequence()
|
||||||
|
.flatMap { if (it.exists()) it.listDirectoryEntries("*.jar") else emptyList() }
|
||||||
|
.toSet()
|
||||||
return JarScanningCordappLoader(paths, versionInfo, extraCordapps, signerKeyFingerprintBlacklist)
|
return JarScanningCordappLoader(paths, versionInfo, extraCordapps, signerKeyFingerprintBlacklist)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,50 +113,40 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
*
|
*
|
||||||
* @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection.
|
* @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection.
|
||||||
*/
|
*/
|
||||||
fun fromJarUrls(scanJars: List<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List<CordappImpl> = emptyList(),
|
fun fromJarUrls(scanJars: Set<Path>,
|
||||||
|
versionInfo: VersionInfo = VersionInfo.UNKNOWN,
|
||||||
|
extraCordapps: List<CordappImpl> = emptyList(),
|
||||||
cordappsSignerKeyFingerprintBlacklist: List<SecureHash> = emptyList()): JarScanningCordappLoader {
|
cordappsSignerKeyFingerprintBlacklist: List<SecureHash> = emptyList()): JarScanningCordappLoader {
|
||||||
val paths = scanJars.map { it.restricted() }
|
return JarScanningCordappLoader(scanJars, versionInfo, extraCordapps, cordappsSignerKeyFingerprintBlacklist)
|
||||||
return JarScanningCordappLoader(paths, versionInfo, extraCordapps, cordappsSignerKeyFingerprintBlacklist)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName)
|
|
||||||
|
|
||||||
private fun jarUrlsInDirectory(directory: Path): List<URL> {
|
|
||||||
return if (!directory.exists()) {
|
|
||||||
emptyList()
|
|
||||||
} else {
|
|
||||||
directory.useDirectoryEntries("*.jar") { jars -> jars.map { it.toUri().toURL() }.toList() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadCordapps(): List<CordappImpl> {
|
private fun loadCordapps(): List<CordappImpl> {
|
||||||
val invalidCordapps = mutableMapOf<String, URL>()
|
val invalidCordapps = mutableMapOf<String, Path>()
|
||||||
|
|
||||||
val cordapps = cordappJarPaths
|
val cordapps = cordappJars
|
||||||
.map { url -> scanCordapp(url).use { it.toCordapp(url) } }
|
.map { path -> scanCordapp(path).use { it.toCordapp(path) } }
|
||||||
.filter {
|
.filter { cordapp ->
|
||||||
if (it.minimumPlatformVersion > versionInfo.platformVersion) {
|
if (cordapp.minimumPlatformVersion > versionInfo.platformVersion) {
|
||||||
logger.warn("Not loading CorDapp ${it.info.shortName} (${it.info.vendor}) as it requires minimum " +
|
logger.warn("Not loading CorDapp ${cordapp.info.shortName} (${cordapp.info.vendor}) as it requires minimum " +
|
||||||
"platform version ${it.minimumPlatformVersion} (This node is running version ${versionInfo.platformVersion}).")
|
"platform version ${cordapp.minimumPlatformVersion} (This node is running version ${versionInfo.platformVersion}).")
|
||||||
invalidCordapps.put("CorDapp requires minimumPlatformVersion: ${it.minimumPlatformVersion}, but was: ${versionInfo.platformVersion}", it.jarPath)
|
invalidCordapps["CorDapp requires minimumPlatformVersion: ${cordapp.minimumPlatformVersion}, but was: ${versionInfo.platformVersion}"] = cordapp.jarFile
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}.filter { cordapp ->
|
||||||
.filter {
|
|
||||||
if (signerKeyFingerprintBlacklist.isEmpty()) {
|
if (signerKeyFingerprintBlacklist.isEmpty()) {
|
||||||
true //Nothing blacklisted, no need to check
|
true //Nothing blacklisted, no need to check
|
||||||
} else {
|
} else {
|
||||||
val certificates = it.jarPath.openStream().let(::JarInputStream).use(JarSignatureCollector::collectCertificates)
|
val certificates = cordapp.jarPath.openStream().let(::JarInputStream).use(JarSignatureCollector::collectCertificates)
|
||||||
val blockedCertificates = certificates.filter { it.publicKey.hash.sha256() in signerKeyFingerprintBlacklist }
|
val blockedCertificates = certificates.filter { it.publicKey.hash.sha256() in signerKeyFingerprintBlacklist }
|
||||||
if (certificates.isEmpty() || (certificates - blockedCertificates).isNotEmpty())
|
if (certificates.isEmpty() || (certificates - blockedCertificates).isNotEmpty()) {
|
||||||
true // Cordapp is not signed or it is signed by at least one non-blacklisted certificate
|
true // Cordapp is not signed or it is signed by at least one non-blacklisted certificate
|
||||||
else {
|
} else {
|
||||||
logger.warn("Not loading CorDapp ${it.info.shortName} (${it.info.vendor}) as it is signed by blacklisted key(s) only (probably development key): " +
|
logger.warn("Not loading CorDapp ${cordapp.info.shortName} (${cordapp.info.vendor}) as it is signed by blacklisted key(s) only (probably development key): " +
|
||||||
"${blockedCertificates.map { it.publicKey }}.")
|
"${blockedCertificates.map { it.publicKey }}.")
|
||||||
invalidCordapps.put("Corresponding contracts are signed by blacklisted key(s) only (probably development key),", it.jarPath)
|
invalidCordapps["Corresponding contracts are signed by blacklisted key(s) only (probably development key),"] = cordapp.jarFile
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -185,14 +184,15 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RestrictedScanResult.toCordapp(url: RestrictedURL): CordappImpl {
|
private fun RestrictedScanResult.toCordapp(path: Path): CordappImpl {
|
||||||
val manifest: Manifest? = url.url.openStream().use { JarInputStream(it).manifest }
|
val manifest: Manifest? = JarInputStream(path.inputStream()).use { it.manifest }
|
||||||
val info = parseCordappInfo(manifest, CordappImpl.jarName(url.url))
|
val info = parseCordappInfo(manifest, CordappImpl.jarName(path))
|
||||||
val minPlatformVersion = manifest?.get(CordappImpl.MIN_PLATFORM_VERSION)?.toIntOrNull() ?: 1
|
val minPlatformVersion = manifest?.get(CordappImpl.MIN_PLATFORM_VERSION)?.toIntOrNull() ?: 1
|
||||||
val targetPlatformVersion = manifest?.get(CordappImpl.TARGET_PLATFORM_VERSION)?.toIntOrNull() ?: minPlatformVersion
|
val targetPlatformVersion = manifest?.get(CordappImpl.TARGET_PLATFORM_VERSION)?.toIntOrNull() ?: minPlatformVersion
|
||||||
validateContractStateClassVersion(this)
|
validateContractStateClassVersion(this)
|
||||||
validateWhitelistClassVersion(this)
|
validateWhitelistClassVersion(this)
|
||||||
return CordappImpl(
|
return CordappImpl(
|
||||||
|
path,
|
||||||
findContractClassNamesWithVersionCheck(this),
|
findContractClassNamesWithVersionCheck(this),
|
||||||
findInitiatedFlows(this),
|
findInitiatedFlows(this),
|
||||||
findRPCFlows(this),
|
findRPCFlows(this),
|
||||||
@ -200,14 +200,13 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
findSchedulableFlows(this),
|
findSchedulableFlows(this),
|
||||||
findServices(this),
|
findServices(this),
|
||||||
findTelemetryComponents(this),
|
findTelemetryComponents(this),
|
||||||
findWhitelists(url),
|
findWhitelists(path),
|
||||||
findSerializers(this),
|
findSerializers(this),
|
||||||
findCheckpointSerializers(this),
|
findCheckpointSerializers(this),
|
||||||
findCustomSchemas(this),
|
findCustomSchemas(this),
|
||||||
findAllFlows(this),
|
findAllFlows(this),
|
||||||
url.url,
|
|
||||||
info,
|
info,
|
||||||
getJarHash(url.url),
|
path.hash,
|
||||||
minPlatformVersion,
|
minPlatformVersion,
|
||||||
targetPlatformVersion,
|
targetPlatformVersion,
|
||||||
findNotaryService(this),
|
findNotaryService(this),
|
||||||
@ -291,8 +290,6 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
return result.firstOrNull()
|
return result.firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getJarHash(url: URL): SecureHash.SHA256 = url.openStream().readFully().sha256()
|
|
||||||
|
|
||||||
private fun findServices(scanResult: RestrictedScanResult): List<Class<out SerializeAsToken>> {
|
private fun findServices(scanResult: RestrictedScanResult): List<Class<out SerializeAsToken>> {
|
||||||
return scanResult.getClassesWithAnnotation(SerializeAsToken::class, CordaService::class)
|
return scanResult.getClassesWithAnnotation(SerializeAsToken::class, CordaService::class)
|
||||||
}
|
}
|
||||||
@ -345,10 +342,10 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
scanResult.versionCheckClassesImplementing(SerializationWhitelist::class)
|
scanResult.versionCheckClassesImplementing(SerializationWhitelist::class)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findWhitelists(cordappJarPath: RestrictedURL): List<SerializationWhitelist> {
|
private fun findWhitelists(cordappJar: Path): List<SerializationWhitelist> {
|
||||||
val whitelists = ServiceLoader.load(SerializationWhitelist::class.java, appClassLoader).toList()
|
val whitelists = ServiceLoader.load(SerializationWhitelist::class.java, appClassLoader).toList()
|
||||||
return whitelists.filter {
|
return whitelists.filter {
|
||||||
it.javaClass.location == cordappJarPath.url && it.javaClass.name.startsWith(cordappJarPath.qualifiedNamePrefix)
|
it.javaClass.location.toPath().isSameFileAs(cordappJar)
|
||||||
} + DefaultWhitelist // Always add the DefaultWhitelist to the whitelist for an app.
|
} + DefaultWhitelist // Always add the DefaultWhitelist to the whitelist for an app.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,19 +358,18 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun findCustomSchemas(scanResult: RestrictedScanResult): Set<MappedSchema> {
|
private fun findCustomSchemas(scanResult: RestrictedScanResult): Set<MappedSchema> {
|
||||||
return scanResult.getClassesWithSuperclass(MappedSchema::class).instances().toSet()
|
return scanResult.getClassesWithSuperclass(MappedSchema::class).mapToSet { it.kotlin.objectOrNewInstance() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun scanCordapp(cordappJarPath: RestrictedURL): RestrictedScanResult {
|
private fun scanCordapp(cordappJar: Path): RestrictedScanResult {
|
||||||
val cordappElement = cordappJarPath.url.toString()
|
logger.info("Scanning CorDapp ${cordappJar.absolutePathString()}")
|
||||||
logger.info("Scanning CorDapp in $cordappElement")
|
|
||||||
val scanResult = ClassGraph()
|
val scanResult = ClassGraph()
|
||||||
.filterClasspathElementsByURL { elt -> elt == cordappJarPath.url }
|
.filterClasspathElementsByURL { it.toPath().isSameFileAs(cordappJar) }
|
||||||
.overrideClassLoaders(appClassLoader)
|
.overrideClassLoaders(appClassLoader)
|
||||||
.ignoreParentClassLoaders()
|
.ignoreParentClassLoaders()
|
||||||
.enableAllInfo()
|
.enableAllInfo()
|
||||||
.pooledScan()
|
.pooledScan()
|
||||||
return RestrictedScanResult(scanResult, cordappJarPath.qualifiedNamePrefix, cordappJarPath)
|
return RestrictedScanResult(scanResult, cordappJar)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T : Any> loadClass(className: String, type: KClass<T>): Class<out T>? {
|
private fun <T : Any> loadClass(className: String, type: KClass<T>): Class<out T>? {
|
||||||
@ -388,28 +384,16 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Remove this class as rootPackageName is never non-null.
|
private inner class RestrictedScanResult(private val scanResult: ScanResult, private val cordappJar: Path) : AutoCloseable {
|
||||||
/** @property rootPackageName only this package and subpackages may be extracted from [url], or null to allow all packages. */
|
|
||||||
private data class RestrictedURL(val url: URL, val rootPackageName: String?) {
|
|
||||||
val qualifiedNamePrefix: String get() = rootPackageName?.let { "$it." } ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T : Any> List<Class<out T>>.instances(): List<T> {
|
|
||||||
return map { it.kotlin.objectOrNewInstance() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class RestrictedScanResult(private val scanResult: ScanResult, private val qualifiedNamePrefix: String,
|
|
||||||
private val cordappJarPath: RestrictedURL) : AutoCloseable {
|
|
||||||
|
|
||||||
fun getNamesOfClassesImplementingWithClassVersionCheck(type: KClass<*>): List<String> {
|
fun getNamesOfClassesImplementingWithClassVersionCheck(type: KClass<*>): List<String> {
|
||||||
return scanResult.getClassesImplementing(type.java.name).filter { it.name.startsWith(qualifiedNamePrefix) }.map {
|
return scanResult.getClassesImplementing(type.java.name).map {
|
||||||
validateClassFileVersion(it)
|
validateClassFileVersion(it)
|
||||||
it.name
|
it.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun versionCheckClassesImplementing(type: KClass<*>) {
|
fun versionCheckClassesImplementing(type: KClass<*>) {
|
||||||
return scanResult.getClassesImplementing(type.java.name).filter { it.name.startsWith(qualifiedNamePrefix) }.forEach {
|
return scanResult.getClassesImplementing(type.java.name).forEach {
|
||||||
validateClassFileVersion(it)
|
validateClassFileVersion(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -418,7 +402,6 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
return scanResult
|
return scanResult
|
||||||
.getSubclasses(type.java.name)
|
.getSubclasses(type.java.name)
|
||||||
.names
|
.names
|
||||||
.filter { it.startsWith(qualifiedNamePrefix) }
|
|
||||||
.mapNotNull { loadClass(it, type) }
|
.mapNotNull { loadClass(it, type) }
|
||||||
.filterNot { it.isAbstractClass }
|
.filterNot { it.isAbstractClass }
|
||||||
}
|
}
|
||||||
@ -426,7 +409,6 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
fun <T : Any> getClassesImplementingWithClassVersionCheck(type: KClass<T>): List<T> {
|
fun <T : Any> getClassesImplementingWithClassVersionCheck(type: KClass<T>): List<T> {
|
||||||
return scanResult
|
return scanResult
|
||||||
.getClassesImplementing(type.java.name)
|
.getClassesImplementing(type.java.name)
|
||||||
.filter { it.name.startsWith(qualifiedNamePrefix) }
|
|
||||||
.mapNotNull {
|
.mapNotNull {
|
||||||
validateClassFileVersion(it)
|
validateClassFileVersion(it)
|
||||||
loadClass(it.name, type) }
|
loadClass(it.name, type) }
|
||||||
@ -437,9 +419,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
fun <T : Any> getClassesImplementing(type: KClass<T>): List<Class<out T>> {
|
fun <T : Any> getClassesImplementing(type: KClass<T>): List<Class<out T>> {
|
||||||
return scanResult
|
return scanResult
|
||||||
.getClassesImplementing(type.java.name)
|
.getClassesImplementing(type.java.name)
|
||||||
.filter { it.name.startsWith(qualifiedNamePrefix) }
|
.mapNotNull { loadClass(it.name, type) }
|
||||||
.mapNotNull {
|
|
||||||
loadClass(it.name, type) }
|
|
||||||
.filterNot { it.isAbstractClass }
|
.filterNot { it.isAbstractClass }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,7 +427,6 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
return scanResult
|
return scanResult
|
||||||
.getClassesWithAnnotation(annotation.java.name)
|
.getClassesWithAnnotation(annotation.java.name)
|
||||||
.names
|
.names
|
||||||
.filter { it.startsWith(qualifiedNamePrefix) }
|
|
||||||
.mapNotNull { loadClass(it, type) }
|
.mapNotNull { loadClass(it, type) }
|
||||||
.filterNot { Modifier.isAbstract(it.modifiers) }
|
.filterNot { Modifier.isAbstract(it.modifiers) }
|
||||||
}
|
}
|
||||||
@ -456,29 +435,18 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
return scanResult
|
return scanResult
|
||||||
.getSubclasses(type.java.name)
|
.getSubclasses(type.java.name)
|
||||||
.names
|
.names
|
||||||
.filter { it.startsWith(qualifiedNamePrefix) }
|
|
||||||
.mapNotNull { loadClass(it, type) }
|
.mapNotNull { loadClass(it, type) }
|
||||||
.filterNot { it.isAbstractClass }
|
.filterNot { it.isAbstractClass }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAllStandardClasses(): List<String> {
|
fun getAllStandardClasses(): List<String> = scanResult.allStandardClasses.names
|
||||||
return scanResult
|
|
||||||
.allStandardClasses
|
|
||||||
.names
|
|
||||||
.filter { it.startsWith(qualifiedNamePrefix) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAllInterfaces(): List<String> {
|
fun getAllInterfaces(): List<String> = scanResult.allInterfaces.names
|
||||||
return scanResult
|
|
||||||
.allInterfaces
|
|
||||||
.names
|
|
||||||
.filter { it.startsWith(qualifiedNamePrefix) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun validateClassFileVersion(classInfo: ClassInfo) {
|
private fun validateClassFileVersion(classInfo: ClassInfo) {
|
||||||
if (classInfo.classfileMajorVersion < JAVA_1_2_CLASS_FILE_FORMAT_MAJOR_VERSION ||
|
if (classInfo.classfileMajorVersion < JAVA_1_2_CLASS_FILE_FORMAT_MAJOR_VERSION ||
|
||||||
classInfo.classfileMajorVersion > JAVA_17_CLASS_FILE_FORMAT_MAJOR_VERSION)
|
classInfo.classfileMajorVersion > JAVA_17_CLASS_FILE_FORMAT_MAJOR_VERSION)
|
||||||
throw IllegalStateException("Class ${classInfo.name} from jar file ${cordappJarPath.url} has an invalid version of " +
|
throw IllegalStateException("Class ${classInfo.name} from jar file $cordappJar has an invalid version of " +
|
||||||
"${classInfo.classfileMajorVersion}")
|
"${classInfo.classfileMajorVersion}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -524,9 +492,7 @@ class DuplicateCordappsInstalledException(app: Cordapp, duplicates: Set<Cordapp>
|
|||||||
class InvalidCordappException(message: String) : CordaRuntimeException(message)
|
class InvalidCordappException(message: String) : CordaRuntimeException(message)
|
||||||
|
|
||||||
abstract class CordappLoaderTemplate : CordappLoader {
|
abstract class CordappLoaderTemplate : CordappLoader {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private val logger = contextLogger()
|
private val logger = contextLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import net.corda.core.crypto.SecureHash
|
|||||||
import net.corda.core.flows.ContractUpgradeFlow
|
import net.corda.core.flows.ContractUpgradeFlow
|
||||||
import net.corda.core.internal.cordapp.CordappImpl
|
import net.corda.core.internal.cordapp.CordappImpl
|
||||||
import net.corda.core.internal.location
|
import net.corda.core.internal.location
|
||||||
|
import net.corda.core.internal.toPath
|
||||||
import net.corda.node.VersionInfo
|
import net.corda.node.VersionInfo
|
||||||
import net.corda.notary.experimental.bftsmart.BFTSmartNotarySchemaV1
|
import net.corda.notary.experimental.bftsmart.BFTSmartNotarySchemaV1
|
||||||
import net.corda.notary.experimental.bftsmart.BFTSmartNotaryService
|
import net.corda.notary.experimental.bftsmart.BFTSmartNotaryService
|
||||||
@ -24,6 +25,7 @@ internal object VirtualCordapp {
|
|||||||
/** A Cordapp representing the core package which is not scanned automatically. */
|
/** A Cordapp representing the core package which is not scanned automatically. */
|
||||||
fun generateCore(versionInfo: VersionInfo): CordappImpl {
|
fun generateCore(versionInfo: VersionInfo): CordappImpl {
|
||||||
return CordappImpl(
|
return CordappImpl(
|
||||||
|
jarFile = ContractUpgradeFlow.javaClass.location.toPath(), // Core JAR location
|
||||||
contractClassNames = listOf(),
|
contractClassNames = listOf(),
|
||||||
initiatedFlows = listOf(),
|
initiatedFlows = listOf(),
|
||||||
rpcFlows = coreRpcFlows,
|
rpcFlows = coreRpcFlows,
|
||||||
@ -37,7 +39,6 @@ internal object VirtualCordapp {
|
|||||||
customSchemas = setOf(),
|
customSchemas = setOf(),
|
||||||
info = Cordapp.Info.Default("corda-core", versionInfo.vendor, versionInfo.releaseVersion, "Open Source (Apache 2)"),
|
info = Cordapp.Info.Default("corda-core", versionInfo.vendor, versionInfo.releaseVersion, "Open Source (Apache 2)"),
|
||||||
allFlows = listOf(),
|
allFlows = listOf(),
|
||||||
jarPath = ContractUpgradeFlow.javaClass.location, // Core JAR location
|
|
||||||
jarHash = SecureHash.allOnesHash,
|
jarHash = SecureHash.allOnesHash,
|
||||||
minimumPlatformVersion = versionInfo.platformVersion,
|
minimumPlatformVersion = versionInfo.platformVersion,
|
||||||
targetPlatformVersion = versionInfo.platformVersion,
|
targetPlatformVersion = versionInfo.platformVersion,
|
||||||
@ -49,6 +50,7 @@ internal object VirtualCordapp {
|
|||||||
/** A Cordapp for the built-in notary service implementation. */
|
/** A Cordapp for the built-in notary service implementation. */
|
||||||
fun generateJPANotary(versionInfo: VersionInfo): CordappImpl {
|
fun generateJPANotary(versionInfo: VersionInfo): CordappImpl {
|
||||||
return CordappImpl(
|
return CordappImpl(
|
||||||
|
jarFile = JPANotaryService::class.java.location.toPath(),
|
||||||
contractClassNames = listOf(),
|
contractClassNames = listOf(),
|
||||||
initiatedFlows = listOf(),
|
initiatedFlows = listOf(),
|
||||||
rpcFlows = listOf(),
|
rpcFlows = listOf(),
|
||||||
@ -62,7 +64,6 @@ internal object VirtualCordapp {
|
|||||||
customSchemas = setOf(JPANotarySchemaV1),
|
customSchemas = setOf(JPANotarySchemaV1),
|
||||||
info = Cordapp.Info.Default("corda-notary", versionInfo.vendor, versionInfo.releaseVersion, "Open Source (Apache 2)"),
|
info = Cordapp.Info.Default("corda-notary", versionInfo.vendor, versionInfo.releaseVersion, "Open Source (Apache 2)"),
|
||||||
allFlows = listOf(),
|
allFlows = listOf(),
|
||||||
jarPath = JPANotaryService::class.java.location,
|
|
||||||
jarHash = SecureHash.allOnesHash,
|
jarHash = SecureHash.allOnesHash,
|
||||||
minimumPlatformVersion = versionInfo.platformVersion,
|
minimumPlatformVersion = versionInfo.platformVersion,
|
||||||
targetPlatformVersion = versionInfo.platformVersion,
|
targetPlatformVersion = versionInfo.platformVersion,
|
||||||
@ -75,6 +76,7 @@ internal object VirtualCordapp {
|
|||||||
/** A Cordapp for the built-in Raft notary service implementation. */
|
/** A Cordapp for the built-in Raft notary service implementation. */
|
||||||
fun generateRaftNotary(versionInfo: VersionInfo): CordappImpl {
|
fun generateRaftNotary(versionInfo: VersionInfo): CordappImpl {
|
||||||
return CordappImpl(
|
return CordappImpl(
|
||||||
|
jarFile = RaftNotaryService::class.java.location.toPath(),
|
||||||
contractClassNames = listOf(),
|
contractClassNames = listOf(),
|
||||||
initiatedFlows = listOf(),
|
initiatedFlows = listOf(),
|
||||||
rpcFlows = listOf(),
|
rpcFlows = listOf(),
|
||||||
@ -88,7 +90,6 @@ internal object VirtualCordapp {
|
|||||||
customSchemas = setOf(RaftNotarySchemaV1),
|
customSchemas = setOf(RaftNotarySchemaV1),
|
||||||
info = Cordapp.Info.Default("corda-notary-raft", versionInfo.vendor, versionInfo.releaseVersion, "Open Source (Apache 2)"),
|
info = Cordapp.Info.Default("corda-notary-raft", versionInfo.vendor, versionInfo.releaseVersion, "Open Source (Apache 2)"),
|
||||||
allFlows = listOf(),
|
allFlows = listOf(),
|
||||||
jarPath = RaftNotaryService::class.java.location,
|
|
||||||
jarHash = SecureHash.allOnesHash,
|
jarHash = SecureHash.allOnesHash,
|
||||||
minimumPlatformVersion = versionInfo.platformVersion,
|
minimumPlatformVersion = versionInfo.platformVersion,
|
||||||
targetPlatformVersion = versionInfo.platformVersion,
|
targetPlatformVersion = versionInfo.platformVersion,
|
||||||
@ -100,6 +101,7 @@ internal object VirtualCordapp {
|
|||||||
/** A Cordapp for the built-in BFT-Smart notary service implementation. */
|
/** A Cordapp for the built-in BFT-Smart notary service implementation. */
|
||||||
fun generateBFTSmartNotary(versionInfo: VersionInfo): CordappImpl {
|
fun generateBFTSmartNotary(versionInfo: VersionInfo): CordappImpl {
|
||||||
return CordappImpl(
|
return CordappImpl(
|
||||||
|
jarFile = BFTSmartNotaryService::class.java.location.toPath(),
|
||||||
contractClassNames = listOf(),
|
contractClassNames = listOf(),
|
||||||
initiatedFlows = listOf(),
|
initiatedFlows = listOf(),
|
||||||
rpcFlows = listOf(),
|
rpcFlows = listOf(),
|
||||||
@ -113,7 +115,6 @@ internal object VirtualCordapp {
|
|||||||
customSchemas = setOf(BFTSmartNotarySchemaV1),
|
customSchemas = setOf(BFTSmartNotarySchemaV1),
|
||||||
info = Cordapp.Info.Default("corda-notary-bft-smart", versionInfo.vendor, versionInfo.releaseVersion, "Open Source (Apache 2)"),
|
info = Cordapp.Info.Default("corda-notary-bft-smart", versionInfo.vendor, versionInfo.releaseVersion, "Open Source (Apache 2)"),
|
||||||
allFlows = listOf(),
|
allFlows = listOf(),
|
||||||
jarPath = BFTSmartNotaryService::class.java.location,
|
|
||||||
jarHash = SecureHash.allOnesHash,
|
jarHash = SecureHash.allOnesHash,
|
||||||
minimumPlatformVersion = versionInfo.platformVersion,
|
minimumPlatformVersion = versionInfo.platformVersion,
|
||||||
targetPlatformVersion = versionInfo.platformVersion,
|
targetPlatformVersion = versionInfo.platformVersion,
|
||||||
|
@ -2,6 +2,7 @@ package net.corda.node.internal.cordapp
|
|||||||
|
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import net.corda.core.internal.toPath
|
||||||
import net.corda.core.node.services.AttachmentId
|
import net.corda.core.node.services.AttachmentId
|
||||||
import net.corda.core.node.services.AttachmentStorage
|
import net.corda.core.node.services.AttachmentStorage
|
||||||
import net.corda.node.VersionInfo
|
import net.corda.node.VersionInfo
|
||||||
@ -17,8 +18,8 @@ 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.net.URL
|
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
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 +29,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 = this::class.java.getResource("/isolated.jar")!!.toPath()
|
||||||
// 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 = this::class.java.getResource("empty.jar")!!.toPath()
|
||||||
val validConfig: Config = ConfigFactory.parseString("key=value")
|
val validConfig: Config = ConfigFactory.parseString("key=value")
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
@ -108,7 +109,7 @@ class CordappProviderImplTests {
|
|||||||
fun `test cordapp configuration`() {
|
fun `test cordapp configuration`() {
|
||||||
val configProvider = MockCordappConfigProvider()
|
val configProvider = MockCordappConfigProvider()
|
||||||
configProvider.cordappConfigs[isolatedCordappName] = validConfig
|
configProvider.cordappConfigs[isolatedCordappName] = validConfig
|
||||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR), VersionInfo.UNKNOWN)
|
val loader = JarScanningCordappLoader.fromJarUrls(setOf(isolatedJAR), VersionInfo.UNKNOWN)
|
||||||
val provider = CordappProviderImpl(loader, configProvider, attachmentStore).apply { start() }
|
val provider = CordappProviderImpl(loader, configProvider, attachmentStore).apply { start() }
|
||||||
|
|
||||||
val expected = provider.getAppContext(provider.cordapps.first()).config
|
val expected = provider.getAppContext(provider.cordapps.first()).config
|
||||||
@ -120,7 +121,7 @@ class CordappProviderImplTests {
|
|||||||
fun `test fixup rule that adds attachment`() {
|
fun `test fixup rule that adds attachment`() {
|
||||||
val fixupJar = File.createTempFile("fixup", ".jar")
|
val fixupJar = File.createTempFile("fixup", ".jar")
|
||||||
.writeFixupRules("$ID1 => $ID2, $ID3")
|
.writeFixupRules("$ID1 => $ID2, $ID3")
|
||||||
val fixedIDs = with(newCordappProvider(fixupJar.toURI().toURL())) {
|
val fixedIDs = with(newCordappProvider(fixupJar.toPath())) {
|
||||||
start()
|
start()
|
||||||
attachmentFixups.fixupAttachmentIds(listOf(ID1))
|
attachmentFixups.fixupAttachmentIds(listOf(ID1))
|
||||||
}
|
}
|
||||||
@ -131,7 +132,7 @@ class CordappProviderImplTests {
|
|||||||
fun `test fixup rule that deletes attachment`() {
|
fun `test fixup rule that deletes attachment`() {
|
||||||
val fixupJar = File.createTempFile("fixup", ".jar")
|
val fixupJar = File.createTempFile("fixup", ".jar")
|
||||||
.writeFixupRules("$ID1 =>")
|
.writeFixupRules("$ID1 =>")
|
||||||
val fixedIDs = with(newCordappProvider(fixupJar.toURI().toURL())) {
|
val fixedIDs = with(newCordappProvider(fixupJar.toPath())) {
|
||||||
start()
|
start()
|
||||||
attachmentFixups.fixupAttachmentIds(listOf(ID1))
|
attachmentFixups.fixupAttachmentIds(listOf(ID1))
|
||||||
}
|
}
|
||||||
@ -143,7 +144,7 @@ class CordappProviderImplTests {
|
|||||||
val fixupJar = File.createTempFile("fixup", ".jar")
|
val fixupJar = File.createTempFile("fixup", ".jar")
|
||||||
.writeFixupRules(" => $ID2")
|
.writeFixupRules(" => $ID2")
|
||||||
val ex = assertFailsWith<IllegalArgumentException> {
|
val ex = assertFailsWith<IllegalArgumentException> {
|
||||||
newCordappProvider(fixupJar.toURI().toURL()).start()
|
newCordappProvider(fixupJar.toPath()).start()
|
||||||
}
|
}
|
||||||
assertThat(ex).hasMessageContaining(
|
assertThat(ex).hasMessageContaining(
|
||||||
"Forbidden empty list of source attachment IDs in '${fixupJar.absolutePath}'"
|
"Forbidden empty list of source attachment IDs in '${fixupJar.absolutePath}'"
|
||||||
@ -156,7 +157,7 @@ class CordappProviderImplTests {
|
|||||||
val fixupJar = File.createTempFile("fixup", ".jar")
|
val fixupJar = File.createTempFile("fixup", ".jar")
|
||||||
.writeFixupRules(rule)
|
.writeFixupRules(rule)
|
||||||
val ex = assertFailsWith<IllegalArgumentException> {
|
val ex = assertFailsWith<IllegalArgumentException> {
|
||||||
newCordappProvider(fixupJar.toURI().toURL()).start()
|
newCordappProvider(fixupJar.toPath()).start()
|
||||||
}
|
}
|
||||||
assertThat(ex).hasMessageContaining(
|
assertThat(ex).hasMessageContaining(
|
||||||
"Invalid fix-up line '${rule.trim()}' in '${fixupJar.absolutePath}'"
|
"Invalid fix-up line '${rule.trim()}' in '${fixupJar.absolutePath}'"
|
||||||
@ -169,7 +170,7 @@ class CordappProviderImplTests {
|
|||||||
val fixupJar = File.createTempFile("fixup", ".jar")
|
val fixupJar = File.createTempFile("fixup", ".jar")
|
||||||
.writeFixupRules(rule)
|
.writeFixupRules(rule)
|
||||||
val ex = assertFailsWith<IllegalArgumentException> {
|
val ex = assertFailsWith<IllegalArgumentException> {
|
||||||
newCordappProvider(fixupJar.toURI().toURL()).start()
|
newCordappProvider(fixupJar.toPath()).start()
|
||||||
}
|
}
|
||||||
assertThat(ex).hasMessageContaining(
|
assertThat(ex).hasMessageContaining(
|
||||||
"Invalid fix-up line '${rule.trim()}' in '${fixupJar.absolutePath}'"
|
"Invalid fix-up line '${rule.trim()}' in '${fixupJar.absolutePath}'"
|
||||||
@ -185,7 +186,7 @@ class CordappProviderImplTests {
|
|||||||
"",
|
"",
|
||||||
"$ID3 => $ID4"
|
"$ID3 => $ID4"
|
||||||
)
|
)
|
||||||
val fixedIDs = with(newCordappProvider(fixupJar.toURI().toURL())) {
|
val fixedIDs = with(newCordappProvider(fixupJar.toPath())) {
|
||||||
start()
|
start()
|
||||||
attachmentFixups.fixupAttachmentIds(listOf(ID2, ID1))
|
attachmentFixups.fixupAttachmentIds(listOf(ID2, ID1))
|
||||||
}
|
}
|
||||||
@ -200,8 +201,8 @@ class CordappProviderImplTests {
|
|||||||
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 = listOf(signedJarPath.toUri().toURL(), duplicateJarPath.toUri().toURL())
|
val paths = setOf(signedJarPath, duplicateJarPath)
|
||||||
JarScanningCordappLoader.fromJarUrls(urls, VersionInfo.UNKNOWN).use {
|
JarScanningCordappLoader.fromJarUrls(paths, VersionInfo.UNKNOWN).use {
|
||||||
assertFailsWith<DuplicateCordappsInstalledException> {
|
assertFailsWith<DuplicateCordappsInstalledException> {
|
||||||
CordappProviderImpl(it, stubConfigProvider, attachmentStore).apply { start() }
|
CordappProviderImpl(it, stubConfigProvider, attachmentStore).apply { start() }
|
||||||
}
|
}
|
||||||
@ -214,8 +215,8 @@ class CordappProviderImplTests {
|
|||||||
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 = listOf(jarA.toUri().toURL(), jarB.toUri().toURL())
|
val paths = setOf(jarA, jarB)
|
||||||
JarScanningCordappLoader.fromJarUrls(urls, VersionInfo.UNKNOWN).use {
|
JarScanningCordappLoader.fromJarUrls(paths, VersionInfo.UNKNOWN).use {
|
||||||
assertFailsWith<IllegalStateException> {
|
assertFailsWith<IllegalStateException> {
|
||||||
CordappProviderImpl(it, stubConfigProvider, attachmentStore).apply { start() }
|
CordappProviderImpl(it, stubConfigProvider, attachmentStore).apply { start() }
|
||||||
}
|
}
|
||||||
@ -238,8 +239,8 @@ class CordappProviderImplTests {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun newCordappProvider(vararg urls: URL): CordappProviderImpl {
|
private fun newCordappProvider(vararg paths: Path): CordappProviderImpl {
|
||||||
val loader = JarScanningCordappLoader.fromJarUrls(urls.toList(), VersionInfo.UNKNOWN)
|
val loader = JarScanningCordappLoader.fromJarUrls(paths.toSet(), VersionInfo.UNKNOWN)
|
||||||
return CordappProviderImpl(loader, stubConfigProvider, attachmentStore).apply { start() }
|
return CordappProviderImpl(loader, stubConfigProvider, attachmentStore).apply { start() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import net.corda.core.flows.InitiatingFlow
|
|||||||
import net.corda.core.flows.SchedulableFlow
|
import net.corda.core.flows.SchedulableFlow
|
||||||
import net.corda.core.flows.StartableByRPC
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.internal.packageName_
|
import net.corda.core.internal.packageName_
|
||||||
|
import net.corda.core.internal.toPath
|
||||||
import net.corda.node.VersionInfo
|
import net.corda.node.VersionInfo
|
||||||
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
|
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
|
||||||
import net.corda.testing.node.internal.cordappWithPackages
|
import net.corda.testing.node.internal.cordappWithPackages
|
||||||
@ -55,8 +56,8 @@ class JarScanningCordappLoaderTest {
|
|||||||
|
|
||||||
@Test(timeout=300_000)
|
@Test(timeout=300_000)
|
||||||
fun `isolated JAR contains a CorDapp with a contract and plugin`() {
|
fun `isolated JAR contains a CorDapp with a contract and plugin`() {
|
||||||
val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("/isolated.jar")!!
|
val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("/isolated.jar")!!.toPath()
|
||||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR))
|
val loader = JarScanningCordappLoader.fromJarUrls(setOf(isolatedJAR))
|
||||||
|
|
||||||
assertThat(loader.cordapps).hasSize(1)
|
assertThat(loader.cordapps).hasSize(1)
|
||||||
|
|
||||||
@ -68,13 +69,13 @@ class JarScanningCordappLoaderTest {
|
|||||||
assertThat(actualCordapp.services).isEmpty()
|
assertThat(actualCordapp.services).isEmpty()
|
||||||
assertThat(actualCordapp.serializationWhitelists).hasSize(1)
|
assertThat(actualCordapp.serializationWhitelists).hasSize(1)
|
||||||
assertThat(actualCordapp.serializationWhitelists.first().javaClass.name).isEqualTo("net.corda.serialization.internal.DefaultWhitelist")
|
assertThat(actualCordapp.serializationWhitelists.first().javaClass.name).isEqualTo("net.corda.serialization.internal.DefaultWhitelist")
|
||||||
assertThat(actualCordapp.jarPath).isEqualTo(isolatedJAR)
|
assertThat(actualCordapp.jarFile).isEqualTo(isolatedJAR)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout=300_000)
|
@Test(timeout=300_000)
|
||||||
fun `constructed CordappImpl contains the right cordapp classes`() {
|
fun `constructed CordappImpl contains the right cordapp classes`() {
|
||||||
val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("/isolated.jar")!!
|
val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("/isolated.jar")!!.toPath()
|
||||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR))
|
val loader = JarScanningCordappLoader.fromJarUrls(setOf(isolatedJAR))
|
||||||
|
|
||||||
val actualCordapp = loader.cordapps.single()
|
val actualCordapp = loader.cordapps.single()
|
||||||
val cordappClasses = actualCordapp.cordappClasses
|
val cordappClasses = actualCordapp.cordappClasses
|
||||||
@ -86,7 +87,7 @@ class JarScanningCordappLoaderTest {
|
|||||||
@Test(timeout=300_000)
|
@Test(timeout=300_000)
|
||||||
fun `flows are loaded by loader`() {
|
fun `flows are loaded by loader`() {
|
||||||
val jarFile = cordappWithPackages(javaClass.packageName_).jarFile
|
val jarFile = cordappWithPackages(javaClass.packageName_).jarFile
|
||||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jarFile.toUri().toURL()))
|
val loader = JarScanningCordappLoader.fromJarUrls(setOf(jarFile))
|
||||||
|
|
||||||
// One cordapp from this source tree. In gradle it will also pick up the node jar.
|
// One cordapp from this source tree. In gradle it will also pick up the node jar.
|
||||||
assertThat(loader.cordapps).isNotEmpty
|
assertThat(loader.cordapps).isNotEmpty
|
||||||
@ -101,8 +102,8 @@ class JarScanningCordappLoaderTest {
|
|||||||
// being used internally. Later iterations will use a classloader per cordapp and this test can be retired.
|
// being used internally. Later iterations will use a classloader per cordapp and this test can be retired.
|
||||||
@Test(timeout=300_000)
|
@Test(timeout=300_000)
|
||||||
fun `cordapp classloader can load cordapp classes`() {
|
fun `cordapp classloader can load cordapp classes`() {
|
||||||
val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("/isolated.jar")!!
|
val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("/isolated.jar")!!.toPath()
|
||||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR), VersionInfo.UNKNOWN)
|
val loader = JarScanningCordappLoader.fromJarUrls(setOf(isolatedJAR), VersionInfo.UNKNOWN)
|
||||||
|
|
||||||
loader.appClassLoader.loadClass(isolatedContractId)
|
loader.appClassLoader.loadClass(isolatedContractId)
|
||||||
loader.appClassLoader.loadClass(isolatedFlowName)
|
loader.appClassLoader.loadClass(isolatedFlowName)
|
||||||
@ -110,8 +111,8 @@ class JarScanningCordappLoaderTest {
|
|||||||
|
|
||||||
@Test(timeout=300_000)
|
@Test(timeout=300_000)
|
||||||
fun `cordapp classloader sets target and min version to 1 if not specified`() {
|
fun `cordapp classloader sets target and min version to 1 if not specified`() {
|
||||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/no-min-or-target-version.jar")!!
|
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/no-min-or-target-version.jar")!!.toPath()
|
||||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN)
|
val loader = JarScanningCordappLoader.fromJarUrls(setOf(jar), VersionInfo.UNKNOWN)
|
||||||
loader.cordapps.forEach {
|
loader.cordapps.forEach {
|
||||||
assertThat(it.targetPlatformVersion).isEqualTo(1)
|
assertThat(it.targetPlatformVersion).isEqualTo(1)
|
||||||
assertThat(it.minimumPlatformVersion).isEqualTo(1)
|
assertThat(it.minimumPlatformVersion).isEqualTo(1)
|
||||||
@ -122,8 +123,8 @@ class JarScanningCordappLoaderTest {
|
|||||||
fun `cordapp classloader returns correct values for minPlatformVersion and targetVersion`() {
|
fun `cordapp classloader returns correct values for minPlatformVersion and targetVersion`() {
|
||||||
// load jar with min and target version in manifest
|
// load jar with min and target version in manifest
|
||||||
// make sure classloader extracts correct values
|
// make sure classloader extracts correct values
|
||||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-target-3.jar")!!
|
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-target-3.jar")!!.toPath()
|
||||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN)
|
val loader = JarScanningCordappLoader.fromJarUrls(setOf(jar), VersionInfo.UNKNOWN)
|
||||||
val cordapp = loader.cordapps.first()
|
val cordapp = loader.cordapps.first()
|
||||||
assertThat(cordapp.targetPlatformVersion).isEqualTo(3)
|
assertThat(cordapp.targetPlatformVersion).isEqualTo(3)
|
||||||
assertThat(cordapp.minimumPlatformVersion).isEqualTo(2)
|
assertThat(cordapp.minimumPlatformVersion).isEqualTo(2)
|
||||||
@ -132,8 +133,8 @@ class JarScanningCordappLoaderTest {
|
|||||||
@Test(timeout=300_000)
|
@Test(timeout=300_000)
|
||||||
fun `cordapp classloader sets target version to min version if target version is not specified`() {
|
fun `cordapp classloader sets target version to min version if target version is not specified`() {
|
||||||
// load jar with minVersion but not targetVersion in manifest
|
// load jar with minVersion but not targetVersion in manifest
|
||||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-no-target.jar")!!
|
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-no-target.jar")!!.toPath()
|
||||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN)
|
val loader = JarScanningCordappLoader.fromJarUrls(setOf(jar), VersionInfo.UNKNOWN)
|
||||||
// exclude the core cordapp
|
// exclude the core cordapp
|
||||||
val cordapp = loader.cordapps.first()
|
val cordapp = loader.cordapps.first()
|
||||||
assertThat(cordapp.targetPlatformVersion).isEqualTo(2)
|
assertThat(cordapp.targetPlatformVersion).isEqualTo(2)
|
||||||
@ -142,8 +143,8 @@ class JarScanningCordappLoaderTest {
|
|||||||
|
|
||||||
@Test(timeout = 300_000)
|
@Test(timeout = 300_000)
|
||||||
fun `cordapp classloader does not load apps when their min platform version is greater than the node platform version`() {
|
fun `cordapp classloader does not load apps when their min platform version is greater than the node platform version`() {
|
||||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-no-target.jar")!!
|
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-no-target.jar")!!.toPath()
|
||||||
val cordappLoader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 1))
|
val cordappLoader = JarScanningCordappLoader.fromJarUrls(setOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 1))
|
||||||
assertThatExceptionOfType(InvalidCordappException::class.java).isThrownBy {
|
assertThatExceptionOfType(InvalidCordappException::class.java).isThrownBy {
|
||||||
cordappLoader.cordapps
|
cordappLoader.cordapps
|
||||||
}
|
}
|
||||||
@ -151,29 +152,29 @@ class JarScanningCordappLoaderTest {
|
|||||||
|
|
||||||
@Test(timeout=300_000)
|
@Test(timeout=300_000)
|
||||||
fun `cordapp classloader does load apps when their min platform version is less than the platform version`() {
|
fun `cordapp classloader does load apps when their min platform version is less than the platform version`() {
|
||||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-target-3.jar")!!
|
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-target-3.jar")!!.toPath()
|
||||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 1000))
|
val loader = JarScanningCordappLoader.fromJarUrls(setOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 1000))
|
||||||
assertThat(loader.cordapps).hasSize(1)
|
assertThat(loader.cordapps).hasSize(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout=300_000)
|
@Test(timeout=300_000)
|
||||||
fun `cordapp classloader does load apps when their min platform version is equal to the platform version`() {
|
fun `cordapp classloader does load apps when their min platform version is equal to the platform version`() {
|
||||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-target-3.jar")!!
|
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-target-3.jar")!!.toPath()
|
||||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 2))
|
val loader = JarScanningCordappLoader.fromJarUrls(setOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 2))
|
||||||
assertThat(loader.cordapps).hasSize(1)
|
assertThat(loader.cordapps).hasSize(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout=300_000)
|
@Test(timeout=300_000)
|
||||||
fun `cordapp classloader loads app signed by allowed certificate`() {
|
fun `cordapp classloader loads app signed by allowed certificate`() {
|
||||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-dev-key.jar")!!
|
val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-dev-key.jar")!!.toPath()
|
||||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), cordappsSignerKeyFingerprintBlacklist = emptyList())
|
val loader = JarScanningCordappLoader.fromJarUrls(setOf(jar), cordappsSignerKeyFingerprintBlacklist = emptyList())
|
||||||
assertThat(loader.cordapps).hasSize(1)
|
assertThat(loader.cordapps).hasSize(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout = 300_000)
|
@Test(timeout = 300_000)
|
||||||
fun `cordapp classloader does not load app signed by blacklisted certificate`() {
|
fun `cordapp classloader does not load app signed by blacklisted certificate`() {
|
||||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-dev-key.jar")!!
|
val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-dev-key.jar")!!.toPath()
|
||||||
val cordappLoader = JarScanningCordappLoader.fromJarUrls(listOf(jar), cordappsSignerKeyFingerprintBlacklist = DEV_PUB_KEY_HASHES)
|
val cordappLoader = JarScanningCordappLoader.fromJarUrls(setOf(jar), cordappsSignerKeyFingerprintBlacklist = DEV_PUB_KEY_HASHES)
|
||||||
assertThatExceptionOfType(InvalidCordappException::class.java).isThrownBy {
|
assertThatExceptionOfType(InvalidCordappException::class.java).isThrownBy {
|
||||||
cordappLoader.cordapps
|
cordappLoader.cordapps
|
||||||
}
|
}
|
||||||
@ -181,8 +182,8 @@ class JarScanningCordappLoaderTest {
|
|||||||
|
|
||||||
@Test(timeout=300_000)
|
@Test(timeout=300_000)
|
||||||
fun `cordapp classloader loads app signed by both allowed and non-blacklisted certificate`() {
|
fun `cordapp classloader loads app signed by both allowed and non-blacklisted certificate`() {
|
||||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-two-keys.jar")!!
|
val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-two-keys.jar")!!.toPath()
|
||||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), cordappsSignerKeyFingerprintBlacklist = DEV_PUB_KEY_HASHES)
|
val loader = JarScanningCordappLoader.fromJarUrls(setOf(jar), cordappsSignerKeyFingerprintBlacklist = DEV_PUB_KEY_HASHES)
|
||||||
assertThat(loader.cordapps).hasSize(1)
|
assertThat(loader.cordapps).hasSize(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,7 +128,7 @@ open class MockServices private constructor(
|
|||||||
) : ServiceHub {
|
) : ServiceHub {
|
||||||
companion object {
|
companion object {
|
||||||
private fun cordappLoaderForPackages(packages: Iterable<String>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader {
|
private fun cordappLoaderForPackages(packages: Iterable<String>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader {
|
||||||
return JarScanningCordappLoader.fromJarUrls(cordappsForPackages(packages).map { it.jarFile.toUri().toURL() }, versionInfo)
|
return JarScanningCordappLoader.fromJarUrls(cordappsForPackages(packages).mapToSet { it.jarFile }, versionInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,10 +3,11 @@ apply plugin: 'org.jetbrains.kotlin.jvm'
|
|||||||
description 'Utilities needed for smoke tests in Corda'
|
description 'Utilities needed for smoke tests in Corda'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
api project(':test-common')
|
||||||
|
|
||||||
// Smoke tests do NOT have any Node code on the classpath!
|
// Smoke tests do NOT have any Node code on the classpath!
|
||||||
implementation project(':core')
|
implementation project(':core')
|
||||||
implementation project(':node-api')
|
implementation project(':node-api')
|
||||||
implementation project(':test-common')
|
|
||||||
implementation project(':client:rpc')
|
implementation project(':client:rpc')
|
||||||
|
|
||||||
implementation "com.google.guava:guava:$guava_version"
|
implementation "com.google.guava:guava:$guava_version"
|
||||||
|
@ -9,6 +9,7 @@ import net.corda.core.identity.CordaX500Name
|
|||||||
import net.corda.nodeapi.internal.config.User
|
import net.corda.nodeapi.internal.config.User
|
||||||
import net.corda.nodeapi.internal.config.toConfig
|
import net.corda.nodeapi.internal.config.toConfig
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.absolutePathString
|
||||||
|
|
||||||
class NodeParams @JvmOverloads constructor(
|
class NodeParams @JvmOverloads constructor(
|
||||||
val legalName: CordaX500Name,
|
val legalName: CordaX500Name,
|
||||||
@ -17,6 +18,7 @@ class NodeParams @JvmOverloads constructor(
|
|||||||
val rpcAdminPort: Int,
|
val rpcAdminPort: Int,
|
||||||
val users: List<User>,
|
val users: List<User>,
|
||||||
val cordappJars: List<Path> = emptyList(),
|
val cordappJars: List<Path> = emptyList(),
|
||||||
|
val jarDirs: List<Path> = emptyList(),
|
||||||
val clientRpcConfig: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
val clientRpcConfig: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
||||||
val devMode: Boolean = true,
|
val devMode: Boolean = true,
|
||||||
val version: String? = null
|
val version: String? = null
|
||||||
@ -37,6 +39,7 @@ class NodeParams @JvmOverloads constructor(
|
|||||||
.root())
|
.root())
|
||||||
.withValue("rpcUsers", valueFor(users.map { it.toConfig().root().unwrapped() }.toList()))
|
.withValue("rpcUsers", valueFor(users.map { it.toConfig().root().unwrapped() }.toList()))
|
||||||
.withValue("useTestClock", valueFor(true))
|
.withValue("useTestClock", valueFor(true))
|
||||||
|
.withValue("jarDirs", valueFor(jarDirs.map(Path::absolutePathString)))
|
||||||
.withValue("devMode", valueFor(devMode))
|
.withValue("devMode", valueFor(devMode))
|
||||||
return if (isNotary) {
|
return if (isNotary) {
|
||||||
config.withValue("notary", ConfigValueFactory.fromMap(mapOf("validating" to true)))
|
config.withValue("notary", ConfigValueFactory.fromMap(mapOf("validating" to true)))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user