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:
Shams Asari 2024-01-29 13:44:14 +00:00 committed by GitHub
parent d642ebfbd7
commit a7d0684fe7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 142 additions and 172 deletions

View File

@ -53,7 +53,6 @@ import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue
class CordaRPCClientReconnectionTest {
private val portAllocator = incrementalPortAllocation()

View File

@ -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"

View File

@ -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) }
}
}

View File

@ -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)

View File

@ -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,

View File

@ -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"} ?: ""}.")

View File

@ -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()
}

View File

@ -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,

View File

@ -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() }
}
}

View File

@ -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)
}
}

View File

@ -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)
}
/**

View File

@ -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"

View File

@ -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)))