mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
CORDA-3644: Scan the CorDapp classloader directly for SerializationWhitelist. (#6014)
* CORDA-3644: Scan the CorDapp classloader directly for SerializationWhitelist. * CORDA-3644: Filter CorDapps from out-of-process node classpaths by their manifest attributes. Also exclude directories and blatant test artifacts. * Fix IRS Demo - its "tests" artifact had a non-standard classifier of "test".
This commit is contained in:
parent
20c5040826
commit
e006b871c8
@ -1,6 +1,5 @@
|
|||||||
package net.corda.docs.java.tutorial.test;
|
package net.corda.docs.java.tutorial.test;
|
||||||
|
|
||||||
import kotlin.Unit;
|
|
||||||
import net.corda.client.rpc.CordaRPCClient;
|
import net.corda.client.rpc.CordaRPCClient;
|
||||||
import net.corda.core.messaging.CordaRPCOps;
|
import net.corda.core.messaging.CordaRPCOps;
|
||||||
import net.corda.core.utilities.KotlinUtilsKt;
|
import net.corda.core.utilities.KotlinUtilsKt;
|
||||||
@ -10,24 +9,24 @@ import net.corda.testing.driver.*;
|
|||||||
import net.corda.testing.node.User;
|
import net.corda.testing.node.User;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
import static java.util.Collections.singleton;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
import static net.corda.testing.core.TestConstants.ALICE_NAME;
|
import static net.corda.testing.core.TestConstants.ALICE_NAME;
|
||||||
|
import static net.corda.testing.driver.Driver.driver;
|
||||||
|
import static net.corda.testing.node.internal.InternalTestUtilsKt.cordappWithPackages;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
public final class TutorialFlowAsyncOperationTest {
|
public class TutorialFlowAsyncOperationTest {
|
||||||
// DOCSTART summingWorks
|
// DOCSTART summingWorks
|
||||||
@Test
|
@Test
|
||||||
public final void summingWorks() {
|
public void summingWorks() {
|
||||||
Driver.driver(new DriverParameters(), (DriverDSL dsl) -> {
|
driver(new DriverParameters(singletonList(cordappWithPackages("net.corda.docs.java.tutorial.flowstatemachines"))), (DriverDSL dsl) -> {
|
||||||
User aliceUser = new User("aliceUser", "testPassword1",
|
User aliceUser = new User("aliceUser", "testPassword1", singleton(Permissions.all()));
|
||||||
new HashSet<>(Collections.singletonList(Permissions.all()))
|
|
||||||
);
|
|
||||||
Future<NodeHandle> aliceFuture = dsl.startNode(new NodeParameters()
|
Future<NodeHandle> aliceFuture = dsl.startNode(new NodeParameters()
|
||||||
.withProvidedName(ALICE_NAME)
|
.withProvidedName(ALICE_NAME)
|
||||||
.withRpcUsers(Collections.singletonList(aliceUser))
|
.withRpcUsers(singletonList(aliceUser))
|
||||||
);
|
);
|
||||||
NodeHandle alice = KotlinUtilsKt.getOrThrow(aliceFuture, null);
|
NodeHandle alice = KotlinUtilsKt.getOrThrow(aliceFuture, null);
|
||||||
CordaRPCClient aliceClient = new CordaRPCClient(alice.getRpcAddress());
|
CordaRPCClient aliceClient = new CordaRPCClient(alice.getRpcAddress());
|
||||||
@ -35,7 +34,7 @@ public final class TutorialFlowAsyncOperationTest {
|
|||||||
Future<Integer> answerFuture = aliceProxy.startFlowDynamic(ExampleSummingFlow.class).getReturnValue();
|
Future<Integer> answerFuture = aliceProxy.startFlowDynamic(ExampleSummingFlow.class).getReturnValue();
|
||||||
int answer = KotlinUtilsKt.getOrThrow(answerFuture, null);
|
int answer = KotlinUtilsKt.getOrThrow(answerFuture, null);
|
||||||
assertEquals(3, answer);
|
assertEquals(3, answer);
|
||||||
return Unit.INSTANCE;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// DOCEND summingWorks
|
// DOCEND summingWorks
|
||||||
|
@ -10,7 +10,6 @@ import net.corda.testing.driver.DriverParameters
|
|||||||
import net.corda.testing.driver.driver
|
import net.corda.testing.driver.driver
|
||||||
import net.corda.testing.node.User
|
import net.corda.testing.node.User
|
||||||
import net.corda.testing.node.internal.cordappWithPackages
|
import net.corda.testing.node.internal.cordappWithPackages
|
||||||
import net.corda.testing.node.internal.findCordapp
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.corda.node.logging
|
package net.corda.node.logging
|
||||||
|
|
||||||
import net.corda.core.flows.FlowException
|
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
import net.corda.core.flows.StartableByRPC
|
import net.corda.core.flows.StartableByRPC
|
||||||
@ -23,7 +22,13 @@ class ErrorCodeLoggingTests {
|
|||||||
node.rpc.startFlow(::MyFlow).waitForCompletion()
|
node.rpc.startFlow(::MyFlow).waitForCompletion()
|
||||||
val logFile = node.logFile()
|
val logFile = node.logFile()
|
||||||
|
|
||||||
val linesWithErrorCode = logFile.useLines { lines -> lines.filter { line -> line.contains("[errorCode=") }.filter { line -> line.contains("moreInformationAt=https://errors.corda.net/") }.toList() }
|
val linesWithErrorCode = logFile.useLines { lines ->
|
||||||
|
lines.filter { line ->
|
||||||
|
line.contains("[errorCode=")
|
||||||
|
}.filter { line ->
|
||||||
|
line.contains("moreInformationAt=https://errors.corda.net/")
|
||||||
|
}.toList()
|
||||||
|
}
|
||||||
|
|
||||||
assertThat(linesWithErrorCode).isNotEmpty
|
assertThat(linesWithErrorCode).isNotEmpty
|
||||||
}
|
}
|
||||||
@ -35,10 +40,11 @@ class ErrorCodeLoggingTests {
|
|||||||
fun `When logging is set to error level, there are no other levels logged after node startup`() {
|
fun `When logging is set to error level, there are no other levels logged after node startup`() {
|
||||||
driver(DriverParameters(notarySpecs = emptyList())) {
|
driver(DriverParameters(notarySpecs = emptyList())) {
|
||||||
val node = startNode(startInSameProcess = false, logLevelOverride = "ERROR").getOrThrow()
|
val node = startNode(startInSameProcess = false, logLevelOverride = "ERROR").getOrThrow()
|
||||||
node.rpc.startFlow(::MyFlow).waitForCompletion()
|
|
||||||
val logFile = node.logFile()
|
val logFile = node.logFile()
|
||||||
|
val lengthAfterStart = logFile.length()
|
||||||
|
node.rpc.startFlow(::MyFlow).waitForCompletion()
|
||||||
// An exception thrown in a flow will log at the "INFO" level.
|
// An exception thrown in a flow will log at the "INFO" level.
|
||||||
assertThat(logFile.length()).isEqualTo(0)
|
assertThat(logFile.length()).isEqualTo(lengthAfterStart)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ import net.corda.node.VersionInfo
|
|||||||
import net.corda.nodeapi.internal.cordapp.CordappLoader
|
import net.corda.nodeapi.internal.cordapp.CordappLoader
|
||||||
import net.corda.nodeapi.internal.coreContractClasses
|
import net.corda.nodeapi.internal.coreContractClasses
|
||||||
import net.corda.serialization.internal.DefaultWhitelist
|
import net.corda.serialization.internal.DefaultWhitelist
|
||||||
import org.apache.commons.collections4.map.LRUMap
|
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
@ -293,9 +292,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun findWhitelists(cordappJarPath: RestrictedURL): List<SerializationWhitelist> {
|
private fun findWhitelists(cordappJarPath: RestrictedURL): List<SerializationWhitelist> {
|
||||||
val whitelists = URLClassLoader(arrayOf(cordappJarPath.url)).use {
|
val whitelists = ServiceLoader.load(SerializationWhitelist::class.java, appClassLoader).toList()
|
||||||
ServiceLoader.load(SerializationWhitelist::class.java, it).toList()
|
|
||||||
}
|
|
||||||
return whitelists.filter {
|
return whitelists.filter {
|
||||||
it.javaClass.location == cordappJarPath.url && it.javaClass.name.startsWith(cordappJarPath.qualifiedNamePrefix)
|
it.javaClass.location == cordappJarPath.url && it.javaClass.name.startsWith(cordappJarPath.qualifiedNamePrefix)
|
||||||
} + DefaultWhitelist // Always add the DefaultWhitelist to the whitelist for an app.
|
} + DefaultWhitelist // Always add the DefaultWhitelist to the whitelist for an app.
|
||||||
@ -309,19 +306,21 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
return scanResult.getClassesWithSuperclass(MappedSchema::class).instances().toSet()
|
return scanResult.getClassesWithSuperclass(MappedSchema::class).instances().toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val cachedScanResult = LRUMap<RestrictedURL, RestrictedScanResult>(1000)
|
|
||||||
|
|
||||||
private fun scanCordapp(cordappJarPath: RestrictedURL): RestrictedScanResult {
|
private fun scanCordapp(cordappJarPath: RestrictedURL): RestrictedScanResult {
|
||||||
logger.info("Scanning CorDapp in ${cordappJarPath.url}")
|
val cordappElement = cordappJarPath.url.toString()
|
||||||
return cachedScanResult.computeIfAbsent(cordappJarPath) {
|
logger.info("Scanning CorDapp in $cordappElement")
|
||||||
val scanResult = ClassGraph().addClassLoader(appClassLoader).overrideClasspath(cordappJarPath.url).enableAllInfo().pooledScan()
|
val scanResult = ClassGraph()
|
||||||
RestrictedScanResult(scanResult, cordappJarPath.qualifiedNamePrefix)
|
.filterClasspathElements { elt -> elt == cordappElement }
|
||||||
}
|
.overrideClassLoaders(appClassLoader)
|
||||||
|
.ignoreParentClassLoaders()
|
||||||
|
.enableAllInfo()
|
||||||
|
.pooledScan()
|
||||||
|
return RestrictedScanResult(scanResult, cordappJarPath.qualifiedNamePrefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T : Any> loadClass(className: String, type: KClass<T>): Class<out T>? {
|
private fun <T : Any> loadClass(className: String, type: KClass<T>): Class<out T>? {
|
||||||
return try {
|
return try {
|
||||||
appClassLoader.loadClass(className).asSubclass(type.java)
|
Class.forName(className, false, appClassLoader).asSubclass(type.java)
|
||||||
} catch (e: ClassCastException) {
|
} catch (e: ClassCastException) {
|
||||||
logger.warn("As $className must be a sub-type of ${type.java.name}")
|
logger.warn("As $className must be a sub-type of ${type.java.name}")
|
||||||
null
|
null
|
||||||
|
@ -56,7 +56,7 @@ jar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
task testJar(type: Jar) {
|
task testJar(type: Jar) {
|
||||||
classifier "test"
|
classifier "tests"
|
||||||
from sourceSets.main.output
|
from sourceSets.main.output
|
||||||
from sourceSets.test.output
|
from sourceSets.test.output
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,20 @@ import net.corda.core.internal.concurrent.fork
|
|||||||
import net.corda.core.internal.concurrent.map
|
import net.corda.core.internal.concurrent.map
|
||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
import net.corda.core.internal.concurrent.transpose
|
import net.corda.core.internal.concurrent.transpose
|
||||||
|
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_NAME
|
||||||
|
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_LICENCE
|
||||||
|
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_VENDOR
|
||||||
|
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_VERSION
|
||||||
|
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_NAME
|
||||||
|
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_LICENCE
|
||||||
|
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_VENDOR
|
||||||
|
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_VERSION
|
||||||
|
import net.corda.core.internal.cordapp.CordappImpl.Companion.MIN_PLATFORM_VERSION
|
||||||
|
import net.corda.core.internal.cordapp.CordappImpl.Companion.TARGET_PLATFORM_VERSION
|
||||||
|
import net.corda.core.internal.cordapp.get
|
||||||
import net.corda.core.internal.createDirectories
|
import net.corda.core.internal.createDirectories
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
|
import net.corda.core.internal.isRegularFile
|
||||||
import net.corda.core.internal.list
|
import net.corda.core.internal.list
|
||||||
import net.corda.core.internal.packageName_
|
import net.corda.core.internal.packageName_
|
||||||
import net.corda.core.internal.readObject
|
import net.corda.core.internal.readObject
|
||||||
@ -80,24 +92,26 @@ import okhttp3.OkHttpClient
|
|||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import java.io.File
|
|
||||||
import java.net.ConnectException
|
import java.net.ConnectException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.ZoneOffset.UTC
|
import java.time.ZoneOffset.UTC
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.util.Random
|
import java.util.*
|
||||||
import java.util.UUID
|
import java.util.Collections.unmodifiableList
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.ScheduledExecutorService
|
import java.util.concurrent.ScheduledExecutorService
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.TimeoutException
|
import java.util.concurrent.TimeoutException
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import java.util.jar.JarInputStream
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
import kotlin.collections.HashSet
|
import kotlin.collections.HashSet
|
||||||
@ -792,6 +806,17 @@ class DriverDSLImpl(
|
|||||||
Permissions.invokeRpc(CordaRPCOps::killFlow)
|
Permissions.invokeRpc(CordaRPCOps::killFlow)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val CORDAPP_MANIFEST_ATTRIBUTES: List<String> = unmodifiableList(listOf(
|
||||||
|
CORDAPP_CONTRACT_NAME,
|
||||||
|
CORDAPP_CONTRACT_LICENCE,
|
||||||
|
CORDAPP_CONTRACT_VENDOR,
|
||||||
|
CORDAPP_CONTRACT_VERSION,
|
||||||
|
CORDAPP_WORKFLOW_NAME,
|
||||||
|
CORDAPP_WORKFLOW_LICENCE,
|
||||||
|
CORDAPP_WORKFLOW_VENDOR,
|
||||||
|
CORDAPP_WORKFLOW_VERSION
|
||||||
|
))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the DJVM's sources to the node's configuration file.
|
* Add the DJVM's sources to the node's configuration file.
|
||||||
* These will all be ignored unless devMode is also true.
|
* These will all be ignored unless devMode is also true.
|
||||||
@ -923,12 +948,11 @@ class DriverDSLImpl(
|
|||||||
|
|
||||||
// The following dependencies are excluded from the classpath of the created JVM,
|
// The following dependencies are excluded from the classpath of the created JVM,
|
||||||
// so that the environment resembles a real one as close as possible.
|
// so that the environment resembles a real one as close as possible.
|
||||||
// These are either classes that will be added as attachments to the node (i.e. samples, finance, opengamma etc.)
|
val cp = ProcessUtilities.defaultClassPath.filter { cpEntry ->
|
||||||
// or irrelevant testing libraries (test, corda-mock etc.).
|
val cpPathEntry = Paths.get(cpEntry)
|
||||||
// TODO: There is pending work to fix this issue without custom blacklisting. See: https://r3-cev.atlassian.net/browse/CORDA-2164.
|
cpPathEntry.isRegularFile()
|
||||||
val exclude = listOf("samples", "finance", "integrationTest", "test", "corda-mock", "com.opengamma.strata")
|
&& !isTestArtifact(cpPathEntry.fileName.toString())
|
||||||
val cp = ProcessUtilities.defaultClassPath.filterNot { cpEntry ->
|
&& !cpPathEntry.isCorDapp
|
||||||
exclude.any { token -> cpEntry.contains("${File.separatorChar}$token") } || cpEntry.endsWith("-tests.jar")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ProcessUtilities.startJavaProcess(
|
return ProcessUtilities.startJavaProcess(
|
||||||
@ -944,6 +968,27 @@ class DriverDSLImpl(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Obvious test artifacts. This is NOT intended to be an exhaustive list!
|
||||||
|
// It is only intended to remove those FEW jars which BLATANTLY do not
|
||||||
|
// belong inside a Corda Node.
|
||||||
|
private fun isTestArtifact(name: String): Boolean {
|
||||||
|
return name.endsWith("-tests.jar")
|
||||||
|
|| name.endsWith("-test.jar")
|
||||||
|
|| name.startsWith("corda-mock")
|
||||||
|
|| name.startsWith("junit")
|
||||||
|
|| name.startsWith("testng")
|
||||||
|
|| name.startsWith("mockito")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identify CorDapp JARs by their attributes in MANIFEST.MF.
|
||||||
|
private val Path.isCorDapp: Boolean get() {
|
||||||
|
return JarInputStream(Files.newInputStream(this).buffered()).use { jar ->
|
||||||
|
val manifest = jar.manifest ?: return false
|
||||||
|
CORDAPP_MANIFEST_ATTRIBUTES.any { manifest[it] != null }
|
||||||
|
|| (manifest[TARGET_PLATFORM_VERSION] != null && manifest[MIN_PLATFORM_VERSION] != null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun startWebserver(handle: NodeHandleInternal, debugPort: Int?, maximumHeapSize: String): Process {
|
private fun startWebserver(handle: NodeHandleInternal, debugPort: Int?, maximumHeapSize: String): Process {
|
||||||
val className = "net.corda.webserver.WebServer"
|
val className = "net.corda.webserver.WebServer"
|
||||||
writeConfig(handle.baseDirectory, "web-server.conf", handle.toWebServerConfig())
|
writeConfig(handle.baseDirectory, "web-server.conf", handle.toWebServerConfig())
|
||||||
|
Loading…
Reference in New Issue
Block a user