Merge branch 'release/os/4.12' into merge-release/os/4.11-release/os/4.12-2024-08-13-341

# Conflicts:
#	docker/src/docker/DockerfileAL
#	node/src/integration-test/kotlin/net/corda/node/services/identity/NotaryCertificateRotationTest.kt
This commit is contained in:
rick.parker
2024-08-13 17:26:41 +01:00
759 changed files with 14374 additions and 12301 deletions

View File

@ -1,14 +1,15 @@
apply plugin: 'kotlin'
apply plugin: 'kotlin-jpa'
apply plugin: 'org.jetbrains.kotlin.jvm'
apply plugin: 'org.jetbrains.kotlin.plugin.jpa'
apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'net.corda.plugins.api-scanner'
apply plugin: 'com.jfrog.artifactory'
apply plugin: 'corda.common-publishing'
description 'Corda Node Driver module'
//noinspection GroovyAssignabilityCheck
configurations {
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime
integrationTestImplementation.extendsFrom testImplementation
integrationTestRuntime.extendsFrom testRuntimeOnly
}
sourceSets {
@ -25,14 +26,33 @@ sourceSets {
}
dependencies {
compile project(':test-utils')
implementation project(':core')
implementation project(':node')
implementation project(':node-api')
implementation project(':serialization')
implementation project(':client:rpc')
implementation project(':client:mock')
implementation project(':common-configuration-parsing')
implementation project(':common-validation')
implementation project(':core-test-utils')
implementation project(':test-common')
implementation project(':test-utils')
implementation project(':tools:cliutils')
compile group: 'org.apache.sshd', name: 'sshd-common', version: '2.9.2'
implementation group: 'org.apache.sshd', name: 'sshd-common', version: '2.12.1'
implementation "javax.persistence:javax.persistence-api:2.2"
// Integration test helpers
testCompile "org.assertj:assertj-core:$assertj_version"
testImplementation "org.assertj:assertj-core:$assertj_version"
integrationTestImplementation project(":client:jackson")
integrationTestImplementation "junit:junit:$junit_version"
integrationTestImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
integrationTestImplementation "info.picocli:picocli:$picocli_version"
integrationTestImplementation "com.google.guava:guava:$guava_version"
integrationTestImplementation 'com.googlecode.json-simple:json-simple:1.1.1'
integrationTestImplementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
integrationTestImplementation 'org.hamcrest:hamcrest-library:2.1'
integrationTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}"
integrationTestRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junit_jupiter_version}"
@ -40,24 +60,60 @@ dependencies {
// Jetty dependencies for NetworkMapClient test.
// Web stuff: for HTTP[S] servlets
compile "org.eclipse.jetty:jetty-servlet:${jetty_version}"
compile "org.eclipse.jetty:jetty-webapp:${jetty_version}"
compile "javax.servlet:javax.servlet-api:${servlet_version}"
implementation "org.eclipse.jetty.ee10:jetty-ee10-servlet:${jetty_version}"
implementation "org.eclipse.jetty.ee10:jetty-ee10-webapp:${jetty_version}"
implementation "javax.servlet:javax.servlet-api:${servlet_version}"
implementation "org.gradle:gradle-tooling-api:7.1"
compile "org.gradle:gradle-tooling-api:${gradle.gradleVersion}"
// Jersey for JAX-RS implementation for use in Jetty
compile "org.glassfish.jersey.core:jersey-server:${jersey_version}"
compile "org.glassfish.jersey.containers:jersey-container-servlet-core:${jersey_version}"
compile "org.glassfish.jersey.containers:jersey-container-jetty-http:${jersey_version}"
implementation "org.glassfish.jersey.core:jersey-server:${jersey_version}"
implementation "org.glassfish.jersey.containers:jersey-container-servlet-core:${jersey_version}"
implementation "org.glassfish.jersey.containers:jersey-container-jetty-http:${jersey_version}"
implementation "org.glassfish.jersey.inject:jersey-hk2:$jersey_version"
implementation "io.reactivex:rxjava:$rxjava_version"
implementation("org.apache.activemq:artemis-core-client:${artemis_version}") {
exclude group: 'org.jgroups', module: 'jgroups'
}
// Bouncy castle support needed for X509 certificate manipulation
implementation "org.bouncycastle:bcprov-lts8on:${bouncycastle_version}"
implementation "org.bouncycastle:bcpkix-lts8on:${bouncycastle_version}"
implementation "org.bouncycastle:bcutil-lts8on:${bouncycastle_version}"
implementation "com.google.code.findbugs:jsr305:$jsr305_version"
implementation "com.google.jimfs:jimfs:1.1"
implementation group: "com.typesafe", name: "config", version: typesafe_config_version
implementation "io.github.classgraph:classgraph:$class_graph_version"
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "org.mockito.kotlin:mockito-kotlin:$mockito_kotlin_version"
implementation "com.esotericsoftware:kryo:$kryo_version"
implementation "io.dropwizard.metrics:metrics-jmx:$metrics_version"
implementation "org.apache.commons:commons-lang3:$commons_lang3_version"
implementation "org.assertj:assertj-core:${assertj_version}"
implementation "org.apache.logging.log4j:log4j-core:$log4j_version"
implementation "junit:junit:$junit_version"
implementation("org.apache.activemq:artemis-server:${artemis_version}") {
exclude group: 'org.apache.commons', module: 'commons-dbcp2'
exclude group: 'org.jgroups', module: 'jgroups'
}
implementation "co.paralleluniverse:quasar-core:$quasar_version"
}
compileJava {
doFirst {
if (JavaVersion.current() == JavaVersion.VERSION_11)
options.compilerArgs = [
'--add-exports', 'java.base/sun.nio.ch=ALL-UNNAMED'
]
options.compilerArgs = [
'--add-modules', 'jdk.incubator.foreign'
]
}
}
processResources {
from(project(":node:capsule").files("src/main/resources/node-jvm-args.txt")) {
into("net/corda/testing/node/internal")
}
}
@ -75,20 +131,10 @@ jar {
}
}
tasks.named('javadocJar', Jar) {
from 'README.md'
include 'README.md'
}
tasks.named('javadoc', Javadoc) {
enabled = false
}
publish {
name jar.baseName
}
scanApi {
//Constructors that are synthesized by Kotlin unexpectedly
excludeMethods = [
@ -99,4 +145,13 @@ scanApi {
"<init>(Lnet/corda/testing/node/InMemoryMessagingNetwork\$PeerHandle;Lnet/corda/node/services/messaging/Message;Lnet/corda/core/messaging/MessageRecipients;Lkotlin/jvm/internal/DefaultConstructorMarker;)V"
]
]
}
}
publishing {
publications {
maven(MavenPublication) {
artifactId jar.baseName
from components.java
}
}
}

View File

@ -6,10 +6,6 @@ import net.corda.core.internal.CertRole
import net.corda.core.internal.concurrent.fork
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.concurrent.transpose
import net.corda.core.internal.div
import net.corda.core.internal.isRegularFile
import net.corda.core.internal.list
import net.corda.core.internal.readLines
import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.NodeStartup
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
@ -26,12 +22,16 @@ import org.assertj.core.api.Assertions.assertThatCode
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import org.json.simple.JSONObject
import org.junit.Test
import java.util.*
import java.util.LinkedList
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executors
import java.util.concurrent.ForkJoinPool
import java.util.concurrent.ScheduledExecutorService
import kotlin.streams.toList
import kotlin.io.path.div
import kotlin.io.path.isRegularFile
import kotlin.io.path.name
import kotlin.io.path.useDirectoryEntries
import kotlin.io.path.useLines
import kotlin.test.assertEquals
class DriverTests {
@ -99,8 +99,8 @@ class DriverTests {
systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString())
)) {
val baseDirectory = startNode(providedName = DUMMY_BANK_A_NAME).getOrThrow().baseDirectory
val logFile = (baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME).list { it.filter { a -> a.isRegularFile() && a.fileName.toString().startsWith("node") }.findFirst().get() }
val debugLinesPresent = logFile.readLines { lines -> lines.anyMatch { line -> line.startsWith("[DEBUG]") } }
val logFile = (baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME).useDirectoryEntries { it.single { a -> a.isRegularFile() && a.name.startsWith("node") } }
val debugLinesPresent = logFile.useLines { lines -> lines.any { line -> line.startsWith("[DEBUG]") } }
assertThat(debugLinesPresent).isTrue()
}
}
@ -185,5 +185,5 @@ class DriverTests {
testFuture.getOrThrow()
}
private fun DriverDSL.newNode(name: CordaX500Name) = { startNode(NodeParameters(providedName = name)) }
}
private fun DriverDSL.newNode(name: CordaX500Name): () -> CordaFuture<NodeHandle> = { startNode(NodeParameters(providedName = name)) }
}

View File

