mirror of
https://github.com/corda/corda.git
synced 2025-01-29 15:43:55 +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.assertTrue
|
||||
|
||||
|
||||
class CordaRPCClientReconnectionTest {
|
||||
|
||||
private val portAllocator = incrementalPortAllocation()
|
||||
|
@ -109,9 +109,9 @@ dependencies {
|
||||
smokeTestImplementation project(":client:rpc")
|
||||
smokeTestImplementation project(':smoke-test-utils')
|
||||
smokeTestImplementation project(':core-test-utils')
|
||||
smokeTestImplementation project(":finance:contracts")
|
||||
smokeTestImplementation project(":finance:workflows")
|
||||
smokeTestImplementation project(":testing:cordapps:4.11-workflows")
|
||||
smokeTestImplementation project(":finance:contracts")
|
||||
smokeTestImplementation "org.assertj:assertj-core:${assertj_version}"
|
||||
smokeTestImplementation "org.bouncycastle:bcprov-jdk18on:${bouncycastle_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 org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import java.net.URL
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.jar.JarOutputStream
|
||||
@ -31,7 +30,7 @@ class AttachmentFixupsTest {
|
||||
fun `test fixup rule that adds attachment`() {
|
||||
val fixupJar = Files.createTempFile("fixup", ".jar")
|
||||
.writeFixupRules("$ID1 => $ID2, $ID3")
|
||||
val fixedIDs = with(newFixupService(fixupJar.toUri().toURL())) {
|
||||
val fixedIDs = with(newFixupService(fixupJar)) {
|
||||
fixupAttachmentIds(listOf(ID1))
|
||||
}
|
||||
assertThat(fixedIDs).containsExactly(ID2, ID3)
|
||||
@ -41,7 +40,7 @@ class AttachmentFixupsTest {
|
||||
fun `test fixup rule that deletes attachment`() {
|
||||
val fixupJar = Files.createTempFile("fixup", ".jar")
|
||||
.writeFixupRules("$ID1 =>")
|
||||
val fixedIDs = with(newFixupService(fixupJar.toUri().toURL())) {
|
||||
val fixedIDs = with(newFixupService(fixupJar)) {
|
||||
fixupAttachmentIds(listOf(ID1))
|
||||
}
|
||||
assertThat(fixedIDs).isEmpty()
|
||||
@ -52,7 +51,7 @@ class AttachmentFixupsTest {
|
||||
val fixupJar = Files.createTempFile("fixup", ".jar")
|
||||
.writeFixupRules(" => $ID2")
|
||||
val ex = assertFailsWith<IllegalArgumentException> {
|
||||
newFixupService(fixupJar.toUri().toURL())
|
||||
newFixupService(fixupJar)
|
||||
}
|
||||
assertThat(ex).hasMessageContaining(
|
||||
"Forbidden empty list of source attachment IDs in '$fixupJar'"
|
||||
@ -65,7 +64,7 @@ class AttachmentFixupsTest {
|
||||
val fixupJar = Files.createTempFile("fixup", ".jar")
|
||||
.writeFixupRules(rule)
|
||||
val ex = assertFailsWith<IllegalArgumentException> {
|
||||
newFixupService(fixupJar.toUri().toURL())
|
||||
newFixupService(fixupJar)
|
||||
}
|
||||
assertThat(ex).hasMessageContaining(
|
||||
"Invalid fix-up line '${rule.trim()}' in '$fixupJar'"
|
||||
@ -78,7 +77,7 @@ class AttachmentFixupsTest {
|
||||
val fixupJar = Files.createTempFile("fixup", ".jar")
|
||||
.writeFixupRules(rule)
|
||||
val ex = assertFailsWith<IllegalArgumentException> {
|
||||
newFixupService(fixupJar.toUri().toURL())
|
||||
newFixupService(fixupJar)
|
||||
}
|
||||
assertThat(ex).hasMessageContaining(
|
||||
"Invalid fix-up line '${rule.trim()}' in '$fixupJar'"
|
||||
@ -94,7 +93,7 @@ class AttachmentFixupsTest {
|
||||
"",
|
||||
"$ID3 => $ID4"
|
||||
)
|
||||
val fixedIDs = with(newFixupService(fixupJar.toUri().toURL())) {
|
||||
val fixedIDs = with(newFixupService(fixupJar)) {
|
||||
fixupAttachmentIds(listOf(ID2, ID1))
|
||||
}
|
||||
assertThat(fixedIDs).containsExactlyInAnyOrder(ID2, ID4)
|
||||
@ -130,8 +129,8 @@ class AttachmentFixupsTest {
|
||||
}
|
||||
}
|
||||
|
||||
private fun newFixupService(vararg urls: URL): AttachmentFixups {
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(urls.toList(), VersionInfo.UNKNOWN)
|
||||
private fun newFixupService(vararg paths: Path): AttachmentFixups {
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(paths.toSet(), VersionInfo.UNKNOWN)
|
||||
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.
|
||||
*/
|
||||
@Suppress("INVISIBLE_MEMBER", "RemoveExplicitTypeArguments") // Because the external verifier uses Kotlin 1.2
|
||||
inline fun <T, R> Iterable<T>.mapToSet(transform: (T) -> R): Set<R> {
|
||||
return if (this is Collection) {
|
||||
when (size) {
|
||||
0 -> return emptySet()
|
||||
1 -> return setOf(transform(first()))
|
||||
else -> mapTo(LinkedHashSet<R>(mapCapacity(size)), transform)
|
||||
}
|
||||
} else {
|
||||
mapTo(LinkedHashSet<R>(), transform)
|
||||
inline fun <T, R> Collection<T>.mapToSet(transform: (T) -> R): Set<R> {
|
||||
return when (size) {
|
||||
0 -> return emptySet()
|
||||
1 -> return setOf(transform(first()))
|
||||
else -> mapTo(LinkedHashSet<R>(mapCapacity(size)), transform)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
return if (this is Collection && isEmpty()) {
|
||||
emptySet()
|
||||
} else {
|
||||
flatMapTo(LinkedHashSet(), transform)
|
||||
}
|
||||
inline fun <T, R> Collection<T>.flatMapToSet(transform: (T) -> Iterable<R>): Set<R> {
|
||||
return if (isEmpty()) emptySet() else flatMapTo(LinkedHashSet(), transform)
|
||||
}
|
||||
|
||||
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.VisibleForTesting
|
||||
import net.corda.core.internal.notary.NotaryService
|
||||
import net.corda.core.internal.toPath
|
||||
import net.corda.core.internal.telemetry.TelemetryComponent
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
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.SerializeAsToken
|
||||
import java.net.URL
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import kotlin.io.path.name
|
||||
|
||||
data class CordappImpl(
|
||||
val jarFile: Path,
|
||||
override val contractClassNames: List<String>,
|
||||
override val initiatedFlows: 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 customSchemas: Set<MappedSchema>,
|
||||
override val allFlows: List<Class<out FlowLogic<*>>>,
|
||||
override val jarPath: URL,
|
||||
override val info: Cordapp.Info,
|
||||
override val jarHash: SecureHash.SHA256,
|
||||
override val minimumPlatformVersion: Int,
|
||||
@ -41,7 +41,11 @@ data class CordappImpl(
|
||||
private val explicitCordappClasses: List<String> = emptyList(),
|
||||
val isVirtual: Boolean = false
|
||||
) : 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
|
||||
override val cordappClasses: List<String> = run {
|
||||
@ -50,7 +54,7 @@ data class CordappImpl(
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun jarName(url: URL): String = url.toPath().name.removeSuffix(".jar")
|
||||
fun jarName(url: Path): String = url.name.removeSuffix(".jar")
|
||||
|
||||
/** CorDapp manifest entries */
|
||||
const val CORDAPP_CONTRACT_NAME = "Cordapp-Contract-Name"
|
||||
@ -74,6 +78,7 @@ data class CordappImpl(
|
||||
|
||||
@VisibleForTesting
|
||||
val TEST_INSTANCE = CordappImpl(
|
||||
jarFile = Paths.get(""),
|
||||
contractClassNames = emptyList(),
|
||||
initiatedFlows = emptyList(),
|
||||
rpcFlows = emptyList(),
|
||||
@ -85,7 +90,6 @@ data class CordappImpl(
|
||||
serializationCustomSerializers = emptyList(),
|
||||
checkpointCustomSerializers = emptyList(),
|
||||
customSchemas = emptySet(),
|
||||
jarPath = Paths.get("").toUri().toURL(),
|
||||
info = UNKNOWN_INFO,
|
||||
allFlows = emptyList(),
|
||||
jarHash = SecureHash.allOnesHash,
|
||||
|
@ -4,6 +4,7 @@ import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.flows.FlowException
|
||||
import net.corda.core.internal.Version
|
||||
import net.corda.core.internal.mapToSet
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
|
||||
/**
|
||||
@ -14,6 +15,8 @@ import net.corda.core.serialization.CordaSerializable
|
||||
@CordaSerializable
|
||||
class MissingContractAttachments
|
||||
@JvmOverloads
|
||||
constructor(val states: List<TransactionState<ContractState>>, contractsClassName: String? = null, minimumRequiredContractClassVersion: Version? = null) : FlowException(
|
||||
"Cannot find contract attachments for " +
|
||||
"${contractsClassName ?: states.map { it.contract }.distinct()}${minimumRequiredContractClassVersion?.let { ", minimum required contract class version $minimumRequiredContractClassVersion"}}.")
|
||||
constructor(val states: List<TransactionState<ContractState>>,
|
||||
contractsClassName: String? = null,
|
||||
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.loadClassOfType
|
||||
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.SinglePartyNotaryService
|
||||
import net.corda.core.internal.objectOrNewInstance
|
||||
import net.corda.core.internal.pooledScan
|
||||
import net.corda.core.internal.readFully
|
||||
import net.corda.core.internal.telemetry.TelemetryComponent
|
||||
import net.corda.core.internal.toPath
|
||||
import net.corda.core.internal.toTypedArray
|
||||
import net.corda.core.internal.warnContractWithoutConstraintPropagation
|
||||
import net.corda.core.node.services.CordaService
|
||||
@ -46,7 +47,6 @@ import net.corda.nodeapi.internal.coreContractClasses
|
||||
import net.corda.serialization.internal.DefaultWhitelist
|
||||
import java.lang.reflect.Modifier
|
||||
import java.math.BigInteger
|
||||
import java.net.URL
|
||||
import java.net.URLClassLoader
|
||||
import java.nio.file.Path
|
||||
import java.util.Random
|
||||
@ -55,30 +55,36 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.jar.JarInputStream
|
||||
import java.util.jar.Manifest
|
||||
import java.util.zip.ZipInputStream
|
||||
import kotlin.io.path.absolutePathString
|
||||
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
|
||||
|
||||
/**
|
||||
* 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,
|
||||
extraCordapps: List<CordappImpl>,
|
||||
private val signerKeyFingerprintBlacklist: List<SecureHash> = emptyList()) : CordappLoaderTemplate() {
|
||||
init {
|
||||
if (cordappJarPaths.isEmpty()) {
|
||||
if (cordappJars.isEmpty()) {
|
||||
logger.info("No CorDapp paths provided")
|
||||
} else {
|
||||
logger.info("Loading CorDapps from ${cordappJarPaths.joinToString()}")
|
||||
logger.info("Loading CorDapps from ${cordappJars.joinToString()}")
|
||||
}
|
||||
}
|
||||
private val cordappClasses: ConcurrentHashMap<String, Set<Cordapp>> = ConcurrentHashMap()
|
||||
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()
|
||||
|
||||
@ -95,7 +101,10 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
extraCordapps: List<CordappImpl> = emptyList(),
|
||||
signerKeyFingerprintBlacklist: List<SecureHash> = emptyList()): JarScanningCordappLoader {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -104,50 +113,40 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
*
|
||||
* @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 {
|
||||
val paths = scanJars.map { it.restricted() }
|
||||
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() }
|
||||
}
|
||||
return JarScanningCordappLoader(scanJars, versionInfo, extraCordapps, cordappsSignerKeyFingerprintBlacklist)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadCordapps(): List<CordappImpl> {
|
||||
val invalidCordapps = mutableMapOf<String, URL>()
|
||||
val invalidCordapps = mutableMapOf<String, Path>()
|
||||
|
||||
val cordapps = cordappJarPaths
|
||||
.map { url -> scanCordapp(url).use { it.toCordapp(url) } }
|
||||
.filter {
|
||||
if (it.minimumPlatformVersion > versionInfo.platformVersion) {
|
||||
logger.warn("Not loading CorDapp ${it.info.shortName} (${it.info.vendor}) as it requires minimum " +
|
||||
"platform version ${it.minimumPlatformVersion} (This node is running version ${versionInfo.platformVersion}).")
|
||||
invalidCordapps.put("CorDapp requires minimumPlatformVersion: ${it.minimumPlatformVersion}, but was: ${versionInfo.platformVersion}", it.jarPath)
|
||||
val cordapps = cordappJars
|
||||
.map { path -> scanCordapp(path).use { it.toCordapp(path) } }
|
||||
.filter { cordapp ->
|
||||
if (cordapp.minimumPlatformVersion > versionInfo.platformVersion) {
|
||||
logger.warn("Not loading CorDapp ${cordapp.info.shortName} (${cordapp.info.vendor}) as it requires minimum " +
|
||||
"platform version ${cordapp.minimumPlatformVersion} (This node is running version ${versionInfo.platformVersion}).")
|
||||
invalidCordapps["CorDapp requires minimumPlatformVersion: ${cordapp.minimumPlatformVersion}, but was: ${versionInfo.platformVersion}"] = cordapp.jarFile
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
.filter {
|
||||
}.filter { cordapp ->
|
||||
if (signerKeyFingerprintBlacklist.isEmpty()) {
|
||||
true //Nothing blacklisted, no need to check
|
||||
} 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 }
|
||||
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
|
||||
else {
|
||||
logger.warn("Not loading CorDapp ${it.info.shortName} (${it.info.vendor}) as it is signed by blacklisted key(s) only (probably development key): " +
|
||||
} else {
|
||||
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 }}.")
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -185,14 +184,15 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
}
|
||||
}
|
||||
|
||||
private fun RestrictedScanResult.toCordapp(url: RestrictedURL): CordappImpl {
|
||||
val manifest: Manifest? = url.url.openStream().use { JarInputStream(it).manifest }
|
||||
val info = parseCordappInfo(manifest, CordappImpl.jarName(url.url))
|
||||
private fun RestrictedScanResult.toCordapp(path: Path): CordappImpl {
|
||||
val manifest: Manifest? = JarInputStream(path.inputStream()).use { it.manifest }
|
||||
val info = parseCordappInfo(manifest, CordappImpl.jarName(path))
|
||||
val minPlatformVersion = manifest?.get(CordappImpl.MIN_PLATFORM_VERSION)?.toIntOrNull() ?: 1
|
||||
val targetPlatformVersion = manifest?.get(CordappImpl.TARGET_PLATFORM_VERSION)?.toIntOrNull() ?: minPlatformVersion
|
||||
validateContractStateClassVersion(this)
|
||||
validateWhitelistClassVersion(this)
|
||||
return CordappImpl(
|
||||
path,
|
||||
findContractClassNamesWithVersionCheck(this),
|
||||
findInitiatedFlows(this),
|
||||
findRPCFlows(this),
|
||||
@ -200,14 +200,13 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
findSchedulableFlows(this),
|
||||
findServices(this),
|
||||
findTelemetryComponents(this),
|
||||
findWhitelists(url),
|
||||
findWhitelists(path),
|
||||
findSerializers(this),
|
||||
findCheckpointSerializers(this),
|
||||
findCustomSchemas(this),
|
||||
findAllFlows(this),
|
||||
url.url,
|
||||
info,
|
||||
getJarHash(url.url),
|
||||
path.hash,
|
||||
minPlatformVersion,
|
||||
targetPlatformVersion,
|
||||
findNotaryService(this),
|
||||
@ -291,8 +290,6 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
return result.firstOrNull()
|
||||
}
|
||||
|
||||
private fun getJarHash(url: URL): SecureHash.SHA256 = url.openStream().readFully().sha256()
|
||||
|
||||
private fun findServices(scanResult: RestrictedScanResult): List<Class<out SerializeAsToken>> {
|
||||
return scanResult.getClassesWithAnnotation(SerializeAsToken::class, CordaService::class)
|
||||
}
|
||||
@ -345,10 +342,10 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
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()
|
||||
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.
|
||||
}
|
||||
|
||||
@ -361,19 +358,18 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
}
|
||||
|
||||
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 {
|
||||
val cordappElement = cordappJarPath.url.toString()
|
||||
logger.info("Scanning CorDapp in $cordappElement")
|
||||
private fun scanCordapp(cordappJar: Path): RestrictedScanResult {
|
||||
logger.info("Scanning CorDapp ${cordappJar.absolutePathString()}")
|
||||
val scanResult = ClassGraph()
|
||||
.filterClasspathElementsByURL { elt -> elt == cordappJarPath.url }
|
||||
.filterClasspathElementsByURL { it.toPath().isSameFileAs(cordappJar) }
|
||||
.overrideClassLoaders(appClassLoader)
|
||||
.ignoreParentClassLoaders()
|
||||
.enableAllInfo()
|
||||
.pooledScan()
|
||||
return RestrictedScanResult(scanResult, cordappJarPath.qualifiedNamePrefix, cordappJarPath)
|
||||
return RestrictedScanResult(scanResult, cordappJar)
|
||||
}
|
||||
|
||||
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.
|
||||
/** @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 {
|
||||
|
||||
private inner class RestrictedScanResult(private val scanResult: ScanResult, private val cordappJar: Path) : AutoCloseable {
|
||||
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)
|
||||
it.name
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -418,7 +402,6 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
return scanResult
|
||||
.getSubclasses(type.java.name)
|
||||
.names
|
||||
.filter { it.startsWith(qualifiedNamePrefix) }
|
||||
.mapNotNull { loadClass(it, type) }
|
||||
.filterNot { it.isAbstractClass }
|
||||
}
|
||||
@ -426,7 +409,6 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
fun <T : Any> getClassesImplementingWithClassVersionCheck(type: KClass<T>): List<T> {
|
||||
return scanResult
|
||||
.getClassesImplementing(type.java.name)
|
||||
.filter { it.name.startsWith(qualifiedNamePrefix) }
|
||||
.mapNotNull {
|
||||
validateClassFileVersion(it)
|
||||
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>> {
|
||||
return scanResult
|
||||
.getClassesImplementing(type.java.name)
|
||||
.filter { it.name.startsWith(qualifiedNamePrefix) }
|
||||
.mapNotNull {
|
||||
loadClass(it.name, type) }
|
||||
.mapNotNull { loadClass(it.name, type) }
|
||||
.filterNot { it.isAbstractClass }
|
||||
}
|
||||
|
||||
@ -447,7 +427,6 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
return scanResult
|
||||
.getClassesWithAnnotation(annotation.java.name)
|
||||
.names
|
||||
.filter { it.startsWith(qualifiedNamePrefix) }
|
||||
.mapNotNull { loadClass(it, type) }
|
||||
.filterNot { Modifier.isAbstract(it.modifiers) }
|
||||
}
|
||||
@ -456,29 +435,18 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
return scanResult
|
||||
.getSubclasses(type.java.name)
|
||||
.names
|
||||
.filter { it.startsWith(qualifiedNamePrefix) }
|
||||
.mapNotNull { loadClass(it, type) }
|
||||
.filterNot { it.isAbstractClass }
|
||||
}
|
||||
|
||||
fun getAllStandardClasses(): List<String> {
|
||||
return scanResult
|
||||
.allStandardClasses
|
||||
.names
|
||||
.filter { it.startsWith(qualifiedNamePrefix) }
|
||||
}
|
||||
fun getAllStandardClasses(): List<String> = scanResult.allStandardClasses.names
|
||||
|
||||
fun getAllInterfaces(): List<String> {
|
||||
return scanResult
|
||||
.allInterfaces
|
||||
.names
|
||||
.filter { it.startsWith(qualifiedNamePrefix) }
|
||||
}
|
||||
fun getAllInterfaces(): List<String> = scanResult.allInterfaces.names
|
||||
|
||||
private fun validateClassFileVersion(classInfo: ClassInfo) {
|
||||
if (classInfo.classfileMajorVersion < JAVA_1_2_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}")
|
||||
}
|
||||
|
||||
@ -524,9 +492,7 @@ class DuplicateCordappsInstalledException(app: Cordapp, duplicates: Set<Cordapp>
|
||||
class InvalidCordappException(message: String) : CordaRuntimeException(message)
|
||||
|
||||
abstract class CordappLoaderTemplate : CordappLoader {
|
||||
|
||||
companion object {
|
||||
|
||||
private val logger = contextLogger()
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.ContractUpgradeFlow
|
||||
import net.corda.core.internal.cordapp.CordappImpl
|
||||
import net.corda.core.internal.location
|
||||
import net.corda.core.internal.toPath
|
||||
import net.corda.node.VersionInfo
|
||||
import net.corda.notary.experimental.bftsmart.BFTSmartNotarySchemaV1
|
||||
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. */
|
||||
fun generateCore(versionInfo: VersionInfo): CordappImpl {
|
||||
return CordappImpl(
|
||||
jarFile = ContractUpgradeFlow.javaClass.location.toPath(), // Core JAR location
|
||||
contractClassNames = listOf(),
|
||||
initiatedFlows = listOf(),
|
||||
rpcFlows = coreRpcFlows,
|
||||
@ -37,7 +39,6 @@ internal object VirtualCordapp {
|
||||
customSchemas = setOf(),
|
||||
info = Cordapp.Info.Default("corda-core", versionInfo.vendor, versionInfo.releaseVersion, "Open Source (Apache 2)"),
|
||||
allFlows = listOf(),
|
||||
jarPath = ContractUpgradeFlow.javaClass.location, // Core JAR location
|
||||
jarHash = SecureHash.allOnesHash,
|
||||
minimumPlatformVersion = versionInfo.platformVersion,
|
||||
targetPlatformVersion = versionInfo.platformVersion,
|
||||
@ -49,6 +50,7 @@ internal object VirtualCordapp {
|
||||
/** A Cordapp for the built-in notary service implementation. */
|
||||
fun generateJPANotary(versionInfo: VersionInfo): CordappImpl {
|
||||
return CordappImpl(
|
||||
jarFile = JPANotaryService::class.java.location.toPath(),
|
||||
contractClassNames = listOf(),
|
||||
initiatedFlows = listOf(),
|
||||
rpcFlows = listOf(),
|
||||
@ -62,7 +64,6 @@ internal object VirtualCordapp {
|
||||
customSchemas = setOf(JPANotarySchemaV1),
|
||||
info = Cordapp.Info.Default("corda-notary", versionInfo.vendor, versionInfo.releaseVersion, "Open Source (Apache 2)"),
|
||||
allFlows = listOf(),
|
||||
jarPath = JPANotaryService::class.java.location,
|
||||
jarHash = SecureHash.allOnesHash,
|
||||
minimumPlatformVersion = versionInfo.platformVersion,
|
||||
targetPlatformVersion = versionInfo.platformVersion,
|
||||
@ -75,6 +76,7 @@ internal object VirtualCordapp {
|
||||
/** A Cordapp for the built-in Raft notary service implementation. */
|
||||
fun generateRaftNotary(versionInfo: VersionInfo): CordappImpl {
|
||||
return CordappImpl(
|
||||
jarFile = RaftNotaryService::class.java.location.toPath(),
|
||||
contractClassNames = listOf(),
|
||||
initiatedFlows = listOf(),
|
||||
rpcFlows = listOf(),
|
||||
@ -88,7 +90,6 @@ internal object VirtualCordapp {
|
||||
customSchemas = setOf(RaftNotarySchemaV1),
|
||||
info = Cordapp.Info.Default("corda-notary-raft", versionInfo.vendor, versionInfo.releaseVersion, "Open Source (Apache 2)"),
|
||||
allFlows = listOf(),
|
||||
jarPath = RaftNotaryService::class.java.location,
|
||||
jarHash = SecureHash.allOnesHash,
|
||||
minimumPlatformVersion = versionInfo.platformVersion,
|
||||
targetPlatformVersion = versionInfo.platformVersion,
|
||||
@ -100,6 +101,7 @@ internal object VirtualCordapp {
|
||||
/** A Cordapp for the built-in BFT-Smart notary service implementation. */
|
||||
fun generateBFTSmartNotary(versionInfo: VersionInfo): CordappImpl {
|
||||
return CordappImpl(
|
||||
jarFile = BFTSmartNotaryService::class.java.location.toPath(),
|
||||
contractClassNames = listOf(),
|
||||
initiatedFlows = listOf(),
|
||||
rpcFlows = listOf(),
|
||||
@ -113,7 +115,6 @@ internal object VirtualCordapp {
|
||||
customSchemas = setOf(BFTSmartNotarySchemaV1),
|
||||
info = Cordapp.Info.Default("corda-notary-bft-smart", versionInfo.vendor, versionInfo.releaseVersion, "Open Source (Apache 2)"),
|
||||
allFlows = listOf(),
|
||||
jarPath = BFTSmartNotaryService::class.java.location,
|
||||
jarHash = SecureHash.allOnesHash,
|
||||
minimumPlatformVersion = versionInfo.platformVersion,
|
||||
targetPlatformVersion = versionInfo.platformVersion,
|
||||
|
@ -2,6 +2,7 @@ package net.corda.node.internal.cordapp
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import net.corda.core.internal.toPath
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.core.node.services.AttachmentStorage
|
||||
import net.corda.node.VersionInfo
|
||||
@ -17,8 +18,8 @@ import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.net.URL
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.jar.JarOutputStream
|
||||
import java.util.zip.Deflater.NO_COMPRESSION
|
||||
import java.util.zip.ZipEntry
|
||||
@ -28,10 +29,10 @@ import kotlin.test.assertFailsWith
|
||||
|
||||
class CordappProviderImplTests {
|
||||
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
|
||||
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")
|
||||
|
||||
@JvmField
|
||||
@ -108,7 +109,7 @@ class CordappProviderImplTests {
|
||||
fun `test cordapp configuration`() {
|
||||
val configProvider = MockCordappConfigProvider()
|
||||
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 expected = provider.getAppContext(provider.cordapps.first()).config
|
||||
@ -120,7 +121,7 @@ class CordappProviderImplTests {
|
||||
fun `test fixup rule that adds attachment`() {
|
||||
val fixupJar = File.createTempFile("fixup", ".jar")
|
||||
.writeFixupRules("$ID1 => $ID2, $ID3")
|
||||
val fixedIDs = with(newCordappProvider(fixupJar.toURI().toURL())) {
|
||||
val fixedIDs = with(newCordappProvider(fixupJar.toPath())) {
|
||||
start()
|
||||
attachmentFixups.fixupAttachmentIds(listOf(ID1))
|
||||
}
|
||||
@ -131,7 +132,7 @@ class CordappProviderImplTests {
|
||||
fun `test fixup rule that deletes attachment`() {
|
||||
val fixupJar = File.createTempFile("fixup", ".jar")
|
||||
.writeFixupRules("$ID1 =>")
|
||||
val fixedIDs = with(newCordappProvider(fixupJar.toURI().toURL())) {
|
||||
val fixedIDs = with(newCordappProvider(fixupJar.toPath())) {
|
||||
start()
|
||||
attachmentFixups.fixupAttachmentIds(listOf(ID1))
|
||||
}
|
||||
@ -143,7 +144,7 @@ class CordappProviderImplTests {
|
||||
val fixupJar = File.createTempFile("fixup", ".jar")
|
||||
.writeFixupRules(" => $ID2")
|
||||
val ex = assertFailsWith<IllegalArgumentException> {
|
||||
newCordappProvider(fixupJar.toURI().toURL()).start()
|
||||
newCordappProvider(fixupJar.toPath()).start()
|
||||
}
|
||||
assertThat(ex).hasMessageContaining(
|
||||
"Forbidden empty list of source attachment IDs in '${fixupJar.absolutePath}'"
|
||||
@ -156,7 +157,7 @@ class CordappProviderImplTests {
|
||||
val fixupJar = File.createTempFile("fixup", ".jar")
|
||||
.writeFixupRules(rule)
|
||||
val ex = assertFailsWith<IllegalArgumentException> {
|
||||
newCordappProvider(fixupJar.toURI().toURL()).start()
|
||||
newCordappProvider(fixupJar.toPath()).start()
|
||||
}
|
||||
assertThat(ex).hasMessageContaining(
|
||||
"Invalid fix-up line '${rule.trim()}' in '${fixupJar.absolutePath}'"
|
||||
@ -169,7 +170,7 @@ class CordappProviderImplTests {
|
||||
val fixupJar = File.createTempFile("fixup", ".jar")
|
||||
.writeFixupRules(rule)
|
||||
val ex = assertFailsWith<IllegalArgumentException> {
|
||||
newCordappProvider(fixupJar.toURI().toURL()).start()
|
||||
newCordappProvider(fixupJar.toPath()).start()
|
||||
}
|
||||
assertThat(ex).hasMessageContaining(
|
||||
"Invalid fix-up line '${rule.trim()}' in '${fixupJar.absolutePath}'"
|
||||
@ -185,7 +186,7 @@ class CordappProviderImplTests {
|
||||
"",
|
||||
"$ID3 => $ID4"
|
||||
)
|
||||
val fixedIDs = with(newCordappProvider(fixupJar.toURI().toURL())) {
|
||||
val fixedIDs = with(newCordappProvider(fixupJar.toPath())) {
|
||||
start()
|
||||
attachmentFixups.fixupAttachmentIds(listOf(ID2, ID1))
|
||||
}
|
||||
@ -200,8 +201,8 @@ class CordappProviderImplTests {
|
||||
val duplicateJarPath = signedJarPath.parent.resolve("duplicate-${signedJarPath.fileName}")
|
||||
|
||||
Files.copy(signedJarPath, duplicateJarPath)
|
||||
val urls = listOf(signedJarPath.toUri().toURL(), duplicateJarPath.toUri().toURL())
|
||||
JarScanningCordappLoader.fromJarUrls(urls, VersionInfo.UNKNOWN).use {
|
||||
val paths = setOf(signedJarPath, duplicateJarPath)
|
||||
JarScanningCordappLoader.fromJarUrls(paths, VersionInfo.UNKNOWN).use {
|
||||
assertFailsWith<DuplicateCordappsInstalledException> {
|
||||
CordappProviderImpl(it, stubConfigProvider, attachmentStore).apply { start() }
|
||||
}
|
||||
@ -214,8 +215,8 @@ class CordappProviderImplTests {
|
||||
SelfCleaningDir().use { file ->
|
||||
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 urls = listOf(jarA.toUri().toURL(), jarB.toUri().toURL())
|
||||
JarScanningCordappLoader.fromJarUrls(urls, VersionInfo.UNKNOWN).use {
|
||||
val paths = setOf(jarA, jarB)
|
||||
JarScanningCordappLoader.fromJarUrls(paths, VersionInfo.UNKNOWN).use {
|
||||
assertFailsWith<IllegalStateException> {
|
||||
CordappProviderImpl(it, stubConfigProvider, attachmentStore).apply { start() }
|
||||
}
|
||||
@ -238,8 +239,8 @@ class CordappProviderImplTests {
|
||||
return this
|
||||
}
|
||||
|
||||
private fun newCordappProvider(vararg urls: URL): CordappProviderImpl {
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(urls.toList(), VersionInfo.UNKNOWN)
|
||||
private fun newCordappProvider(vararg paths: Path): CordappProviderImpl {
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(paths.toSet(), VersionInfo.UNKNOWN)
|
||||
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.StartableByRPC
|
||||
import net.corda.core.internal.packageName_
|
||||
import net.corda.core.internal.toPath
|
||||
import net.corda.node.VersionInfo
|
||||
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
|
||||
import net.corda.testing.node.internal.cordappWithPackages
|
||||
@ -55,8 +56,8 @@ class JarScanningCordappLoaderTest {
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `isolated JAR contains a CorDapp with a contract and plugin`() {
|
||||
val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("/isolated.jar")!!
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR))
|
||||
val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("/isolated.jar")!!.toPath()
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(setOf(isolatedJAR))
|
||||
|
||||
assertThat(loader.cordapps).hasSize(1)
|
||||
|
||||
@ -68,13 +69,13 @@ class JarScanningCordappLoaderTest {
|
||||
assertThat(actualCordapp.services).isEmpty()
|
||||
assertThat(actualCordapp.serializationWhitelists).hasSize(1)
|
||||
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)
|
||||
fun `constructed CordappImpl contains the right cordapp classes`() {
|
||||
val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("/isolated.jar")!!
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR))
|
||||
val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("/isolated.jar")!!.toPath()
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(setOf(isolatedJAR))
|
||||
|
||||
val actualCordapp = loader.cordapps.single()
|
||||
val cordappClasses = actualCordapp.cordappClasses
|
||||
@ -86,7 +87,7 @@ class JarScanningCordappLoaderTest {
|
||||
@Test(timeout=300_000)
|
||||
fun `flows are loaded by loader`() {
|
||||
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.
|
||||
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.
|
||||
@Test(timeout=300_000)
|
||||
fun `cordapp classloader can load cordapp classes`() {
|
||||
val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("/isolated.jar")!!
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR), VersionInfo.UNKNOWN)
|
||||
val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("/isolated.jar")!!.toPath()
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(setOf(isolatedJAR), VersionInfo.UNKNOWN)
|
||||
|
||||
loader.appClassLoader.loadClass(isolatedContractId)
|
||||
loader.appClassLoader.loadClass(isolatedFlowName)
|
||||
@ -110,8 +111,8 @@ class JarScanningCordappLoaderTest {
|
||||
|
||||
@Test(timeout=300_000)
|
||||
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 loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN)
|
||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/no-min-or-target-version.jar")!!.toPath()
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(setOf(jar), VersionInfo.UNKNOWN)
|
||||
loader.cordapps.forEach {
|
||||
assertThat(it.targetPlatformVersion).isEqualTo(1)
|
||||
assertThat(it.minimumPlatformVersion).isEqualTo(1)
|
||||
@ -122,8 +123,8 @@ class JarScanningCordappLoaderTest {
|
||||
fun `cordapp classloader returns correct values for minPlatformVersion and targetVersion`() {
|
||||
// load jar with min and target version in manifest
|
||||
// make sure classloader extracts correct values
|
||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-target-3.jar")!!
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN)
|
||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-target-3.jar")!!.toPath()
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(setOf(jar), VersionInfo.UNKNOWN)
|
||||
val cordapp = loader.cordapps.first()
|
||||
assertThat(cordapp.targetPlatformVersion).isEqualTo(3)
|
||||
assertThat(cordapp.minimumPlatformVersion).isEqualTo(2)
|
||||
@ -132,8 +133,8 @@ class JarScanningCordappLoaderTest {
|
||||
@Test(timeout=300_000)
|
||||
fun `cordapp classloader sets target version to min version if target version is not specified`() {
|
||||
// load jar with minVersion but not targetVersion in manifest
|
||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-no-target.jar")!!
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN)
|
||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-no-target.jar")!!.toPath()
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(setOf(jar), VersionInfo.UNKNOWN)
|
||||
// exclude the core cordapp
|
||||
val cordapp = loader.cordapps.first()
|
||||
assertThat(cordapp.targetPlatformVersion).isEqualTo(2)
|
||||
@ -142,8 +143,8 @@ class JarScanningCordappLoaderTest {
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
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 cordappLoader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 1))
|
||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-no-target.jar")!!.toPath()
|
||||
val cordappLoader = JarScanningCordappLoader.fromJarUrls(setOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 1))
|
||||
assertThatExceptionOfType(InvalidCordappException::class.java).isThrownBy {
|
||||
cordappLoader.cordapps
|
||||
}
|
||||
@ -151,29 +152,29 @@ class JarScanningCordappLoaderTest {
|
||||
|
||||
@Test(timeout=300_000)
|
||||
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 loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 1000))
|
||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-target-3.jar")!!.toPath()
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(setOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 1000))
|
||||
assertThat(loader.cordapps).hasSize(1)
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
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 loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 2))
|
||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-target-3.jar")!!.toPath()
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(setOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 2))
|
||||
assertThat(loader.cordapps).hasSize(1)
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `cordapp classloader loads app signed by allowed certificate`() {
|
||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-dev-key.jar")!!
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), cordappsSignerKeyFingerprintBlacklist = emptyList())
|
||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-dev-key.jar")!!.toPath()
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(setOf(jar), cordappsSignerKeyFingerprintBlacklist = emptyList())
|
||||
assertThat(loader.cordapps).hasSize(1)
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `cordapp classloader does not load app signed by blacklisted certificate`() {
|
||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-dev-key.jar")!!
|
||||
val cordappLoader = JarScanningCordappLoader.fromJarUrls(listOf(jar), cordappsSignerKeyFingerprintBlacklist = DEV_PUB_KEY_HASHES)
|
||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-dev-key.jar")!!.toPath()
|
||||
val cordappLoader = JarScanningCordappLoader.fromJarUrls(setOf(jar), cordappsSignerKeyFingerprintBlacklist = DEV_PUB_KEY_HASHES)
|
||||
assertThatExceptionOfType(InvalidCordappException::class.java).isThrownBy {
|
||||
cordappLoader.cordapps
|
||||
}
|
||||
@ -181,8 +182,8 @@ class JarScanningCordappLoaderTest {
|
||||
|
||||
@Test(timeout=300_000)
|
||||
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 loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), cordappsSignerKeyFingerprintBlacklist = DEV_PUB_KEY_HASHES)
|
||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-two-keys.jar")!!.toPath()
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(setOf(jar), cordappsSignerKeyFingerprintBlacklist = DEV_PUB_KEY_HASHES)
|
||||
assertThat(loader.cordapps).hasSize(1)
|
||||
}
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ open class MockServices private constructor(
|
||||
) : ServiceHub {
|
||||
companion object {
|
||||
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'
|
||||
|
||||
dependencies {
|
||||
api project(':test-common')
|
||||
|
||||
// Smoke tests do NOT have any Node code on the classpath!
|
||||
implementation project(':core')
|
||||
implementation project(':node-api')
|
||||
implementation project(':test-common')
|
||||
implementation project(':client:rpc')
|
||||
|
||||
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.toConfig
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolutePathString
|
||||
|
||||
class NodeParams @JvmOverloads constructor(
|
||||
val legalName: CordaX500Name,
|
||||
@ -17,6 +18,7 @@ class NodeParams @JvmOverloads constructor(
|
||||
val rpcAdminPort: Int,
|
||||
val users: List<User>,
|
||||
val cordappJars: List<Path> = emptyList(),
|
||||
val jarDirs: List<Path> = emptyList(),
|
||||
val clientRpcConfig: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
||||
val devMode: Boolean = true,
|
||||
val version: String? = null
|
||||
@ -37,6 +39,7 @@ class NodeParams @JvmOverloads constructor(
|
||||
.root())
|
||||
.withValue("rpcUsers", valueFor(users.map { it.toConfig().root().unwrapped() }.toList()))
|
||||
.withValue("useTestClock", valueFor(true))
|
||||
.withValue("jarDirs", valueFor(jarDirs.map(Path::absolutePathString)))
|
||||
.withValue("devMode", valueFor(devMode))
|
||||
return if (isNotary) {
|
||||
config.withValue("notary", ConfigValueFactory.fromMap(mapOf("validating" to true)))
|
||||
|
Loading…
x
Reference in New Issue
Block a user