@ -3,9 +3,13 @@ package net.corda.testing.node
import co.paralleluniverse.fibers.Suspendable
import net.corda.client.jackson.JacksonSupport
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.flows.*
import net.corda.core.internal.div
import net.corda.core.internal.list
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.FlowStackSnapshot
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.flows.StateMachineRunId
import net.corda.core.internal.read
import net.corda.core.messaging.startFlow
import net.corda.core.serialization.CordaSerializable
@ -16,6 +20,8 @@ import org.junit.Ignore
import org.junit.Test
import java.nio.file.Path
import java.time.LocalDate
import kotlin.io.path.div
import kotlin.io.path.useDirectoryEntries
import kotlin.test.assertEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue
@ -29,11 +35,11 @@ data class StackSnapshotFrame(val method: String, val clazz: String, val dataTyp
* an empty list the frame is considered to be full.
*/
fun convertToStackSnapshotFrames(snapshot: FlowStackSnapshot): List<StackSnapshotFrame> {
return snapshot.stackFrames.map {
val dataTypes = it.stackObjects.map {
return snapshot.stackFrames.map { frame ->
val dataTypes = frame.stackObjects.map {
if (it == null) null else it::class.qualifiedName
}
val stackTraceElement = it.stackTraceElement
val stackTraceElement = frame.stackTraceElement
StackSnapshotFrame(stackTraceElement.methodName, stackTraceElement.className, dataTypes)
}
}
@ -48,7 +54,7 @@ fun convertToStackSnapshotFrames(snapshot: FlowStackSnapshot): List<StackSnapsho
*/
@StartableByRPC
class SideEffectFlow : FlowLogic<List<StackSnapshotFrame>>() {
var sideEffectField = ""
private var sideEffectField = ""
@Suspendable
override fun call(): List<StackSnapshotFrame> {
@ -155,7 +161,7 @@ class PersistingSideEffectFlow : FlowLogic<StateMachineRunId>() {
* Similar to [PersistingSideEffectFlow] but aims to produce multiple snapshot files.
*/
@StartableByRPC
class MultiplePersistingSideEffectFlow(val persistCallCount: Int) : FlowLogic<StateMachineRunId>() {
class MultiplePersistingSideEffectFlow(private val persistCallCount: Int) : FlowLogic<StateMachineRunId>() {
@Suspendable
override fun call(): StateMachineRunId {
@ -212,7 +218,7 @@ private fun flowSnapshotDir(baseDir: Path, flowId: StateMachineRunId): Path {
}
fun countFilesInDir(baseDir: Path, flowId: StateMachineRunId): Int {
return flowSnapshotDir(baseDir, flowId).list { it.count().toInt() }
return flowSnapshotDir(baseDir, flowId).useDirectoryEntries { it.count() }
}
fun assertFrame(expectedMethod: String, expectedEmpty: Boolean, frame: StackSnapshotFrame) {
@ -311,9 +317,9 @@ class FlowStackSnapshotTest {
val snapshotFromFile = readFlowStackSnapshotFromDir(a.baseDirectory, flowId)
var inCallCount = 0
var inPersistCount = 0
snapshotFromFile.stackFrames.forEach {
val trace = it.stackTraceElement
it.stackObjects.forEach {
snapshotFromFile.stackFrames.forEach { frame ->
val trace = frame.stackTraceElement
frame.stackObjects.forEach {
when (it) {
Constants.IN_CALL_VALUE -> {
assertEquals(PersistingSideEffectFlow::call.name, trace.methodName)

View File

@ -1,10 +1,14 @@
package net.corda.testing.node
import net.corda.core.internal.div
import net.corda.core.internal.deleteRecursively
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import net.corda.testing.node.internal.ProcessUtilities.startJavaProcess
import net.corda.testing.node.internal.nodeJvmArgs
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.io.path.Path
import kotlin.io.path.createDirectories
import kotlin.io.path.div
class MockNetworkIntegrationTests {
companion object {
@ -21,6 +25,17 @@ class MockNetworkIntegrationTests {
@Test(timeout=300_000)
fun `does not leak non-daemon threads`() {
val quasar = projectRootDir / "lib" / "quasar.jar"
assertEquals(0, startJavaProcess<MockNetworkIntegrationTests>(emptyList(), extraJvmArguments = listOf("-javaagent:$quasar")).waitFor())
val quasarOptions = "m"
val workingDirectory = Path("build", "MockNetworkIntegrationTests").apply {
deleteRecursively()
createDirectories()
}
val process = startJavaProcess<MockNetworkIntegrationTests>(
emptyList(),
workingDirectory = workingDirectory,
extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + nodeJvmArgs
)
assertThat(process.waitFor()).isZero()
}
}

View File

@ -1,9 +1,7 @@
package net.corda.testing.node.internal
import net.corda.testing.internal.IS_OPENJ9
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.matchesPattern
import org.junit.Assume
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@ -11,7 +9,6 @@ import java.io.BufferedReader
import java.io.InputStreamReader
import java.util.stream.Collectors
@RunWith(value = Parameterized::class)
class CordaCliWrapperErrorHandlingTests(val arguments: List<String>, val outputRegexPattern: String) {
@ -33,22 +30,19 @@ class CordaCliWrapperErrorHandlingTests(val arguments: List<String>, val outputR
@Test(timeout=300_000)
fun `Run CordaCliWrapper sample app with arguments and check error output matches regExp`() {
// For openj9 the process error output appears sometimes to be garbled.
Assume.assumeTrue(!IS_OPENJ9)
val process = ProcessUtilities.startJavaProcess(
className = className,
arguments = arguments,
inheritIO = false)
val process = ProcessUtilities.startJavaProcess(className = className, arguments = arguments)
process.waitFor()
val processErrorOutput = BufferedReader(
InputStreamReader(process.errorStream))
.lines()
.filter { !it.startsWith("Warning: Nashorn") }
.filter { it.contains("Exception") ||
it.contains("at ") ||
it.contains("exception") }
.collect(Collectors.joining("\n"))
.toString()
assertThat(processErrorOutput, matchesPattern(outputRegexPattern))
}
}
}

View File

@ -1,10 +1,13 @@
package net.corda.testing.node.internal
import net.corda.core.internal.div
import net.corda.core.internal.deleteRecursively
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import net.corda.testing.node.internal.ProcessUtilities.startJavaProcess
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.io.path.Path
import kotlin.io.path.createDirectories
import kotlin.io.path.div
class InternalMockNetworkIntegrationTests {
companion object {
@ -21,6 +24,17 @@ class InternalMockNetworkIntegrationTests {
@Test(timeout=300_000)
fun `does not leak non-daemon threads`() {
val quasar = projectRootDir / "lib" / "quasar.jar"
assertEquals(0, startJavaProcess<InternalMockNetworkIntegrationTests>(emptyList(), extraJvmArguments = listOf("-javaagent:$quasar")).waitFor())
val quasarOptions = "m"
val workingDirectory = Path("build", "InternalMockNetworkIntegrationTests").apply {
deleteRecursively()
createDirectories()
}
val process = startJavaProcess<InternalMockNetworkIntegrationTests>(
emptyList(),
workingDirectory = workingDirectory,
extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + nodeJvmArgs
)
assertThat(process.waitFor()).isZero()
}
}

View File

@ -1,12 +1,12 @@
package net.corda.testing.node.internal
import net.corda.core.internal.readText
import net.corda.core.internal.writeText
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.nio.file.Paths
import java.util.concurrent.TimeUnit
import kotlin.io.path.readText
import kotlin.io.path.writeText
import kotlin.test.assertEquals
import kotlin.test.assertTrue

View File

@ -1,97 +1,108 @@
package net.corda.testing.driver;
import sun.misc.Unsafe;
import sun.nio.ch.DirectBuffer;
import jdk.incubator.foreign.MemoryHandles;
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.ResourceScope;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Field;
import java.io.UncheckedIOException;
import java.lang.invoke.VarHandle;
import java.net.ServerSocket;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* JDK11 upgrade: rewritten in Java to gain access to private internal JDK classes via module directives (not available to Kotlin compiler):
* import sun.misc.Unsafe;
* import sun.nio.ch.DirectBuffer;
*/
import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.WRITE;
// This was originally (re)written in Java to access internal JDK APIs. Since it's no longer doing that, this can be converted back to Kotlin.
public class SharedMemoryIncremental extends PortAllocation {
private static final int DEFAULT_START_PORT = 10_000;
private static final int FIRST_EPHEMERAL_PORT = 30_000;
static private final int DEFAULT_START_PORT = 10_000;
static private final int FIRST_EPHEMERAL_PORT = 30_000;
private final int startPort;
private final int endPort;
private int startPort;
private int endPort;
private MappedByteBuffer mb;
private Long startingAddress;
private File file = new File(System.getProperty("user.home"), "corda-" + startPort + "-to-" + endPort + "-port-allocator.bin");
private RandomAccessFile backingFile;
{
try {
backingFile = new RandomAccessFile(file, "rw");
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
private final MemorySegment memorySegment;
private final VarHandle intHandle;
private final MappedByteBuffer unsafeBuffer;
private SharedMemoryIncremental(int startPort, int endPort) {
this.startPort = startPort;
this.endPort = endPort;
Path file = Path.of(System.getProperty("user.home"), "corda-" + startPort + "-to-" + endPort + "-port-allocator.bin");
try {
mb = backingFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, 16);
startingAddress = ((DirectBuffer) mb).address();
try {
Files.createFile(file);
} catch (FileAlreadyExistsException ignored) {}
if (isFfmAvailable()) {
memorySegment = MemorySegment.mapFile(file, 0, Integer.SIZE, MapMode.READ_WRITE, ResourceScope.globalScope());
intHandle = MemoryHandles.varHandle(int.class, ByteOrder.nativeOrder());
unsafeBuffer = null;
} else {
LoggerFactory.getLogger(getClass()).warn("Using unsafe port allocator which may lead to the same port being allocated " +
"twice. Consider adding --add-modules=jdk.incubator.foreign to the test JVM.");
memorySegment = null;
intHandle = null;
unsafeBuffer = FileChannel.open(file, READ, WRITE).map(MapMode.READ_WRITE, 0, Integer.SIZE);
}
} catch (IOException e) {
e.printStackTrace();
throw new UncheckedIOException(e);
}
}
private static boolean isFfmAvailable() {
try {
Class.forName("jdk.incubator.foreign.MemorySegment");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
public static SharedMemoryIncremental INSTANCE = new SharedMemoryIncremental(DEFAULT_START_PORT, FIRST_EPHEMERAL_PORT);
static private Unsafe UNSAFE = getUnsafe();
static private Unsafe getUnsafe() {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
@Override
public int nextPort() {
long oldValue;
long newValue;
boolean loopSuccess;
do {
oldValue = UNSAFE.getLongVolatile(null, startingAddress);
while (true) {
int oldValue;
if (intHandle != null) {
oldValue = (int) intHandle.getVolatile(memorySegment, 0L);
} else {
oldValue = unsafeBuffer.getInt(0);
}
int newValue;
if (oldValue + 1 >= endPort || oldValue < startPort) {
newValue = startPort;
} else {
newValue = (oldValue + 1);
}
boolean reserveSuccess = UNSAFE.compareAndSwapLong(null, startingAddress, oldValue, newValue);
loopSuccess = reserveSuccess && isLocalPortAvailable(newValue);
} while (!loopSuccess);
return (int) newValue;
if (intHandle != null) {
if (!intHandle.compareAndSet(memorySegment, 0L, oldValue, newValue)) {
continue;
}
} else {
unsafeBuffer.putInt(0, newValue);
}
if (isLocalPortAvailable(newValue)) {
return newValue;
}
}
}
private boolean isLocalPortAvailable(Long portToTest) {
try (ServerSocket serverSocket = new ServerSocket(Math.toIntExact(portToTest))) {
private boolean isLocalPortAvailable(int portToTest) {
try (ServerSocket ignored = new ServerSocket(portToTest)) {
return true;
} catch (IOException e) {
// Don't catch anything other than IOException here in case we
// accidentally create an infinite loop. For example, installing
// a SecurityManager could throw AccessControlException.
return false;
}
return true;
}
}

View File

@ -6,7 +6,6 @@ import net.corda.core.DoNotImplement
import net.corda.core.concurrent.CordaFuture
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.Party
import net.corda.core.internal.div
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.NetworkParameters
@ -31,6 +30,7 @@ import java.nio.file.Path
import java.nio.file.Paths
import java.time.Duration
import java.util.concurrent.atomic.AtomicInteger
import kotlin.io.path.div
/**
* Object ecapsulating a notary started automatically by the driver.
@ -98,7 +98,6 @@ interface InProcess : NodeHandle {
/**
* Starts an already constructed flow. Note that you must be on the server thread to call this method.
* @param context indicates who started the flow, see: [InvocationContext].
*/
fun <T> startFlow(logic: FlowLogic<T>): CordaFuture<T> = internalServices.startFlow(logic, internalServices.newContext())
.getOrThrow().resultFuture
@ -628,7 +627,7 @@ data class DriverParameters(
waitForAllNodesToFinish: Boolean,
notarySpecs: List<NotarySpec>,
extraCordappPackagesToScan: List<String>,
@Suppress("DEPRECATION") jmxPolicy: JmxPolicy,
jmxPolicy: JmxPolicy,
networkParameters: NetworkParameters,
notaryCustomOverrides: Map<String, Any?>,
inMemoryDB: Boolean,

View File

@ -25,7 +25,7 @@ import net.corda.testing.node.User
* log level argument.
* @property rpcAddress optional override for RPC address on which node will be accepting RPC connections from the clients. Port provided must be vacant.
*/
@Suppress("unused")
@Suppress("unused", "TooManyFunctions")
data class NodeParameters(
val providedName: CordaX500Name? = null,
val rpcUsers: List<User> = emptyList(),
@ -37,7 +37,8 @@ data class NodeParameters(
val flowOverrides: Map<out Class<out FlowLogic<*>>, Class<out FlowLogic<*>>> = emptyMap(),
val logLevelOverride: String? = null,
val rpcAddress: NetworkHostAndPort? = null,
val systemProperties: Map<String, String> = emptyMap()
val systemProperties: Map<String, String> = emptyMap(),
val legacyContracts: Collection<TestCordapp> = emptySet()
) {
/**
* Create a new node parameters object with default values. Each parameter can be specified with its wither method which returns a copy
@ -54,6 +55,9 @@ data class NodeParameters(
fun withAdditionalCordapps(additionalCordapps: Set<TestCordapp>): NodeParameters = copy(additionalCordapps = additionalCordapps)
fun withFlowOverrides(flowOverrides: Map<Class<out FlowLogic<*>>, Class<out FlowLogic<*>>>): NodeParameters = copy(flowOverrides = flowOverrides)
fun withLogLevelOverride(logLevelOverride: String?): NodeParameters = copy(logLevelOverride = logLevelOverride)
fun withRpcAddress(rpcAddress: NetworkHostAndPort?): NodeParameters = copy(rpcAddress = rpcAddress)
fun withSystemProperties(systemProperties: Map<String, String>): NodeParameters = copy(systemProperties = systemProperties)
fun withLegacyContracts(legacyContracts: Collection<TestCordapp>): NodeParameters = copy(legacyContracts = legacyContracts)
constructor(
providedName: CordaX500Name?,
@ -221,4 +225,58 @@ data class NodeParameters(
logLevelOverride = logLevelOverride,
rpcAddress = rpcAddress,
systemProperties = systemProperties)
constructor(
providedName: CordaX500Name?,
rpcUsers: List<User>,
verifierType: VerifierType,
customOverrides: Map<String, Any?>,
startInSameProcess: Boolean?,
maximumHeapSize: String,
additionalCordapps: Collection<TestCordapp> = emptySet(),
flowOverrides: Map<out Class<out FlowLogic<*>>, Class<out FlowLogic<*>>>,
logLevelOverride: String? = null,
rpcAddress: NetworkHostAndPort? = null,
systemProperties: Map<String, String> = emptyMap()
) : this(
providedName,
rpcUsers,
verifierType,
customOverrides,
startInSameProcess,
maximumHeapSize,
additionalCordapps,
flowOverrides,
logLevelOverride,
rpcAddress,
systemProperties,
legacyContracts = emptySet())
@Suppress("LongParameterList")
fun copy(
providedName: CordaX500Name?,
rpcUsers: List<User>,
verifierType: VerifierType,
customOverrides: Map<String, Any?>,
startInSameProcess: Boolean?,
maximumHeapSize: String,
additionalCordapps: Collection<TestCordapp> = emptySet(),
flowOverrides: Map<out Class<out FlowLogic<*>>, Class<out FlowLogic<*>>>,
logLevelOverride: String? = null,
rpcAddress: NetworkHostAndPort? = null,
systemProperties: Map<String, String> = emptyMap()
) = this.copy(
providedName = providedName,
rpcUsers = rpcUsers,
verifierType = verifierType,
customOverrides = customOverrides,
startInSameProcess = startInSameProcess,
maximumHeapSize = maximumHeapSize,
additionalCordapps = additionalCordapps,
flowOverrides = flowOverrides,
logLevelOverride = logLevelOverride,
rpcAddress = rpcAddress,
systemProperties = systemProperties,
legacyContracts = legacyContracts)
}

View File

@ -1,5 +1,6 @@
package net.corda.testing.driver.internal
import jakarta.validation.constraints.NotNull
import net.corda.core.flows.FlowLogic
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.NodeInfo
@ -14,7 +15,6 @@ import net.corda.testing.driver.OutOfProcess
import net.corda.testing.node.User
import rx.Observable
import java.nio.file.Path
import javax.validation.constraints.NotNull
interface NodeHandleInternal : NodeHandle {
val configuration: NodeConfiguration

View File

@ -1,21 +1,31 @@
package net.corda.testing.node
import com.google.common.collect.MutableClassToInstanceMap
import net.corda.core.CordaInternal
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractClassName
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionState
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.AbstractAttachment
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.cordapp.CordappProviderInternal
import net.corda.core.internal.getRequiredTransaction
import net.corda.core.internal.mapToSet
import net.corda.core.internal.requireSupportedHashType
import net.corda.core.internal.telemetry.TelemetryComponent
import net.corda.core.internal.telemetry.TelemetryServiceImpl
import net.corda.core.internal.verification.ExternalVerifierHandle
import net.corda.core.internal.verification.VerifyingServiceHub
import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.FlowHandle
import net.corda.core.messaging.FlowProgressHandle
@ -34,33 +44,34 @@ import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.NetworkParametersService
import net.corda.core.node.services.ServiceLifecycleObserver
import net.corda.core.node.services.TransactionStorage
import net.corda.core.node.services.TransactionVerifierService
import net.corda.core.node.services.VaultService
import net.corda.core.node.services.diagnostics.DiagnosticsService
import net.corda.core.node.services.vault.CordaTransactionSupport
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.internal.AttachmentsClassLoaderCacheImpl
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor
import net.corda.coretesting.internal.DEV_ROOT_CA
import net.corda.node.VersionInfo
import net.corda.node.internal.ServicesForResolutionImpl
import net.corda.node.internal.NodeServicesForResolution
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.node.services.api.SchemaService
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage
import net.corda.node.services.api.VaultServiceInternal
import net.corda.node.services.api.WritableTransactionStorage
import net.corda.node.services.attachments.NodeAttachmentTrustCalculator
import net.corda.node.services.diagnostics.NodeDiagnosticsService
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.persistence.PublicKeyToOwningIdentityCacheImpl
import net.corda.node.services.persistence.toInternal
import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.services.vault.NodeVaultService
import net.corda.nodeapi.internal.cordapp.CordappLoader
import net.corda.nodeapi.internal.cordapp.cordappSchemas
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.contextTransaction
@ -69,7 +80,6 @@ import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.MockCordappProvider
import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.testing.internal.configureDatabase
import net.corda.testing.node.internal.DriverDSLImpl
import net.corda.testing.node.internal.MockCryptoService
import net.corda.testing.node.internal.MockKeyManagementService
import net.corda.testing.node.internal.MockNetworkParametersStorage
@ -78,6 +88,7 @@ import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.getCallerPackage
import net.corda.testing.services.MockAttachmentStorage
import java.io.ByteArrayOutputStream
import java.nio.file.FileAlreadyExistsException
import java.nio.file.Paths
import java.security.KeyPair
import java.sql.Connection
@ -116,10 +127,9 @@ open class MockServices private constructor(
*arrayOf(initialIdentity.keyPair) + moreKeys
)
) : 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(cordappsForPackages(packages).mapToSet { it.jarFile }, versionInfo = versionInfo)
}
/**
@ -135,8 +145,8 @@ open class MockServices private constructor(
val dbPath = dbDir.resolve("persistence")
try {
DatabaseSnapshot.copyDatabaseSnapshot(dbDir)
} catch (ex: java.nio.file.FileAlreadyExistsException) {
DriverDSLImpl.log.warn("Database already exists on disk, not attempting to pre-migrate database.")
} catch (e: FileAlreadyExistsException) {
loggerFor<MockServices>().warn("Database already exists on disk, not attempting to pre-migrate database.")
}
val props = Properties()
props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource")
@ -295,22 +305,19 @@ open class MockServices private constructor(
// Because Kotlin is dumb and makes not publicly visible objects public, thus changing the public API.
private val mockStateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage()
private val dummyAttachment by lazy {
val inputStream = ByteArrayOutputStream().apply {
ZipOutputStream(this).use {
with(it) {
putNextEntry(ZipEntry(JarFile.MANIFEST_NAME))
}
}
}.toByteArray().inputStream()
val attachment = object : Attachment {
override val id get() = throw UnsupportedOperationException()
override fun open() = inputStream
override val signerKeys get() = throw UnsupportedOperationException()
override val signers: List<Party> get() = throw UnsupportedOperationException()
override val size: Int = 512
private val dummyAttachment: Attachment by lazy {
object : AbstractAttachment(
{
val baos = ByteArrayOutputStream()
ZipOutputStream(baos).use { zip ->
zip.putNextEntry(ZipEntry(JarFile.MANIFEST_NAME))
}
baos.toByteArray()
},
null
) {
override val id: SecureHash by lazy(attachmentData::sha256)
}
attachment
}
}
@ -482,27 +489,23 @@ open class MockServices private constructor(
get() {
return NodeInfo(listOf(NetworkHostAndPort("mock.node.services", 10000)), listOf(initialIdentity.identity), 1, serial = 1L)
}
private val mockCordappProvider: MockCordappProvider = MockCordappProvider(cordappLoader, attachments).also {
private val mockCordappProvider: MockCordappProvider = MockCordappProvider(cordappLoader, attachments.toInternal()).also {
it.start()
}
override val transactionVerifierService: TransactionVerifierService
get() = InMemoryTransactionVerifierService(
numberOfWorkers = 2,
cordappProvider = mockCordappProvider,
attachments = attachments
)
override val cordappProvider: CordappProvider get() = mockCordappProvider
override var networkParametersService: NetworkParametersService = MockNetworkParametersStorage(initialNetworkParameters)
override val diagnosticsService: DiagnosticsService = NodeDiagnosticsService()
protected val servicesForResolution: ServicesForResolution
get() = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersService, validatedTransactions)
// This is kept here for backwards compatibility, otherwise this has no extra utility.
protected val servicesForResolution: ServicesForResolution get() = verifyingView
private val verifyingView: VerifyingServiceHub by lazy { VerifyingView(this) }
internal fun makeVaultService(schemaService: SchemaService, database: CordaPersistence, cordappLoader: CordappLoader): VaultServiceInternal {
return NodeVaultService(
clock,
keyManagementService,
servicesForResolution as NodeServicesForResolution,
verifyingView,
database,
schemaService,
cordappLoader.appClassLoader
@ -511,9 +514,9 @@ open class MockServices private constructor(
// This needs to be internal as MutableClassToInstanceMap is a guava type and shouldn't be part of our public API
/** A map of available [CordaService] implementations */
internal val cordappServices: MutableClassToInstanceMap<SerializeAsToken> = MutableClassToInstanceMap.create<SerializeAsToken>()
internal val cordappServices: MutableClassToInstanceMap<SerializeAsToken> = MutableClassToInstanceMap.create()
internal val cordappTelemetryComponents: MutableClassToInstanceMap<TelemetryComponent> = MutableClassToInstanceMap.create<TelemetryComponent>()
private val cordappTelemetryComponents: MutableClassToInstanceMap<TelemetryComponent> = MutableClassToInstanceMap.create()
override fun <T : SerializeAsToken> cordaService(type: Class<T>): T {
require(type.isAnnotationPresent(CordaService::class.java)) { "${type.name} is not a Corda service" }
@ -543,19 +546,46 @@ open class MockServices private constructor(
mockCordappProvider.addMockCordapp(contractClassName, attachments)
}
override fun loadState(stateRef: StateRef) = servicesForResolution.loadState(stateRef)
override fun loadStates(stateRefs: Set<StateRef>) = servicesForResolution.loadStates(stateRefs)
override fun loadState(stateRef: StateRef): TransactionState<ContractState> {
return getRequiredTransaction(stateRef.txhash).resolveBaseTransaction(this).outputs[stateRef.index]
}
override fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> = stateRefs.mapToSet(::toStateAndRef)
/** Returns a dummy Attachment, in context of signature constrains non-downgrade rule this default to contract class version `1`. */
override fun loadContractAttachment(stateRef: StateRef) = dummyAttachment
}
/**
* Function which can be used to create a mock [CordaService] for use within testing, such as an Oracle.
*/
fun <T : SerializeAsToken> createMockCordaService(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T): T {
class MockAppServiceHubImpl<out T : SerializeAsToken>(val serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T) : AppServiceHub, ServiceHub by serviceHub {
val serviceInstance: T = serviceConstructor(this)
/**
* All [ServiceHub]s must also implement [VerifyingServiceHub]. However, since [MockServices] is part of the public API, making it
* extend [VerifyingServiceHub] would leak internal APIs. Instead we have this private view class and have the `toVerifyingServiceHub`
* extension method return it.
*/
private class VerifyingView(private val mockServices: MockServices) : VerifyingServiceHub, ServiceHub by mockServices {
override val attachmentTrustCalculator = NodeAttachmentTrustCalculator(
attachmentStorage = mockServices.attachments.toInternal(),
cacheFactory = TestingNamedCacheFactory()
)
override val attachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory())
override val cordappProvider: CordappProviderInternal get() = mockServices.mockCordappProvider
override fun loadContractAttachment(stateRef: StateRef): Attachment = mockServices.loadContractAttachment(stateRef)
override fun loadState(stateRef: StateRef): TransactionState<*> = mockServices.loadState(stateRef)
override fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> = mockServices.loadStates(stateRefs)
override val externalVerifierHandle: ExternalVerifierHandle
get() = throw UnsupportedOperationException("`Verification of legacy transactions is not supported by MockServices. Use MockNode instead.")
}
@CordaInternal
internal class MockAppServiceHubImpl<out T : SerializeAsToken>(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T) :
AppServiceHub, VerifyingServiceHub by serviceHub.verifyingView {
internal val serviceInstance: T = serviceConstructor(this)
init {
serviceHub.cordappServices.putInstance(serviceInstance.javaClass, serviceInstance)
@ -576,5 +606,11 @@ fun <T : SerializeAsToken> createMockCordaService(serviceHub: MockServices, serv
throw UnsupportedOperationException()
}
}
return MockAppServiceHubImpl(serviceHub, serviceConstructor).serviceInstance
}
/**
* Function which can be used to create a mock [CordaService] for use within testing, such as an Oracle.
*/
fun <T : SerializeAsToken> createMockCordaService(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T): T {
return MockServices.MockAppServiceHubImpl(serviceHub, serviceConstructor).serviceInstance
}

View File

@ -3,7 +3,10 @@ package net.corda.testing.node
import net.corda.core.DoNotImplement
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.NodeParameters
import net.corda.testing.node.internal.TestCordappImpl
import net.corda.testing.node.internal.ScanPackageTestCordapp
import net.corda.testing.node.internal.UriTestCordapp
import java.net.URI
import java.nio.file.Path
/**
* Encapsulates a CorDapp that exists on the current classpath, which can be pulled in for testing. Use [TestCordapp.findCordapp]
@ -25,6 +28,12 @@ abstract class TestCordapp {
/** Returns a copy of this [TestCordapp] but with the specified CorDapp config. */
abstract fun withConfig(config: Map<String, Any>): TestCordapp
/**
* Returns a copy of this [TestCordapp] signed with a development signing key. The same signing key will be used for all signed
* [TestCordapp]s. If the CorDapp jar is already signed, then the new jar created will its signing key replaced by the development key.
*/
abstract fun asSigned(): TestCordapp
companion object {
/**
* Scans the current classpath to find the CorDapp that contains the given package. All the CorDapp's metdata present in its
@ -34,6 +43,14 @@ abstract class TestCordapp {
* @param scanPackage The package name used to find the CorDapp. This does not need to be the root package of the CorDapp.
*/
@JvmStatic
fun findCordapp(scanPackage: String): TestCordapp = TestCordappImpl(scanPackage = scanPackage, config = emptyMap())
fun findCordapp(scanPackage: String): TestCordapp = ScanPackageTestCordapp(scanPackage)
/**
* [URI] location to a CorDapp jar. This may be a path on the local file system or a URL to an external resource.
*
* A [Path] can be converted into a [URI] with [Path.toUri].
*/
@JvmStatic
fun of(uri: URI): TestCordapp = UriTestCordapp(uri)
}
}

View File

@ -1,27 +1,29 @@
package net.corda.testing.node.internal
import io.github.classgraph.ClassGraph
import net.corda.core.internal.*
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.cordapp.set
import net.corda.core.internal.pooledScan
import net.corda.core.node.services.AttachmentFixup
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.testing.core.internal.JarSignatureTestUtils.generateKey
import net.corda.testing.core.internal.JarSignatureTestUtils.containsKey
import net.corda.testing.core.internal.JarSignatureTestUtils.signJar
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.attribute.FileTime
import java.time.Instant
import java.util.*
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import java.util.jar.Attributes
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.jar.Manifest
import java.util.zip.ZipEntry
import kotlin.io.path.createDirectories
import kotlin.io.path.div
import kotlin.io.path.outputStream
/**
* Represents a completely custom CorDapp comprising of resources taken from packages on the existing classpath, even including individual
@ -44,6 +46,8 @@ data class CustomCordapp(
override fun withOnlyJarContents(): CustomCordapp = CustomCordapp(packages = packages, classes = classes, fixups = fixups)
override fun asSigned(): CustomCordapp = signed()
fun signed(keyStorePath: Path? = null, numberOfSignatures: Int = 1, keyAlgorithm: String = "RSA"): CustomCordapp =
copy(signingInfo = SigningInfo(keyStorePath, numberOfSignatures, keyAlgorithm))
@ -109,23 +113,6 @@ data class CustomCordapp(
}
}
private fun signJar(jarFile: Path) {
if (signingInfo != null) {
val keyStorePathToUse = signingInfo.keyStorePath ?: defaultJarSignerDirectory.createDirectories()
for (i in 1 .. signingInfo.numberOfSignatures) {
val alias = "alias$i"
val pwd = "secret!"
if (!keyStorePathToUse.containsKey(alias, pwd)) {
keyStorePathToUse.generateKey(alias, pwd, "O=Test Company Ltd $i,OU=Test,L=London,C=GB", signingInfo.keyAlgorithm)
}
val pk = keyStorePathToUse.signJar(jarFile.toString(), alias, pwd)
logger.debug { "Signed Jar: $jarFile with public key $pk" }
}
} else {
logger.debug { "Unsigned Jar: $jarFile" }
}
}
private fun createTestManifest(name: String, versionId: Int, targetPlatformVersion: Int): Manifest {
val manifest = Manifest()
@ -155,13 +142,12 @@ data class CustomCordapp(
}
}
data class SigningInfo(val keyStorePath: Path?, val numberOfSignatures: Int, val keyAlgorithm: String)
data class SigningInfo(val keyStorePath: Path?, val signatureCount: Int, val algorithm: String)
companion object {
private val logger = contextLogger()
private val epochFileTime = FileTime.from(Instant.EPOCH)
private val cordappsDirectory: Path
private val defaultJarSignerDirectory: Path
private val whitespace = "\\s++".toRegex()
private val cache = ConcurrentHashMap<CustomCordapp, Path>()
@ -169,7 +155,6 @@ data class CustomCordapp(
val buildDir = Paths.get("build").toAbsolutePath()
val timeDirName = getTimestampAsDirectoryName()
cordappsDirectory = buildDir / "generated-custom-cordapps" / timeDirName
defaultJarSignerDirectory = buildDir / "jar-signer" / timeDirName
}
fun getJarFile(cordapp: CustomCordapp): Path {
@ -179,10 +164,12 @@ data class CustomCordapp(
val jarFile = cordappsDirectory.createDirectories() / filename
if (it.fixups.isNotEmpty()) {
it.createFixupJar(jarFile)
} else if(it.packages.isNotEmpty() || it.classes.isNotEmpty() || it.fixups.isNotEmpty()) {
it.packageAsJar(jarFile)
} else if (it.packages.isNotEmpty() || it.classes.isNotEmpty()) {
it.packageAsJar(jarFile)
}
if (it.signingInfo != null) {
TestCordappSigner.signJar(jarFile, it.signingInfo.keyStorePath, it.signingInfo.signatureCount, it.signingInfo.algorithm)
}
it.signJar(jarFile)
logger.debug { "$it packaged into $jarFile" }
jarFile
}

View File

@ -7,7 +7,6 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigRenderOptions
import com.typesafe.config.ConfigValue
import com.typesafe.config.ConfigValueFactory
import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.RPCException
@ -24,6 +23,7 @@ import net.corda.core.internal.concurrent.fork
import net.corda.core.internal.concurrent.map
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.concurrent.transpose
import net.corda.core.internal.copyToDirectory
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_LICENCE
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_NAME
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_VENDOR
@ -35,24 +35,18 @@ import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_VE
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.deleteIfExists
import net.corda.core.internal.div
import net.corda.core.internal.isRegularFile
import net.corda.core.internal.list
import net.corda.core.internal.packageName_
import net.corda.core.internal.readObject
import net.corda.core.internal.readText
import net.corda.core.internal.toPath
import net.corda.core.internal.uncheckedCast
import net.corda.core.internal.writeText
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.millis
import net.corda.core.utilities.toHexString
import net.corda.coretesting.internal.stubs.CertificateStoreStubs
@ -62,6 +56,7 @@ import net.corda.node.internal.DataSourceFactory
import net.corda.node.internal.Node
import net.corda.node.internal.NodeWithInfo
import net.corda.node.internal.clientSslOptionsCompatibleWith
import net.corda.node.internal.cordapp.JarScanningCordappLoader.Companion.LEGACY_CONTRACTS_DIR_NAME
import net.corda.node.services.Permissions
import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.FlowOverride
@ -118,8 +113,9 @@ import java.time.Instant
import java.time.ZoneOffset.UTC
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.util.*
import java.util.Collections.unmodifiableList
import java.util.Random
import java.util.UUID
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
@ -127,10 +123,15 @@ import java.util.concurrent.TimeoutException
import java.util.concurrent.atomic.AtomicInteger
import java.util.jar.JarInputStream
import java.util.jar.Manifest
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.collections.HashSet
import kotlin.concurrent.thread
import kotlin.io.path.createDirectories
import kotlin.io.path.deleteIfExists
import kotlin.io.path.div
import kotlin.io.path.isRegularFile
import kotlin.io.path.name
import kotlin.io.path.readText
import kotlin.io.path.useDirectoryEntries
import kotlin.io.path.writeText
import net.corda.nodeapi.internal.config.User as InternalUser
class DriverDSLImpl(
@ -266,7 +267,7 @@ class DriverDSLImpl(
override fun startNode(parameters: NodeParameters, bytemanPort: Int?): CordaFuture<NodeHandle> {
val p2pAddress = portAllocation.nextHostAndPort()
// TODO: Derive name from the full picked name, don't just wrap the common name
val name = parameters.providedName ?: CordaX500Name("${oneOf(names).organisation}-${p2pAddress.port}", "London", "GB")
val name = parameters.providedName ?: CordaX500Name("${names.random().organisation}-${p2pAddress.port}", "London", "GB")
val config = createConfig(name, parameters, p2pAddress)
if (isH2Database(config) && !inMemoryDB) {
@ -414,11 +415,11 @@ class DriverDSLImpl(
while (process.isAlive) try {
val response = client.newCall(Request.Builder().url(url).build()).execute()
if (response.isSuccessful && (response.body()?.string() == "started")) {
if (response.isSuccessful && (response.body?.string() == "started")) {
return WebserverHandle(handle.webAddress, process)
}
} catch (e: ConnectException) {
log.debug("Retrying webserver info at ${handle.webAddress}")
log.debug { "Retrying webserver info at ${handle.webAddress}" }
}
throw IllegalStateException("Webserver at ${handle.webAddress} has died")
@ -577,9 +578,8 @@ class DriverDSLImpl(
// This causes two node info files to be generated.
startOutOfProcessMiniNode(config, arrayOf("generate-node-info")).map {
// Once done we have to read the signed node info file that's been generated
val nodeInfoFile = config.corda.baseDirectory.list { paths ->
paths.filter { it.fileName.toString().startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }.findFirst()
.get()
val nodeInfoFile = config.corda.baseDirectory.useDirectoryEntries { paths ->
paths.single { it.name.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }
}
val nodeInfo = nodeInfoFile.readObject<SignedNodeInfo>().verified()
Pair(config.withNotaryDefinition(spec.validating), NotaryInfo(nodeInfo.legalIdentities[0], spec.validating))
@ -718,6 +718,11 @@ class DriverDSLImpl(
extraCustomCordapps + (cordappsForAllNodes ?: emptySet())
)
if (parameters.legacyContracts.isNotEmpty()) {
val legacyContractsDir = (baseDirectory / LEGACY_CONTRACTS_DIR_NAME).createDirectories()
parameters.legacyContracts.forEach { (it as TestCordappInternal).jarFile.copyToDirectory(legacyContractsDir) }
}
val nodeFuture = if (parameters.startInSameProcess ?: startNodesInProcess) {
val nodeAndThreadFuture = startInProcessNode(executorService, config, allowHibernateToManageAppSchema)
shutdownManager.registerShutdown(
@ -848,7 +853,7 @@ class DriverDSLImpl(
companion object {
private val RPC_CONNECT_POLL_INTERVAL: Duration = 100.millis
internal val log = contextLogger()
private val log = contextLogger()
// While starting with inProcess mode, we need to have different names to avoid clashes
private val inMemoryCounter = AtomicInteger()
@ -890,18 +895,6 @@ class DriverDSLImpl(
CORDAPP_WORKFLOW_VERSION
))
private inline fun <T> Config.withOptionalValue(key: String, obj: T?, body: (T) -> ConfigValue): Config {
return if (obj == null) {
this
} else {
withValue(key, body(obj))
}
}
private fun <T> valueFor(any: T): ConfigValue = ConfigValueFactory.fromAnyRef(any)
private fun <A> oneOf(array: Array<A>) = array[Random().nextInt(array.size)]
private fun startInProcessNode(
executorService: ScheduledExecutorService,
config: NodeConfig,
@ -969,15 +962,15 @@ class DriverDSLImpl(
val excludePackagePattern = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;" +
"com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;" +
"com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;" +
"io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;" +
"net.i2p**;org.apache**;" +
"io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;org.apache**;" +
"org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;" +
"org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;" +
"org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;" +
"com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**;)"
val excludeClassloaderPattern = "l(net.corda.core.serialization.internal.**)"
val quasarOptions = "m"
val extraJvmArguments = systemProperties.removeResolvedClasspath().map { "-D${it.key}=${it.value}" } +
"-javaagent:$quasarJarPath=$excludePackagePattern$excludeClassloaderPattern"
"-javaagent:$quasarJarPath=$quasarOptions$excludePackagePattern$excludeClassloaderPattern"
val loggingLevel = when {
logLevelOverride != null -> logLevelOverride
@ -992,26 +985,27 @@ class DriverDSLImpl(
it.addAll(extraCmdLineFlag)
}.toList()
val bytemanJvmArgs = {
val bytemanAgent = bytemanJarPath?.let {
bytemanPort?.let {
"-javaagent:$bytemanJarPath=port:$bytemanPort,listener:true"
}
val bytemanAgent = bytemanJarPath?.let {
bytemanPort?.let {
"-javaagent:$bytemanJarPath=port:$bytemanPort,listener:true"
}
listOfNotNull(bytemanAgent) +
if (bytemanAgent != null && debugPort != null) listOf(
}
val bytemanJvmArgs = listOfNotNull(bytemanAgent) +
if (bytemanAgent != null && debugPort != null) {
listOf(
"-Dorg.jboss.byteman.verbose=true",
"-Dorg.jboss.byteman.debug=true"
)
else emptyList()
}.invoke()
} else {
emptyList()
}
// The following dependencies are excluded from the classpath of the created JVM,
// so that the environment resembles a real one as close as possible.
val cp = ProcessUtilities.defaultClassPath.filter { cpEntry ->
val cpPathEntry = Paths.get(cpEntry)
cpPathEntry.isRegularFile()
&& !isTestArtifact(cpPathEntry.fileName.toString())
&& !isTestArtifact(cpPathEntry.name)
&& !cpPathEntry.isExcludedJar
}
@ -1019,7 +1013,7 @@ class DriverDSLImpl(
className = "net.corda.node.Corda", // cannot directly get class for this, so just use string
arguments = arguments,
jdwpPort = debugPort,
extraJvmArguments = extraJvmArguments + bytemanJvmArgs + "-Dnet.corda.node.printErrorsToStdErr=true",
extraJvmArguments = extraJvmArguments + bytemanJvmArgs + nodeJvmArgs + "-Dnet.corda.node.printErrorsToStdErr=true",
workingDirectory = config.corda.baseDirectory,
maximumHeapSize = maximumHeapSize,
classPath = cp,
@ -1066,10 +1060,10 @@ class DriverDSLImpl(
}
private fun startWebserver(handle: NodeHandleInternal, debugPort: Int?, maximumHeapSize: String): Process {
val className = "net.corda.webserver.WebServer"
writeConfig(handle.baseDirectory, "web-server.conf", handle.toWebServerConfig())
return ProcessUtilities.startJavaProcess(
className = className, // cannot directly get class for this, so just use string
className = "net.corda.webserver.WebServer", // cannot directly get class for this, so just use string
workingDirectory = handle.baseDirectory,
arguments = listOf(BASE_DIR, handle.baseDirectory.toString()),
jdwpPort = debugPort,
extraJvmArguments = listOf("-Dname=node-${handle.p2pAddress}-webserver") +
@ -1092,12 +1086,11 @@ class DriverDSLImpl(
}
private fun NodeHandleInternal.toWebServerConfig(): Config {
var config = ConfigFactory.empty()
config += "webAddress" to webAddress.toString()
config += "myLegalName" to configuration.myLegalName.toString()
config += "rpcAddress" to configuration.rpcOptions.address.toString()
config += "rpcUsers" to configuration.toConfig().getValue("rpcUsers")
config += "rpcUsers" to configuration.rpcUsers.map { it.toConfig().root().unwrapped() }
config += "useHTTPS" to useHTTPS
config += "baseDirectory" to configuration.baseDirectory.toAbsolutePath().toString()
@ -1111,7 +1104,7 @@ class DriverDSLImpl(
}
private fun createCordappsClassLoader(cordapps: Collection<TestCordappInternal>?): URLClassLoader? {
if (cordapps == null || cordapps.isEmpty()) {
if (cordapps.isNullOrEmpty()) {
return null
}
return URLClassLoader(cordapps.map { it.jarFile.toUri().toURL() }.toTypedArray())
@ -1267,59 +1260,7 @@ fun <DI : DriverDSL, D : InternalDriverDSL, A> genericDriver(
driverDsl.start()
return dsl(coerce(driverDsl))
} catch (exception: Throwable) {
DriverDSLImpl.log.error("Driver shutting down because of exception", exception)
throw exception
} finally {
driverDsl.shutdown()
shutdownHook.cancel()
}
}
}
/**
* This is a helper method to allow extending of the DSL, along the lines of
* interface SomeOtherExposedDSLInterface : DriverDSL
* interface SomeOtherInternalDSLInterface : InternalDriverDSL, SomeOtherExposedDSLInterface
* class SomeOtherDSL(val driverDSL : DriverDSLImpl) : InternalDriverDSL by driverDSL, SomeOtherInternalDSLInterface
*
* @param coerce We need this explicit coercion witness because we can't put an extra DI : D bound in a `where` clause.
*/
fun <DI : DriverDSL, D : InternalDriverDSL, A> genericDriver(
defaultParameters: DriverParameters = DriverParameters(),
driverDslWrapper: (DriverDSLImpl) -> D,
coerce: (D) -> DI, dsl: DI.() -> A
): A {
setDriverSerialization().use { _ ->
val driverDsl = driverDslWrapper(
DriverDSLImpl(
portAllocation = defaultParameters.portAllocation,
debugPortAllocation = defaultParameters.debugPortAllocation,
systemProperties = defaultParameters.systemProperties,
driverDirectory = defaultParameters.driverDirectory.toAbsolutePath(),
useTestClock = defaultParameters.useTestClock,
isDebug = defaultParameters.isDebug,
startNodesInProcess = defaultParameters.startNodesInProcess,
waitForAllNodesToFinish = defaultParameters.waitForAllNodesToFinish,
extraCordappPackagesToScan = @Suppress("DEPRECATION") defaultParameters.extraCordappPackagesToScan,
jmxPolicy = defaultParameters.jmxPolicy,
notarySpecs = defaultParameters.notarySpecs,
compatibilityZone = null,
networkParameters = defaultParameters.networkParameters,
notaryCustomOverrides = defaultParameters.notaryCustomOverrides,
inMemoryDB = defaultParameters.inMemoryDB,
cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes),
environmentVariables = defaultParameters.environmentVariables,
allowHibernateToManageAppSchema = defaultParameters.allowHibernateToManageAppSchema,
premigrateH2Database = defaultParameters.premigrateH2Database,
notaryHandleTimeout = defaultParameters.notaryHandleTimeout
)
)
val shutdownHook = addShutdownHook(driverDsl::shutdown)
try {
driverDsl.start()
return dsl(coerce(driverDsl))
} catch (exception: Throwable) {
DriverDSLImpl.log.error("Driver shutting down because of exception", exception)
loggerFor<DriverDSL>().error("Driver shutting down because of exception", exception)
throw exception
} finally {
driverDsl.shutdown()
@ -1334,7 +1275,7 @@ fun <DI : DriverDSL, D : InternalDriverDSL, A> genericDriver(
* @property publishNotaries Hook for a network map server to capture the generated [NotaryInfo] objects needed for
* creating the network parameters. This is needed as the network map server is expected to distribute it. The callback
* will occur on a different thread to the driver-calling thread.
* @property rootCert If specified then the nodes will register themselves with the doorman service using [url] and expect
* @property rootCert If specified then the nodes will register themselves with the doorman service using [SharedCompatibilityZoneParams.url] and expect
* the registration response to be rooted at this cert. If not specified then no registration is performed and the dev
* root cert is used as normal.
*

View File

@ -1,7 +1,5 @@
package net.corda.testing.node.internal
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.common.configuration.parsing.internal.ConfigurationWithOptions
import net.corda.core.DoNotImplement
import net.corda.core.crypto.SecureHash
@ -15,10 +13,8 @@ import net.corda.core.internal.FlowIORequest
import net.corda.core.internal.NetworkParametersStorage
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.createDirectories
import net.corda.core.internal.deleteIfExists
import net.corda.core.internal.div
import net.corda.core.internal.notary.NotaryService
import net.corda.core.internal.telemetry.TelemetryServiceImpl
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.MessageRecipients
@ -27,7 +23,6 @@ import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.node.NotaryInfo
import net.corda.core.internal.telemetry.TelemetryServiceImpl
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
@ -74,6 +69,8 @@ import net.corda.testing.node.MockServices.Companion.makeTestDataSourcePropertie
import net.corda.testing.node.TestClock
import org.apache.activemq.artemis.utils.ReusableLatch
import org.apache.sshd.common.util.security.SecurityUtils
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.whenever
import rx.Observable
import rx.Scheduler
import rx.internal.schedulers.CachedThreadScheduler
@ -85,6 +82,9 @@ import java.time.Clock
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference
import kotlin.io.path.createDirectories
import kotlin.io.path.deleteIfExists
import kotlin.io.path.div
val MOCK_VERSION_INFO = VersionInfo(PLATFORM_VERSION, "Mock release", "Mock revision", "Mock Vendor")
@ -116,9 +116,6 @@ data class InternalMockNodeParameters(
)
}
/**
* A [StartedNode] which exposes its internal [InternalMockNetwork.MockNode] for testing.
*/
interface TestStartedNode {
val internals: InternalMockNetwork.MockNode
val info: NodeInfo
@ -170,7 +167,7 @@ open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
val autoVisibleNodes: Boolean = true) : AutoCloseable {
companion object {
fun createCordappClassLoader(cordapps: Collection<TestCordappInternal>?): URLClassLoader? {
if (cordapps == null || cordapps.isEmpty()) {
if (cordapps.isNullOrEmpty()) {
return null
}
return URLClassLoader(cordapps.map { it.jarFile.toUri().toURL() }.toTypedArray())
@ -352,7 +349,6 @@ open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
private val entropyCounter = AtomicReference(args.entropyRoot)
override val log get() = staticLog
override val transactionVerifierWorkerCount: Int get() = 1
private var _rxIoScheduler: Scheduler? = null
override val rxIoScheduler: Scheduler
@ -455,18 +451,15 @@ open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
return if (track) {
smm.changes.filter { it is StateMachineManager.Change.Add }.map { it.logic }.ofType(initiatedFlowClass)
} else {
Observable.empty<T>()
Observable.empty()
}
}
override fun makeNetworkParametersStorage(): NetworkParametersStorage = MockNetworkParametersStorage()
}
fun createUnstartedNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters()): MockNode {
return createUnstartedNode(parameters, defaultFactory)
}
fun createUnstartedNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters(), nodeFactory: (MockNodeArgs) -> MockNode): MockNode {
fun createUnstartedNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters(),
nodeFactory: (MockNodeArgs) -> MockNode = defaultFactory): MockNode {
return createNodeImpl(parameters, nodeFactory, false)
}
@ -681,16 +674,17 @@ private fun mockNodeConfiguration(certificatesDirectory: Path): NodeConfiguratio
}
class MockNodeFlowManager : NodeFlowManager() {
val testingRegistrations = HashMap<Class<out FlowLogic<*>>, InitiatedFlowFactory<*>>()
private val testingRegistrations = HashMap<Class<out FlowLogic<*>>, InitiatedFlowFactory<*>>()
override fun getFlowFactoryForInitiatingFlow(initiatedFlowClass: Class<out FlowLogic<*>>): InitiatedFlowFactory<*>? {
if (initiatedFlowClass in testingRegistrations) {
return testingRegistrations.get(initiatedFlowClass)
return testingRegistrations[initiatedFlowClass]
}
return super.getFlowFactoryForInitiatingFlow(initiatedFlowClass)
}
fun registerTestingFactory(initiator: Class<out FlowLogic<*>>, factory: InitiatedFlowFactory<*>) {
testingRegistrations.put(initiator, factory)
testingRegistrations[initiator] = factory
}
}

View File

@ -1,7 +1,7 @@
package net.corda.testing.node.internal
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.whenever
import net.corda.node.services.config.FlowTimeoutConfiguration
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.NotaryConfig
@ -18,4 +18,4 @@ fun MockNodeConfigOverrides.applyMockNodeOverrides(config: NodeConfiguration) {
this.extraDataSourceProperties?.forEach { k, v -> it.dataSourceProperties.put(k, v) }
this.flowTimeout?.also { fto -> doReturn(FlowTimeoutConfiguration(fto.timeout, fto.maxRestartCount, fto.backoffBase)).whenever(config).flowTimeout }
}
}
}

View File

@ -10,8 +10,6 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.FlowStateMachineHandle
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.div
import net.corda.core.internal.readText
import net.corda.core.internal.times
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.services.AttachmentFixup
@ -22,14 +20,14 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.millis
import net.corda.core.utilities.seconds
import net.corda.coretesting.internal.createTestSerializationEnv
import net.corda.coretesting.internal.inVMExecutors
import net.corda.node.services.api.StartedNodeServices
import net.corda.node.services.messaging.Message
import net.corda.node.services.statemachine.Checkpoint
import net.corda.testing.driver.DriverDSL
import net.corda.testing.driver.NodeHandle
import net.corda.testing.internal.chooseIdentity
import net.corda.coretesting.internal.createTestSerializationEnv
import net.corda.coretesting.internal.inVMExecutors
import net.corda.testing.node.InMemoryMessagingNetwork
import net.corda.testing.node.TestCordapp
import net.corda.testing.node.User
@ -50,6 +48,8 @@ import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import kotlin.io.path.div
import kotlin.io.path.readText
import kotlin.reflect.KClass
private val log = LoggerFactory.getLogger("net.corda.testing.internal.InternalTestUtils")
@ -61,7 +61,7 @@ private val log = LoggerFactory.getLogger("net.corda.testing.internal.InternalTe
* You will probably need to use [FINANCE_CORDAPPS] instead to get access to the flows as well.
*/
@JvmField
val FINANCE_CONTRACTS_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.contracts")
val FINANCE_CONTRACTS_CORDAPP: ScanPackageTestCordapp = findCordapp("net.corda.finance.contracts")
/**
* Reference to the finance-workflows CorDapp in this repo. The metadata is taken directly from finance/workflows/build.gradle, including the
@ -70,10 +70,10 @@ val FINANCE_CONTRACTS_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.
* You will probably need to use [FINANCE_CORDAPPS] instead to get access to the contract classes as well.
*/
@JvmField
val FINANCE_WORKFLOWS_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.workflows")
val FINANCE_WORKFLOWS_CORDAPP: ScanPackageTestCordapp = findCordapp("net.corda.finance.workflows")
@JvmField
val FINANCE_CORDAPPS: Set<TestCordappImpl> = setOf(FINANCE_CONTRACTS_CORDAPP, FINANCE_WORKFLOWS_CORDAPP)
val FINANCE_CORDAPPS: Set<ScanPackageTestCordapp> = setOf(FINANCE_CONTRACTS_CORDAPP, FINANCE_WORKFLOWS_CORDAPP)
/**
* *Custom* CorDapp containing the contents of the `net.corda.testing.contracts` package, i.e. the dummy contracts. This is not a real CorDapp
@ -105,9 +105,9 @@ fun cordappWithFixups(fixups: List<AttachmentFixup>) = CustomCordapp(fixups = fi
/**
* Find the single CorDapp jar on the current classpath which contains the given package. This is a convenience method for
* [TestCordapp.findCordapp] but returns the internal [TestCordappImpl].
* [TestCordapp.findCordapp] but returns the internal [ScanPackageTestCordapp].
*/
fun findCordapp(scanPackage: String): TestCordappImpl = TestCordapp.findCordapp(scanPackage) as TestCordappImpl
fun findCordapp(scanPackage: String): ScanPackageTestCordapp = TestCordapp.findCordapp(scanPackage) as ScanPackageTestCordapp
/** Create a *custom* CorDapp which just contains the enclosed classes of the receiver class. */
fun Any.enclosedCordapp(): CustomCordapp {
@ -169,8 +169,7 @@ fun addressMustBeBoundFuture(executorService: ScheduledExecutorService, hostAndP
}
try {
Socket(hostAndPort.host, hostAndPort.port).close()
Unit
} catch (_exception: SocketException) {
} catch (_: SocketException) {
null
}
}
@ -188,7 +187,7 @@ fun nodeMustBeStartedFuture(
throw exception()
}
when {
logFile.readText().contains("Running P2PMessaging loop") -> {
"Running P2PMessaging loop" in logFile.readText() -> {
Unit
}
Instant.now().isAfter(stopPolling) -> {
@ -217,9 +216,7 @@ fun addressMustNotBeBoundFuture(executorService: ScheduledExecutorService, hostA
try {
Socket(hostAndPort.host, hostAndPort.port).close()
null
} catch (_exception: SocketException) {
Unit
}
} catch (_: SocketException) { }
}
}
@ -304,6 +301,10 @@ fun DriverDSL.assertUncompletedCheckpoints(name: CordaX500Name, expected: Long)
}
}
val nodeJvmArgs: List<String> by lazy {
DriverDSLImpl::class.java.getResourceAsStream("node-jvm-args.txt")!!.use { it.bufferedReader().readLines() }
}
/**
* Should only be used by Driver and MockNode.
*/

View File

@ -5,26 +5,27 @@ import net.corda.core.identity.Party
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.concurrent.fork
import net.corda.core.internal.concurrent.transpose
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.node.NodeInfo
import net.corda.core.node.NotaryInfo
import net.corda.core.utilities.getOrThrow
import net.corda.coretesting.internal.testThreadFactory
import net.corda.node.VersionInfo
import net.corda.node.internal.FlowManager
import net.corda.node.internal.Node
import net.corda.node.internal.NodeFlowManager
import net.corda.node.internal.NodeWithInfo
import net.corda.node.services.config.*
import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.FlowOverrideConfig
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.configOf
import net.corda.node.services.config.parseAsNodeConfiguration
import net.corda.node.services.config.plus
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.config.toConfig
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.driver.internal.incrementalPortAllocation
import net.corda.coretesting.internal.testThreadFactory
import net.corda.testing.node.User
import org.apache.commons.lang3.SystemUtils
import org.apache.logging.log4j.Level
import org.junit.After
import org.junit.Before
@ -34,7 +35,8 @@ import rx.internal.schedulers.CachedThreadScheduler
import java.nio.file.Path
import java.util.concurrent.Executors
import kotlin.concurrent.thread
import kotlin.test.assertFalse
import kotlin.io.path.createDirectories
import kotlin.io.path.div
// TODO Some of the logic here duplicates what's in the driver - the reason why it's not straightforward to replace it by
// using DriverDSLImpl in `init()` and `stopAllNodes()` is because of the platform version passed to nodes (driver doesn't
@ -60,7 +62,7 @@ abstract class NodeBasedTest @JvmOverloads constructor(
private val portAllocation = incrementalPortAllocation()
init {
System.setProperty("consoleLogLevel", Level.DEBUG.name().toLowerCase())
System.setProperty("consoleLogLevel", Level.DEBUG.name().lowercase())
}
@Before
@ -161,12 +163,9 @@ class InProcessNode(
configuration: NodeConfiguration,
versionInfo: VersionInfo,
flowManager: FlowManager = NodeFlowManager(configuration.flowOverrides),
allowHibernateToManageAppSchema: Boolean = true) : Node(configuration, versionInfo, false, flowManager = flowManager, allowHibernateToManageAppSchema = allowHibernateToManageAppSchema) {
allowHibernateToManageAppSchema: Boolean = true
) : Node(configuration, versionInfo, false, flowManager = flowManager, allowHibernateToManageAppSchema = allowHibernateToManageAppSchema) {
override val runMigrationScripts: Boolean = true
override fun start(): NodeInfo {
assertFalse(isInvalidJavaVersion(), "You are using a version of Java that is not supported (${SystemUtils.JAVA_VERSION}). Please upgrade to the latest version of Java 8.")
return super.start()
}
override val rxIoScheduler get() = CachedThreadScheduler(testThreadFactory()).also { runOnStop += it::shutdown }

View File

@ -1,8 +1,9 @@
package net.corda.testing.node.internal
import net.corda.core.internal.div
import java.io.File
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.div
object ProcessUtilities {
@Suppress("LongParameterList")
@ -38,19 +39,16 @@ object ProcessUtilities {
maximumHeapSize: String? = null,
identifier: String = "",
environmentVariables: Map<String,String> = emptyMap(),
inheritIO: Boolean = true
): Process {
val command = mutableListOf<String>().apply {
add(javaPath)
(jdwpPort != null) && add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$jdwpPort")
if (maximumHeapSize != null) add("-Xmx$maximumHeapSize")
add("-XX:+UseG1GC")
addAll(extraJvmArguments)
add(className)
addAll(arguments)
}
return ProcessBuilder(command).apply {
if (inheritIO) inheritIO()
environment().putAll(environmentVariables)
environment()["CLASSPATH"] = classPath.joinToString(File.pathSeparator)
if (workingDirectory != null) {
@ -63,7 +61,7 @@ object ProcessUtilities {
}.start()
}
private val javaPath = (System.getProperty("java.home") / "bin" / "java").toString()
private val javaPath = Path(System.getProperty("java.home"), "bin", "java").toString()
val defaultClassPath: List<String> = System.getProperty("java.class.path").split(File.pathSeparator)
}
}

View File

@ -14,7 +14,6 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.concurrent.doneFuture
import net.corda.core.internal.concurrent.fork
import net.corda.core.internal.concurrent.map
import net.corda.core.internal.div
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.RPCOps
import net.corda.core.node.NetworkParameters
@ -59,6 +58,7 @@ import java.nio.file.Path
import java.nio.file.Paths
import java.time.Duration
import java.util.*
import kotlin.io.path.div
import net.corda.nodeapi.internal.config.User as InternalUser
inline fun <reified I : RPCOps> RPCDriverDSL.startInVmRpcClient(
@ -188,23 +188,23 @@ data class RPCDriverDSL(
private val driverDSL: DriverDSLImpl, private val externalTrace: Trace?
) : InternalDriverDSL by driverDSL {
private companion object {
const val notificationAddress = "notifications"
const val NOTIFICATION_ADDRESS = "notifications"
private fun ConfigurationImpl.configureCommonSettings(maxFileSize: Int, maxBufferedBytesPerClient: Long) {
name = "RPCDriver"
managementNotificationAddress = SimpleString(notificationAddress)
managementNotificationAddress = SimpleString.of(NOTIFICATION_ADDRESS)
isPopulateValidatedUser = true
journalBufferSize_NIO = maxFileSize
journalBufferSize_AIO = maxFileSize
journalFileSize = maxFileSize
queueConfigs = listOf(
QueueConfiguration(RPCApi.RPC_SERVER_QUEUE_NAME).setAddress(RPCApi.RPC_SERVER_QUEUE_NAME).setDurable(false),
QueueConfiguration(RPCApi.RPC_CLIENT_BINDING_REMOVALS).setAddress(notificationAddress)
QueueConfiguration.of(RPCApi.RPC_SERVER_QUEUE_NAME).setAddress(RPCApi.RPC_SERVER_QUEUE_NAME).setDurable(false),
QueueConfiguration.of(RPCApi.RPC_CLIENT_BINDING_REMOVALS).setAddress(NOTIFICATION_ADDRESS)
.setFilterString(RPCApi.RPC_CLIENT_BINDING_REMOVAL_FILTER_EXPRESSION).setDurable(false),
QueueConfiguration(RPCApi.RPC_CLIENT_BINDING_ADDITIONS).setAddress(notificationAddress)
QueueConfiguration.of(RPCApi.RPC_CLIENT_BINDING_ADDITIONS).setAddress(NOTIFICATION_ADDRESS)
.setFilterString(RPCApi.RPC_CLIENT_BINDING_ADDITION_FILTER_EXPRESSION).setDurable(false)
)
addressesSettings = mapOf(
addressSettings = mapOf(
"${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.#" to AddressSettings().apply {
maxSizeBytes = maxBufferedBytesPerClient
addressFullMessagePolicy = AddressFullMessagePolicy.PAGE

View File

@ -1,7 +1,9 @@
package net.corda.testing.node.internal
import io.github.classgraph.ClassGraph
import net.corda.core.internal.*
import net.corda.core.internal.attributes
import net.corda.core.internal.mapToSet
import net.corda.core.internal.pooledScan
import net.corda.core.utilities.contextLogger
import net.corda.testing.node.TestCordapp
import org.gradle.tooling.GradleConnector
@ -9,9 +11,10 @@ import org.gradle.tooling.ProgressEvent
import java.io.File
import java.io.RandomAccessFile
import java.nio.file.Path
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.streams.toList
import kotlin.io.path.div
import kotlin.io.path.exists
import kotlin.io.path.useDirectoryEntries
/**
* Implementation of the public [TestCordapp] API.
@ -21,39 +24,43 @@ import kotlin.streams.toList
* the [scanPackage] may reference a gradle CorDapp project on the local system. In this scenerio the project's "jar" task is executed to
* build the CorDapp jar. This allows us to inherit the CorDapp's MANIFEST information without having to do any extra processing.
*/
data class TestCordappImpl(val scanPackage: String, override val config: Map<String, Any>) : TestCordappInternal() {
override fun withConfig(config: Map<String, Any>): TestCordappImpl = copy(config = config)
data class ScanPackageTestCordapp(val scanPackage: String,
override val config: Map<String, Any> = emptyMap(),
val signed: Boolean = false) : TestCordappInternal() {
override fun withConfig(config: Map<String, Any>): ScanPackageTestCordapp = copy(config = config)
override fun withOnlyJarContents(): TestCordappImpl = copy(config = emptyMap())
override fun asSigned(): TestCordapp = copy(signed = true)
override val jarFile: Path
get() {
val jars = findJars(scanPackage)
when (jars.size) {
0 -> throw IllegalArgumentException("There are no CorDapps containing the package $scanPackage on the classpath. Make sure " +
"the package name is correct and that the CorDapp is added as a gradle dependency.")
1 -> return jars.first()
else -> throw IllegalArgumentException("There is more than one CorDapp containing the package $scanPackage on the classpath " +
"$jars. Specify a package name which is unique to the CorDapp.")
}
override fun withOnlyJarContents(): ScanPackageTestCordapp = copy(config = emptyMap(), signed = false)
override val jarFile: Path by lazy {
val jars = findJars()
val jar = when (jars.size) {
0 -> throw IllegalArgumentException("There are no CorDapps containing the package $scanPackage on the classpath. Make sure " +
"the package name is correct and that the CorDapp is added as a gradle dependency.")
1 -> jars.first()
else -> throw IllegalArgumentException("There is more than one CorDapp containing the package $scanPackage on the classpath " +
"$jars. Specify a package name which is unique to the CorDapp.")
}
if (signed) TestCordappSigner.signJarCopy(jar) else jar
}
private fun findJars(): Set<Path> {
val rootPaths = findRootPaths(scanPackage)
return if (rootPaths.all { it.toString().endsWith(".jar") }) {
// We don't need to do anything more if all the root paths are jars
rootPaths
} else {
// Otherwise we need to build those paths which are local projects and extract the built jar from them
rootPaths.mapToSet { if (it.toString().endsWith(".jar")) it else buildCordappJar(it) }
}
}
companion object {
private val packageToRootPaths = ConcurrentHashMap<String, Set<Path>>()
private val projectRootToBuiltJar = ConcurrentHashMap<Path, Path>()
private val log = contextLogger()
fun findJars(scanPackage: String): Set<Path> {
val rootPaths = findRootPaths(scanPackage)
return if (rootPaths.all { it.toString().endsWith(".jar") }) {
// We don't need to do anything more if all the root paths are jars
rootPaths
} else {
// Otherwise we need to build those paths which are local projects and extract the built jar from them
rootPaths.mapTo(HashSet()) { if (it.toString().endsWith(".jar")) it else buildCordappJar(it) }
}
}
private fun findRootPaths(scanPackage: String): Set<Path> {
return packageToRootPaths.computeIfAbsent(scanPackage) {
val classGraph = ClassGraph().acceptPaths(scanPackage.replace('.', '/'))
@ -87,11 +94,8 @@ data class TestCordappImpl(val scanPackage: String, override val config: Map<Str
runGradleBuild(projectRoot)
val libs = projectRoot / "build" / "libs"
val jars = libs.list {
it.filter { it.toString().endsWith(".jar") }
.filter { !it.toString().endsWith("sources.jar") }
.filter { !it.toString().endsWith("javadoc.jar") }
.toList()
val jars = libs.useDirectoryEntries("*.jar") { jars ->
jars.filter { !it.toString().endsWith("sources.jar") && !it.toString().endsWith("javadoc.jar") }.toList()
}.sortedBy { it.attributes().creationTime() }
checkNotNull(jars.lastOrNull()) { "No jars were built in $libs" }
}

View File

@ -2,12 +2,13 @@ package net.corda.testing.node.internal
import com.typesafe.config.ConfigValueFactory
import net.corda.core.internal.copyToDirectory
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.internal.writeText
import net.corda.testing.node.TestCordapp
import java.nio.file.FileAlreadyExistsException
import java.nio.file.Path
import kotlin.io.path.createDirectories
import kotlin.io.path.div
import kotlin.io.path.name
import kotlin.io.path.writeText
/**
* Extends the public [TestCordapp] API with internal extensions for use within the testing framework and for internal testing of the platform.
@ -45,7 +46,7 @@ abstract class TestCordappInternal : TestCordapp() {
// Ignore if the node already has the same CorDapp jar. This can happen if the node is being restarted.
}
val configString = ConfigValueFactory.fromMap(cordapp.config).toConfig().root().render()
(configDir / "${jar.fileName.toString().removeSuffix(".jar")}.conf").writeText(configString)
(configDir / "${jar.name.removeSuffix(".jar")}.conf").writeText(configString)
}
}

View File

@ -0,0 +1,45 @@
package net.corda.testing.node.internal
import net.corda.core.internal.deleteRecursively
import net.corda.testing.core.internal.JarSignatureTestUtils.containsKey
import net.corda.testing.core.internal.JarSignatureTestUtils.generateKey
import net.corda.testing.core.internal.JarSignatureTestUtils.signJar
import net.corda.testing.core.internal.JarSignatureTestUtils.unsignJar
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.absolutePathString
import kotlin.io.path.copyTo
import kotlin.io.path.name
object TestCordappSigner {
private val defaultSignerDir = Files.createTempDirectory("testcordapp-signer")
init {
defaultSignerDir.generateKey(alias = "testcordapp")
Runtime.getRuntime().addShutdownHook(Thread(defaultSignerDir::deleteRecursively))
}
fun signJarCopy(jar: Path, signerDir: Path? = null, signatureCount: Int = 1, algorithm: String = "RSA"): Path {
val copy = Files.createTempFile(jar.name, ".jar")
copy.toFile().deleteOnExit()
jar.copyTo(copy, overwrite = true)
signJar(copy, signerDir, signatureCount, algorithm)
return copy
}
fun signJar(jar: Path, signerDir: Path? = null, signatureCount: Int = 1, algorithm: String = "RSA") {
jar.unsignJar()
val signerDirToUse = signerDir ?: defaultSignerDir
for (i in 1 .. signatureCount) {
// Note in the jarsigner tool if -sigfile is not specified then the first 8 chars of alias are used as the file
// name for the .SF and .DSA files. (See jarsigner doc). So $i below needs to be at beginning so unique files are
// created.
val alias = "$i-testcordapp-$algorithm"
val password = "secret!"
if (!signerDirToUse.containsKey(alias, password)) {
signerDirToUse.generateKey(alias, password, "O=Test Company Ltd $i,OU=Test,L=London,C=GB", algorithm)
}
signerDirToUse.signJar(jar.absolutePathString(), alias, password)
}
}
}

View File

@ -0,0 +1,39 @@
package net.corda.testing.node.internal
import net.corda.core.internal.copyTo
import net.corda.core.utilities.Try
import net.corda.core.utilities.Try.Failure
import net.corda.core.utilities.Try.Success
import net.corda.testing.node.TestCordapp
import java.net.URI
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
import kotlin.io.path.toPath
data class UriTestCordapp(val uri: URI,
override val config: Map<String, Any> = emptyMap(),
val signed: Boolean = false) : TestCordappInternal() {
override fun withConfig(config: Map<String, Any>): TestCordapp = copy(config = config)
override fun asSigned(): TestCordapp = copy(signed = true)
override fun withOnlyJarContents(): TestCordappInternal = copy(config = emptyMap(), signed = false)
override val jarFile: Path by lazy {
val toPathAttempt = Try.on(uri::toPath)
when (toPathAttempt) {
is Success -> if (signed) TestCordappSigner.signJarCopy(toPathAttempt.value) else toPathAttempt.value
is Failure -> {
// URI is not a local path, so we copy it to a temp file and use that.
val downloaded = Files.createTempFile("test-cordapp-${uri.path.substringAfterLast("/").substringBeforeLast(".jar")}", ".jar")
downloaded.toFile().deleteOnExit()
uri.toURL().openStream().use { it.copyTo(downloaded, REPLACE_EXISTING) }
if (signed) {
TestCordappSigner.signJar(downloaded)
}
downloaded
}
}
}
}

View File

@ -2,6 +2,10 @@
package net.corda.testing.node.internal.network
import jakarta.ws.rs.GET
import jakarta.ws.rs.Path
import jakarta.ws.rs.Produces
import jakarta.ws.rs.core.Response
import net.corda.core.crypto.Crypto
import net.corda.core.internal.CertRole
import net.corda.core.internal.toX500Name
@ -24,11 +28,11 @@ import org.bouncycastle.asn1.x509.DistributionPointName
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralNames
import org.eclipse.jetty.ee10.servlet.ServletContextHandler
import org.eclipse.jetty.ee10.servlet.ServletHolder
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.ServerConnector
import org.eclipse.jetty.server.handler.HandlerCollection
import org.eclipse.jetty.servlet.ServletContextHandler
import org.eclipse.jetty.servlet.ServletHolder
import org.eclipse.jetty.server.handler.ContextHandlerCollection
import org.glassfish.jersey.server.ResourceConfig
import org.glassfish.jersey.servlet.ServletContainer
import java.io.Closeable
@ -40,10 +44,6 @@ import java.security.cert.X509Certificate
import java.time.Duration
import java.util.*
import javax.security.auth.x500.X500Principal
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.core.Response
import kotlin.collections.ArrayList
class CrlServer(hostAndPort: NetworkHostAndPort) : Closeable {
@ -79,7 +79,7 @@ class CrlServer(hostAndPort: NetworkHostAndPort) : Closeable {
}
private val server: Server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply {
handler = HandlerCollection().apply {
handler = ContextHandlerCollection().apply {
addHandler(buildServletContextHandler())
}
}

View File

@ -1,5 +1,15 @@
package net.corda.testing.node.internal.network
import jakarta.ws.rs.Consumes
import jakarta.ws.rs.GET
import jakarta.ws.rs.POST
import jakarta.ws.rs.Path
import jakarta.ws.rs.PathParam
import jakarta.ws.rs.Produces
import jakarta.ws.rs.core.MediaType
import jakarta.ws.rs.core.Response
import jakarta.ws.rs.core.Response.ok
import jakarta.ws.rs.core.Response.status
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignedData
import net.corda.core.identity.CordaX500Name
@ -14,11 +24,11 @@ import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.ParametersUpdate
import net.corda.testing.common.internal.testNetworkParameters
import org.eclipse.jetty.ee10.servlet.ServletContextHandler
import org.eclipse.jetty.ee10.servlet.ServletHolder
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.ServerConnector
import org.eclipse.jetty.server.handler.HandlerCollection
import org.eclipse.jetty.servlet.ServletContextHandler
import org.eclipse.jetty.servlet.ServletHolder
import org.eclipse.jetty.server.handler.ContextHandlerCollection
import org.glassfish.jersey.server.ResourceConfig
import org.glassfish.jersey.servlet.ServletContainer
import java.io.Closeable
@ -29,11 +39,6 @@ import java.security.SignatureException
import java.time.Duration
import java.time.Instant
import java.util.*
import javax.ws.rs.*
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
import javax.ws.rs.core.Response.ok
import javax.ws.rs.core.Response.status
class NetworkMapServer(private val pollInterval: Duration,
hostAndPort: NetworkHostAndPort = NetworkHostAndPort("localhost", 0),
@ -54,7 +59,7 @@ class NetworkMapServer(private val pollInterval: Duration,
init {
server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply {
handler = HandlerCollection().apply {
handler = ContextHandlerCollection().apply {
addHandler(ServletContextHandler().apply {
contextPath = "/"
val resourceConfig = ResourceConfig().apply {
@ -232,7 +237,7 @@ class NetworkMapServer(private val pollInterval: Duration,
null
}
requireNotNull(requestedParameters)
return Response.ok(requestedParameters!!.serialize().bytes).build()
return Response.ok(requestedParameters.serialize().bytes).build()
}
@GET

View File

@ -14,11 +14,12 @@ import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.DUMMY_CONTRACTS_CORDAPP
import net.corda.testing.node.internal.enclosedCordapp
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.security.PublicKey
import java.util.*
import java.util.Random
class CustomNotaryTest {
private lateinit var mockNet: MockNetwork
@ -50,13 +51,15 @@ class CustomNotaryTest {
mockNet.stopNodes()
}
@Test(expected = CustomNotaryException::class, timeout=300_000)
@Test(timeout=300_000)
fun `custom notary service is active`() {
val tx = DummyContract.generateInitial(Random().nextInt(), notary, alice.ref(0))
val stx = aliceNode.services.signInitialTransaction(tx)
val future = aliceNode.startFlow(NotaryFlow.Client(stx))
mockNet.runNetwork()
future.getOrThrow()
assertThatExceptionOfType(CustomNotaryException::class.java).isThrownBy {
future.getOrThrow()
}
}
class CustomNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : NotaryService() {

View File

@ -2,13 +2,13 @@ package net.corda.testing.node.internal
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.cordapp.get
import net.corda.core.internal.inputStream
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.nio.file.Path
import java.util.jar.JarInputStream
import kotlin.io.path.inputStream
class CustomCordappTest {
@Rule