Merge branch 'release/os/4.3' into release/os/4.4

# Conflicts:
#	docs/source/docker-image.rst
This commit is contained in:
Anthony Keenan 2019-10-21 13:44:32 +01:00
commit 0a194cfb51
91 changed files with 1787 additions and 913 deletions

View File

@ -29,20 +29,18 @@ pipeline {
}
}
stage('Corda Pull Request - Run Tests') {
stage('Unit Tests') {
steps {
sh "./gradlew " +
"-DbuildId=\"\${BUILD_ID}\" " +
"-Dkubenetize=true " +
"-Ddocker.tag=\"\${DOCKER_TAG_TO_USE}\"" +
" allParallelUnitTest"
if (env.CHANGE_ID) {
pullRequest.createStatus(status: 'success',
context: 'continuous-integration/jenkins/pr-merge/unitTest',
description: 'Unit Tests Passed',
targetUrl: "${env.JOB_URL}/testResults")
}
stage('Unit Tests') {
steps {
sh "./gradlew " +
"-DbuildId=\"\${BUILD_ID}\" " +
"-Dkubenetize=true " +
"-Ddocker.tag=\"\${DOCKER_TAG_TO_USE}\"" +
" allParallelUnitTest"
if (env.CHANGE_ID) {
pullRequest.createStatus(status: 'success',
context: 'continuous-integration/jenkins/pr-merge/unitTest',
description: 'Unit Tests Passed',
targetUrl: "${env.JOB_URL}/testResults")
}
}
}

View File

@ -3,6 +3,14 @@
<option name="LINE_SEPARATOR" value="&#10;" />
<option name="RIGHT_MARGIN" value="140" />
<option name="SOFT_MARGINS" value="140" />
<JavaCodeStyleSettings>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
</JavaCodeStyleSettings>
<GroovyCodeStyleSettings>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="100" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="100" />
</GroovyCodeStyleSettings>
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
@ -11,6 +19,8 @@
<package name="tornadofx" withSubpackages="false" static="false" />
</value>
</option>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="CONTINUATION_INDENT_IN_PARAMETER_LISTS" value="true" />
<option name="CONTINUATION_INDENT_IN_ARGUMENT_LISTS" value="true" />
<option name="CONTINUATION_INDENT_FOR_EXPRESSION_BODIES" value="true" />

View File

@ -1,6 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

3
Jenkinsfile vendored
View File

@ -1,3 +1,4 @@
import static com.r3.build.BuildControl.killAllExistingBuildsForJob
@Library('existing-build-control')
import static com.r3.build.BuildControl.killAllExistingBuildsForJob
@ -8,7 +9,7 @@ pipeline {
options { timestamps() }
environment {
DOCKER_TAG_TO_USE = "${UUID.randomUUID().toString().toLowerCase().subSequence(0, 12)}"
DOCKER_TAG_TO_USE = "${env.GIT_COMMIT.subSequence(0, 8)}"
EXECUTOR_NUMBER = "${env.EXECUTOR_NUMBER}"
BUILD_ID = "${env.BUILD_ID}-${env.JOB_NAME}"
}

View File

@ -448,7 +448,7 @@ task detektBaseline(type: JavaExec) {
main = "io.gitlab.arturbosch.detekt.cli.Main"
classpath = configurations.detekt
def input = "$projectDir"
def config = "$projectDir/detekt-config.yml"
def config = "$projectDir/detekt-config.yml, $projectDir/detekt-baseline-config.yml"
def baseline = "$projectDir/detekt-baseline.xml"
def params = ['-i', input, '-c', config, '-b', baseline, '--create-baseline']
args(params)
@ -612,6 +612,14 @@ task allParallelSlowIntegrationTest(type: ParallelTestGroup) {
memoryInGbPerFork 10
distribute Distribution.CLASS
}
task allParallelSmokeTest(type: ParallelTestGroup) {
testGroups "smokeTest"
numberOfShards 4
streamOutput true
coresPerFork 6
memoryInGbPerFork 10
distribute Distribution.METHOD
}
task allParallelUnitTest(type: ParallelTestGroup) {
testGroups "test"
numberOfShards 10
@ -623,7 +631,15 @@ task allParallelUnitTest(type: ParallelTestGroup) {
task allParallelUnitAndIntegrationTest(type: ParallelTestGroup) {
testGroups "test", "integrationTest"
numberOfShards 15
streamOutput true
streamOutput false
coresPerFork 6
memoryInGbPerFork 10
distribute Distribution.CLASS
}
task parallelRegressionTest(type: ParallelTestGroup) {
testGroups "test", "integrationTest", "slowIntegrationTest", "smokeTest"
numberOfShards 5
streamOutput false
coresPerFork 6
memoryInGbPerFork 10
distribute Distribution.CLASS

View File

@ -51,13 +51,12 @@ public class BucketingAllocator {
System.out.println("Number of tests: " + container.testsForFork.stream().mapToInt(b -> b.foundTests.size()).sum());
System.out.println("Tests to Run: ");
container.testsForFork.forEach(tb -> {
System.out.println(tb.nameWithAsterix);
System.out.println(tb.testName);
tb.foundTests.forEach(ft -> System.out.println("\t" + ft.getFirst() + ", " + ft.getSecond()));
});
});
}
private void allocateTestsToForks(@NotNull List<TestBucket> matchedTests) {
matchedTests.forEach(matchedTestBucket -> {
TestsForForkContainer smallestContainer = Collections.min(forkContainers, Comparator.comparing(TestsForForkContainer::getCurrentDuration));
@ -69,10 +68,9 @@ public class BucketingAllocator {
return allDiscoveredTests.stream().map(tuple -> {
String testName = tuple.getFirst();
Object task = tuple.getSecond();
String noAsterixName = testName.substring(0, testName.length() - 1);
//2DO [can this filtering algorithm be improved - the test names are sorted, it should be possible to do something using binary search]
List<Tuple2<String, Double>> matchingTests = allTestsFromCSV.stream().filter(testFromCSV -> testFromCSV.getFirst().startsWith(noAsterixName)).collect(Collectors.toList());
return new TestBucket(task, testName, noAsterixName, matchingTests);
List<Tuple2<String, Double>> matchingTests = allTestsFromCSV.stream().filter(testFromCSV -> testFromCSV.getFirst().startsWith(testName)).collect(Collectors.toList());
return new TestBucket(task, testName, matchingTests);
}).sorted(Comparator.comparing(TestBucket::getDuration).reversed()).collect(Collectors.toList());
}
@ -81,22 +79,20 @@ public class BucketingAllocator {
TestLister lister = source.getFirst();
Object testTask = source.getSecond();
return lister.getAllTestsDiscovered().stream().map(test -> new Tuple2<>(test, testTask)).collect(Collectors.toList());
}).flatMap(Collection::stream).collect(Collectors.toList());
}).flatMap(Collection::stream).sorted(Comparator.comparing(Tuple2::getFirst)).collect(Collectors.toList());
}
public static class TestBucket {
final Object testTask;
final String nameWithAsterix;
final String nameWithoutAsterix;
final String testName;
final List<Tuple2<String, Double>> foundTests;
final Double duration;
public TestBucket(Object testTask, String nameWithAsterix, String nameWithoutAsterix, List<Tuple2<String, Double>> foundTests) {
public TestBucket(Object testTask, String testName, List<Tuple2<String, Double>> foundTests) {
this.testTask = testTask;
this.nameWithAsterix = nameWithAsterix;
this.nameWithoutAsterix = nameWithoutAsterix;
this.testName = testName;
this.foundTests = foundTests;
duration = Math.max(foundTests.stream().mapToDouble(tp -> Math.max(tp.getSecond(), 10)).sum(), 10);
duration = Math.max(foundTests.stream().mapToDouble(tp -> Math.max(tp.getSecond(), 1)).sum(), 1);
}
public Double getDuration() {
@ -107,8 +103,7 @@ public class BucketingAllocator {
public String toString() {
return "TestBucket{" +
"testTask=" + testTask +
", nameWithAsterix='" + nameWithAsterix + '\'' +
", nameWithoutAsterix='" + nameWithoutAsterix + '\'' +
", nameWithAsterix='" + testName + '\'' +
", foundTests=" + foundTests +
", duration=" + duration +
'}';
@ -142,7 +137,7 @@ public class BucketingAllocator {
}
public List<String> getTestsForTask(Object task) {
return frozenTests.getOrDefault(task, Collections.emptyList()).stream().map(it -> it.nameWithAsterix).collect(Collectors.toList());
return frozenTests.getOrDefault(task, Collections.emptyList()).stream().map(it -> it.testName).collect(Collectors.toList());
}
public List<TestBucket> getBucketsForFork() {

View File

@ -41,8 +41,8 @@ public class BucketingAllocatorTask extends DefaultTask {
this.dependsOn(source);
}
public List<String> getTestsForForkAndTestTask(Integer fork, Test testTask) {
return allocator.getTestsForForkAndTestTask(fork, testTask);
public List<String> getTestIncludesForForkAndTestTask(Integer fork, Test testTask) {
return allocator.getTestsForForkAndTestTask(fork, testTask).stream().map(t -> t + "*").collect(Collectors.toList());
}
@TaskAction
@ -56,11 +56,11 @@ public class BucketingAllocatorTask extends DefaultTask {
String duration = "Duration(ms)";
List<CSVRecord> records = CSVFormat.DEFAULT.withHeader().parse(reader).getRecords();
return records.stream().map(record -> {
try{
try {
String testName = record.get(name);
String testDuration = record.get(duration);
return new Tuple2<>(testName, Math.max(Double.parseDouble(testDuration), 10));
}catch (IllegalArgumentException | IllegalStateException e){
return new Tuple2<>(testName, Math.max(Double.parseDouble(testDuration), 1));
} catch (IllegalArgumentException | IllegalStateException e) {
return null;
}
}).filter(Objects::nonNull).sorted(Comparator.comparing(Tuple2::getFirst)).collect(Collectors.toList());

View File

@ -10,7 +10,6 @@ import org.gradle.api.tasks.testing.Test
*/
class DistributedTesting implements Plugin<Project> {
static def getPropertyAsInt(Project proj, String property, Integer defaultValue) {
return proj.hasProperty(property) ? Integer.parseInt(proj.property(property).toString()) : defaultValue
}
@ -33,6 +32,7 @@ class DistributedTesting implements Plugin<Project> {
//1. add the task to determine all tests within the module
//2. modify the underlying testing task to use the output of the listing task to include a subset of tests for each fork
//3. KubesTest will invoke these test tasks in a parallel fashion on a remote k8s cluster
//4. after each completed test write its name to a file to keep track of what finished for restart purposes
project.subprojects { Project subProject ->
subProject.tasks.withType(Test) { Test task ->
println "Evaluating ${task.getPath()}"
@ -47,7 +47,6 @@ class DistributedTesting implements Plugin<Project> {
if (!task.hasProperty("ignoreForDistribution")) {
KubesTest parallelTestTask = generateParallelTestingTask(subProject, task, imageBuildingTask, providedTag)
}
}
}
@ -129,9 +128,23 @@ class DistributedTesting implements Plugin<Project> {
maxHeapSize = "6g"
doFirst {
filter {
List<String> executedTests = []
File executedTestsFile = new File(KubesTest.TEST_RUN_DIR + "/executedTests.txt")
try {
executedTests = executedTestsFile.readLines()
} catch (FileNotFoundException e) {
executedTestsFile.createNewFile()
}
task.afterTest { desc, result ->
executedTestsFile.withWriterAppend { writer ->
writer.writeLine(desc.getClassName() + "." + desc.getName())
}
}
def fork = getPropertyAsInt(subProject, "dockerFork", 0)
subProject.logger.info("requesting tests to include in testing task ${task.getPath()} (idx: ${fork})")
List<String> includes = globalAllocator.getTestsForForkAndTestTask(
List<String> includes = globalAllocator.getTestIncludesForForkAndTestTask(
fork,
task)
subProject.logger.info "got ${includes.size()} tests to include into testing task ${task.getPath()}"
@ -141,10 +154,18 @@ class DistributedTesting implements Plugin<Project> {
excludeTestsMatching "*"
}
includes.removeAll(executedTests)
executedTests.forEach { exclude ->
subProject.logger.info "excluding: $exclude for testing task ${task.getPath()}"
excludeTestsMatching exclude
}
includes.forEach { include ->
subProject.logger.info "including: $include for testing task ${task.getPath()}"
includeTestsMatching include
}
failOnNoMatchingTests false
}
}

View File

@ -1,123 +0,0 @@
package net.corda.testing
import com.bmuschko.gradle.docker.DockerRegistryCredentials
import com.bmuschko.gradle.docker.tasks.container.*
import com.bmuschko.gradle.docker.tasks.image.*
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
/**
this plugin is responsible for setting up all the required docker image building tasks required for producing and pushing an
image of the current build output to a remote container registry
*/
class ImageBuilding implements Plugin<Project> {
public static final String registryName = "stefanotestingcr.azurecr.io/testing"
DockerPushImage pushTask
@Override
void apply(Project project) {
def registryCredentialsForPush = new DockerRegistryCredentials(project.getObjects())
registryCredentialsForPush.username.set("stefanotestingcr")
registryCredentialsForPush.password.set(System.getProperty("docker.push.password") ? System.getProperty("docker.push.password") : "")
DockerPullImage pullTask = project.tasks.create("pullBaseImage", DockerPullImage) {
repository = "stefanotestingcr.azurecr.io/buildbase"
tag = "latest"
doFirst {
registryCredentials = registryCredentialsForPush
}
}
DockerBuildImage buildDockerImageForSource = project.tasks.create('buildDockerImageForSource', DockerBuildImage) {
dependsOn Arrays.asList(project.rootProject.getTasksByName("clean", true), pullTask)
inputDir.set(new File("."))
dockerFile.set(new File(new File("testing"), "Dockerfile"))
}
DockerCreateContainer createBuildContainer = project.tasks.create('createBuildContainer', DockerCreateContainer) {
File baseWorkingDir = new File(System.getProperty("docker.work.dir") ? System.getProperty("docker.work.dir") : System.getProperty("java.io.tmpdir"))
File gradleDir = new File(baseWorkingDir, "gradle")
File mavenDir = new File(baseWorkingDir, "maven")
doFirst {
if (!gradleDir.exists()) {
gradleDir.mkdirs()
}
if (!mavenDir.exists()) {
mavenDir.mkdirs()
}
logger.info("Will use: ${gradleDir.absolutePath} for caching gradle artifacts")
}
dependsOn buildDockerImageForSource
targetImageId buildDockerImageForSource.getImageId()
binds = [(gradleDir.absolutePath): "/tmp/gradle", (mavenDir.absolutePath): "/home/root/.m2"]
}
DockerStartContainer startBuildContainer = project.tasks.create('startBuildContainer', DockerStartContainer) {
dependsOn createBuildContainer
targetContainerId createBuildContainer.getContainerId()
}
DockerLogsContainer logBuildContainer = project.tasks.create('logBuildContainer', DockerLogsContainer) {
dependsOn startBuildContainer
targetContainerId createBuildContainer.getContainerId()
follow = true
}
DockerWaitContainer waitForBuildContainer = project.tasks.create('waitForBuildContainer', DockerWaitContainer) {
dependsOn logBuildContainer
targetContainerId createBuildContainer.getContainerId()
doLast {
if (getExitCode() != 0) {
throw new GradleException("Failed to build docker image, aborting build")
}
}
}
DockerCommitImage commitBuildImageResult = project.tasks.create('commitBuildImageResult', DockerCommitImage) {
dependsOn waitForBuildContainer
targetContainerId createBuildContainer.getContainerId()
}
DockerTagImage tagBuildImageResult = project.tasks.create('tagBuildImageResult', DockerTagImage) {
dependsOn commitBuildImageResult
imageId = commitBuildImageResult.getImageId()
tag = System.getProperty("docker.provided.tag") ? System.getProperty("docker.provided.tag") : "${UUID.randomUUID().toString().toLowerCase().subSequence(0, 12)}"
repository = registryName
}
DockerPushImage pushBuildImage = project.tasks.create('pushBuildImage', DockerPushImage) {
dependsOn tagBuildImageResult
doFirst {
registryCredentials = registryCredentialsForPush
}
imageName = registryName
tag = tagBuildImageResult.tag
}
this.pushTask = pushBuildImage
DockerRemoveContainer deleteContainer = project.tasks.create('deleteBuildContainer', DockerRemoveContainer) {
dependsOn pushBuildImage
targetContainerId createBuildContainer.getContainerId()
}
DockerRemoveImage deleteTaggedImage = project.tasks.create('deleteTaggedImage', DockerRemoveImage) {
dependsOn pushBuildImage
force = true
targetImageId commitBuildImageResult.getImageId()
}
DockerRemoveImage deleteBuildImage = project.tasks.create('deleteBuildImage', DockerRemoveImage) {
dependsOn deleteContainer, deleteTaggedImage
force = true
targetImageId buildDockerImageForSource.getImageId()
}
if (System.getProperty("docker.keep.image") == null) {
pushBuildImage.finalizedBy(deleteContainer, deleteBuildImage, deleteTaggedImage)
}
}
}

View File

@ -0,0 +1,147 @@
package net.corda.testing;
import com.bmuschko.gradle.docker.DockerRegistryCredentials;
import com.bmuschko.gradle.docker.tasks.container.*;
import com.bmuschko.gradle.docker.tasks.image.*;
import org.gradle.api.GradleException;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* this plugin is responsible for setting up all the required docker image building tasks required for producing and pushing an
* image of the current build output to a remote container registry
*/
public class ImageBuilding implements Plugin<Project> {
public static final String registryName = "stefanotestingcr.azurecr.io/testing";
public DockerPushImage pushTask;
@Override
public void apply(@NotNull final Project project) {
final DockerRegistryCredentials registryCredentialsForPush = new DockerRegistryCredentials(project.getObjects());
registryCredentialsForPush.getUsername().set("stefanotestingcr");
registryCredentialsForPush.getPassword().set(System.getProperty("docker.push.password", ""));
final DockerPullImage pullTask = project.getTasks().create("pullBaseImage", DockerPullImage.class, dockerPullImage -> {
dockerPullImage.doFirst(task -> dockerPullImage.setRegistryCredentials(registryCredentialsForPush));
dockerPullImage.getRepository().set("stefanotestingcr.azurecr.io/buildbase");
dockerPullImage.getTag().set("latest");
});
final DockerBuildImage buildDockerImageForSource = project.getTasks().create("buildDockerImageForSource", DockerBuildImage.class,
dockerBuildImage -> {
dockerBuildImage.dependsOn(Arrays.asList(project.getRootProject().getTasksByName("clean", true), pullTask));
dockerBuildImage.getInputDir().set(new File("."));
dockerBuildImage.getDockerFile().set(new File(new File("testing"), "Dockerfile"));
});
final DockerCreateContainer createBuildContainer = project.getTasks().create("createBuildContainer", DockerCreateContainer.class,
dockerCreateContainer -> {
final File baseWorkingDir = new File(System.getProperty("docker.work.dir") != null &&
!System.getProperty("docker.work.dir").isEmpty() ?
System.getProperty("docker.work.dir") : System.getProperty("java.io.tmpdir"));
final File gradleDir = new File(baseWorkingDir, "gradle");
final File mavenDir = new File(baseWorkingDir, "maven");
dockerCreateContainer.doFirst(task -> {
if (!gradleDir.exists()) {
gradleDir.mkdirs();
}
if (!mavenDir.exists()) {
mavenDir.mkdirs();
}
project.getLogger().info("Will use: ${gradleDir.absolutePath} for caching gradle artifacts");
});
dockerCreateContainer.dependsOn(buildDockerImageForSource);
dockerCreateContainer.targetImageId(buildDockerImageForSource.getImageId());
final Map<String, String> map = new HashMap<>();
map.put(gradleDir.getAbsolutePath(), "/tmp/gradle");
map.put(mavenDir.getAbsolutePath(), "/home/root/.m2");
dockerCreateContainer.getBinds().set(map);
});
final DockerStartContainer startBuildContainer = project.getTasks().create("startBuildContainer", DockerStartContainer.class,
dockerStartContainer -> {
dockerStartContainer.dependsOn(createBuildContainer);
dockerStartContainer.targetContainerId(createBuildContainer.getContainerId());
});
final DockerLogsContainer logBuildContainer = project.getTasks().create("logBuildContainer", DockerLogsContainer.class,
dockerLogsContainer -> {
dockerLogsContainer.dependsOn(startBuildContainer);
dockerLogsContainer.targetContainerId(createBuildContainer.getContainerId());
dockerLogsContainer.getFollow().set(true);
});
final DockerWaitContainer waitForBuildContainer = project.getTasks().create("waitForBuildContainer", DockerWaitContainer.class,
dockerWaitContainer -> {
dockerWaitContainer.dependsOn(logBuildContainer);
dockerWaitContainer.targetContainerId(createBuildContainer.getContainerId());
dockerWaitContainer.doLast(task -> {
if (dockerWaitContainer.getExitCode() != 0) {
throw new GradleException("Failed to build docker image, aborting build");
}
});
});
final DockerCommitImage commitBuildImageResult = project.getTasks().create("commitBuildImageResult", DockerCommitImage.class,
dockerCommitImage -> {
dockerCommitImage.dependsOn(waitForBuildContainer);
dockerCommitImage.targetContainerId(createBuildContainer.getContainerId());
});
final DockerTagImage tagBuildImageResult = project.getTasks().create("tagBuildImageResult", DockerTagImage.class, dockerTagImage -> {
dockerTagImage.dependsOn(commitBuildImageResult);
dockerTagImage.getImageId().set(commitBuildImageResult.getImageId());
dockerTagImage.getTag().set(System.getProperty("docker.provided.tag",
"${UUID.randomUUID().toString().toLowerCase().subSequence(0, 12)}"));
dockerTagImage.getRepository().set(registryName);
});
final DockerPushImage pushBuildImage = project.getTasks().create("pushBuildImage", DockerPushImage.class, dockerPushImage -> {
dockerPushImage.dependsOn(tagBuildImageResult);
dockerPushImage.doFirst(task -> dockerPushImage.setRegistryCredentials(registryCredentialsForPush));
dockerPushImage.getImageName().set(registryName);
dockerPushImage.getTag().set(tagBuildImageResult.getTag());
});
this.pushTask = pushBuildImage;
final DockerRemoveContainer deleteContainer = project.getTasks().create("deleteBuildContainer", DockerRemoveContainer.class,
dockerRemoveContainer -> {
dockerRemoveContainer.dependsOn(pushBuildImage);
dockerRemoveContainer.targetContainerId(createBuildContainer.getContainerId());
});
final DockerRemoveImage deleteTaggedImage = project.getTasks().create("deleteTaggedImage", DockerRemoveImage.class,
dockerRemoveImage -> {
dockerRemoveImage.dependsOn(pushBuildImage);
dockerRemoveImage.getForce().set(true);
dockerRemoveImage.targetImageId(commitBuildImageResult.getImageId());
});
final DockerRemoveImage deleteBuildImage = project.getTasks().create("deleteBuildImage", DockerRemoveImage.class,
dockerRemoveImage -> {
dockerRemoveImage.dependsOn(deleteContainer, deleteTaggedImage);
dockerRemoveImage.getForce().set(true);
dockerRemoveImage.targetImageId(buildDockerImageForSource.getImageId());
});
if (System.getProperty("docker.keep.image") == null) {
pushBuildImage.finalizedBy(deleteContainer, deleteBuildImage, deleteTaggedImage);
}
}
}

View File

@ -1,325 +0,0 @@
package net.corda.testing
import io.fabric8.kubernetes.api.model.*
import io.fabric8.kubernetes.client.*
import io.fabric8.kubernetes.client.dsl.ExecListener
import io.fabric8.kubernetes.client.dsl.ExecWatch
import io.fabric8.kubernetes.client.utils.Serialization
import okhttp3.Response
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.tasks.TaskAction
import java.nio.file.Files
import java.nio.file.Path
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.stream.Collectors
import java.util.stream.IntStream
class KubesTest extends DefaultTask {
static final ExecutorService executorService = Executors.newCachedThreadPool()
static final ExecutorService singleThreadedExecutor = Executors.newSingleThreadExecutor()
String dockerTag
String fullTaskToExecutePath
String taskToExecuteName
Boolean printOutput = false
Integer numberOfCoresPerFork = 4
Integer memoryGbPerFork = 6
public volatile List<File> testOutput = Collections.emptyList()
public volatile List<KubePodResult> containerResults = Collections.emptyList()
String namespace = "thisisatest"
int k8sTimeout = 50 * 1_000
int webSocketTimeout = k8sTimeout * 6
int numberOfPods = 20
int timeoutInMinutesForPodToStart = 60
Distribution distribution = Distribution.METHOD
@TaskAction
void runTestsOnKubes() {
try {
Class.forName("org.apache.commons.compress.archivers.tar.TarArchiveInputStream")
} catch (ClassNotFoundException ignored) {
throw new GradleException("Apache Commons compress has not be loaded, this can happen if running from within intellj - please select \"delegate to gradle\" for build and test actions")
}
def buildId = System.getProperty("buildId") ? System.getProperty("buildId") :
(project.hasProperty("corda_revision") ? project.property("corda_revision").toString() : "0")
def currentUser = System.getProperty("user.name") ? System.getProperty("user.name") : "UNKNOWN_USER"
String stableRunId = new BigInteger(64, new Random(buildId.hashCode() + currentUser.hashCode() + taskToExecuteName.hashCode())).toString(36).toLowerCase()
String suffix = new BigInteger(64, new Random()).toString(36).toLowerCase()
io.fabric8.kubernetes.client.Config config = new io.fabric8.kubernetes.client.ConfigBuilder()
.withConnectionTimeout(k8sTimeout)
.withRequestTimeout(k8sTimeout)
.withRollingTimeout(k8sTimeout)
.withWebsocketTimeout(webSocketTimeout)
.withWebsocketPingInterval(webSocketTimeout)
.build()
final KubernetesClient client = new DefaultKubernetesClient(config)
try {
client.pods().inNamespace(namespace).list().getItems().forEach({ podToDelete ->
if (podToDelete.getMetadata().name.contains(stableRunId)) {
project.logger.lifecycle("deleting: " + podToDelete.getMetadata().getName())
client.resource(podToDelete).delete()
}
})
} catch (Exception ignored) {
//it's possible that a pod is being deleted by the original build, this can lead to racey conditions
}
List<CompletableFuture<KubePodResult>> futures = IntStream.range(0, numberOfPods).mapToObj({ i ->
String potentialPodName = (taskToExecuteName + "-" + stableRunId + suffix + i).toLowerCase()
String podName = potentialPodName.substring(0, Math.min(potentialPodName.size(), 62))
runBuild(client, namespace, numberOfPods, i, podName, printOutput, 3)
}).collect(Collectors.toList())
this.testOutput = Collections.synchronizedList(futures.collect { it -> it.get().binaryResults }.flatten())
this.containerResults = futures.collect { it -> it.get() }
}
CompletableFuture<KubePodResult> runBuild(KubernetesClient client,
String namespace,
int numberOfPods,
int podIdx,
String podName,
boolean printOutput,
int numberOfRetries) {
CompletableFuture<KubePodResult> toReturn = new CompletableFuture<KubePodResult>()
executorService.submit({
int tryCount = 0
Pod createdPod = null
while (tryCount < numberOfRetries) {
try {
Pod podRequest = buildPod(podName)
project.logger.lifecycle("requesting pod: " + podName)
createdPod = client.pods().inNamespace(namespace).create(podRequest)
project.logger.lifecycle("scheduled pod: " + podName)
File outputFile = Files.createTempFile("container", ".log").toFile()
attachStatusListenerToPod(client, namespace, podName)
schedulePodForDeleteOnShutdown(podName, client, createdPod)
waitForPodToStart(podName, client, namespace)
def stdOutOs = new PipedOutputStream()
def stdOutIs = new PipedInputStream(4096)
ByteArrayOutputStream errChannelStream = new ByteArrayOutputStream();
KubePodResult result = new KubePodResult(createdPod, null, outputFile)
CompletableFuture<KubePodResult> waiter = new CompletableFuture<>()
ExecListener execListener = buildExecListenerForPod(podName, errChannelStream, waiter, result)
stdOutIs.connect(stdOutOs)
String[] buildCommand = getBuildCommand(numberOfPods, podIdx)
ExecWatch execWatch = client.pods().inNamespace(namespace).withName(podName)
.writingOutput(stdOutOs)
.writingErrorChannel(errChannelStream)
.usingListener(execListener).exec(buildCommand)
startLogPumping(outputFile, stdOutIs, podIdx, printOutput)
KubePodResult execResult = waiter.join()
project.logger.lifecycle("build has ended on on pod ${podName} (${podIdx}/${numberOfPods})")
project.logger.lifecycle "Gathering test results from ${execResult.createdPod.metadata.name}"
def binaryResults = downloadTestXmlFromPod(client, namespace, execResult.createdPod)
project.logger.lifecycle("deleting: " + execResult.createdPod.getMetadata().getName())
client.resource(execResult.createdPod).delete()
result.binaryResults = binaryResults
toReturn.complete(result)
break
} catch (Exception e) {
logger.error("Encountered error during testing cycle on pod ${podName} (${podIdx}/${numberOfPods})", e)
try {
if (createdPod) {
client.pods().delete(createdPod)
while (client.pods().inNamespace(namespace).list().getItems().find { p -> p.metadata.name == podName }) {
logger.warn("pod ${podName} has not been deleted, waiting 1s")
Thread.sleep(1000)
}
}
} catch (Exception ignored) {
}
tryCount++
logger.lifecycle("will retry ${podName} another ${numberOfRetries - tryCount} times")
}
}
if (tryCount >= numberOfRetries) {
toReturn.completeExceptionally(new RuntimeException("Failed to build in pod ${podName} (${podIdx}/${numberOfPods}) within retry limit"))
}
})
return toReturn
}
void startLogPumping(File outputFile, stdOutIs, podIdx, boolean printOutput) {
Thread loggingThread = new Thread({ ->
BufferedWriter out = null
BufferedReader br = null
try {
out = new BufferedWriter(new FileWriter(outputFile))
br = new BufferedReader(new InputStreamReader(stdOutIs))
String line
while ((line = br.readLine()) != null) {
def toWrite = ("${taskToExecuteName}/Container" + podIdx + ": " + line).trim()
if (printOutput) {
project.logger.lifecycle(toWrite)
}
out.println(toWrite)
}
} catch (IOException ignored) {
}
finally {
out?.close()
br?.close()
}
})
loggingThread.setDaemon(true)
loggingThread.start()
}
ExecListener buildExecListenerForPod(podName, errChannelStream, CompletableFuture<KubePodResult> waitingFuture, KubePodResult result) {
new ExecListener() {
final Long start = System.currentTimeMillis()
@Override
void onOpen(Response response) {
project.logger.lifecycle("Build started on pod $podName")
}
@Override
void onFailure(Throwable t, Response response) {
project.logger.lifecycle("Received error from rom pod $podName")
waitingFuture.completeExceptionally(t)
}
@Override
void onClose(int code, String reason) {
project.logger.lifecycle("Received onClose() from pod ${podName}, build took: ${(System.currentTimeMillis() - start) / 1000} seconds")
try {
def errChannelContents = errChannelStream.toString()
Status status = Serialization.unmarshal(errChannelContents, Status.class);
result.resultCode = status.details?.causes?.first()?.message?.toInteger() ? status.details?.causes?.first()?.message?.toInteger() : 0
waitingFuture.complete(result)
} catch (Exception e) {
waitingFuture.completeExceptionally(e)
}
}
}
}
void schedulePodForDeleteOnShutdown(String podName, client, Pod createdPod) {
project.logger.info("attaching shutdown hook for pod ${podName}")
Runtime.getRuntime().addShutdownHook({
println "Deleting pod: " + podName
client.pods().delete(createdPod)
})
}
Watch attachStatusListenerToPod(KubernetesClient client, String namespace, String podName) {
client.pods().inNamespace(namespace).withName(podName).watch(new Watcher<Pod>() {
@Override
void eventReceived(Watcher.Action action, Pod resource) {
project.logger.lifecycle("[StatusChange] pod ${resource.getMetadata().getName()} ${action.name()} (${resource.status.phase})")
}
@Override
void onClose(KubernetesClientException cause) {
}
})
}
void waitForPodToStart(String podName, KubernetesClient client, String namespace) {
project.logger.lifecycle("Waiting for pod " + podName + " to start before executing build")
client.pods().inNamespace(namespace).withName(podName).waitUntilReady(timeoutInMinutesForPodToStart, TimeUnit.MINUTES)
project.logger.lifecycle("pod " + podName + " has started, executing build")
}
Pod buildPod(String podName) {
return new PodBuilder().withNewMetadata().withName(podName).endMetadata()
.withNewSpec()
.addNewVolume()
.withName("gradlecache")
.withNewHostPath()
.withPath("/tmp/gradle")
.withType("DirectoryOrCreate")
.endHostPath()
.endVolume()
.addNewContainer()
.withImage(dockerTag)
.withCommand("bash")
.withArgs("-c", "sleep 3600")
.addNewEnv()
.withName("DRIVER_NODE_MEMORY")
.withValue("1024m")
.withName("DRIVER_WEB_MEMORY")
.withValue("1024m")
.endEnv()
.withName(podName)
.withNewResources()
.addToRequests("cpu", new Quantity("${numberOfCoresPerFork}"))
.addToRequests("memory", new Quantity("${memoryGbPerFork}Gi"))
.endResources()
.addNewVolumeMount()
.withName("gradlecache")
.withMountPath("/tmp/gradle")
.endVolumeMount()
.endContainer()
.withImagePullSecrets(new LocalObjectReference("regcred"))
.withRestartPolicy("Never")
.endSpec()
.build()
}
String[] getBuildCommand(int numberOfPods, int podIdx) {
return ["bash", "-c",
"let x=1 ; while [ \${x} -ne 0 ] ; do echo \"Waiting for DNS\" ; curl services.gradle.org > /dev/null 2>&1 ; x=\$? ; sleep 1 ; done ; " +
"cd /tmp/source ; " +
"let y=1 ; while [ \${y} -ne 0 ] ; do echo \"Preparing build directory\" ; ./gradlew testClasses integrationTestClasses --parallel 2>&1 ; y=\$? ; sleep 1 ; done ;" +
"./gradlew -D${ListTests.DISTRIBUTION_PROPERTY}=${distribution.name()} -Dkubenetize -PdockerFork=" + podIdx + " -PdockerForks=" + numberOfPods + " $fullTaskToExecutePath --info 2>&1 ;" +
"let rs=\$? ; sleep 10 ; exit \${rs}"]
}
Collection<File> downloadTestXmlFromPod(KubernetesClient client, String namespace, Pod cp) {
String resultsInContainerPath = "/tmp/source/build/test-reports"
String binaryResultsFile = "results.bin"
String podName = cp.getMetadata().getName()
Path tempDir = new File(new File(project.getBuildDir(), "test-results-xml"), podName).toPath()
if (!tempDir.toFile().exists()) {
tempDir.toFile().mkdirs()
}
project.logger.lifecycle("Saving " + podName + " results to: " + tempDir.toAbsolutePath().toFile().getAbsolutePath())
client.pods()
.inNamespace(namespace)
.withName(podName)
.dir(resultsInContainerPath)
.copy(tempDir)
return findFolderContainingBinaryResultsFile(new File(tempDir.toFile().getAbsolutePath()), binaryResultsFile)
}
List<File> findFolderContainingBinaryResultsFile(File start, String fileNameToFind) {
Queue<File> filesToInspect = new LinkedList<>(Collections.singletonList(start))
List<File> folders = new ArrayList<>()
while (!filesToInspect.isEmpty()) {
File fileToInspect = filesToInspect.poll()
if (fileToInspect.getAbsolutePath().endsWith(fileNameToFind)) {
folders.add(fileToInspect.parentFile)
}
if (fileToInspect.isDirectory()) {
filesToInspect.addAll(Arrays.stream(fileToInspect.listFiles()).collect(Collectors.toList()))
}
}
return folders
}
}

View File

@ -0,0 +1,388 @@
package net.corda.testing;
import io.fabric8.kubernetes.api.model.*;
import io.fabric8.kubernetes.client.*;
import io.fabric8.kubernetes.client.dsl.ExecListener;
import io.fabric8.kubernetes.client.dsl.PodResource;
import io.fabric8.kubernetes.client.utils.Serialization;
import net.corda.testing.retry.Retry;
import okhttp3.Response;
import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.TaskAction;
import org.jetbrains.annotations.NotNull;
import java.io.*;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class KubesTest extends DefaultTask {
static final String TEST_RUN_DIR = "/test-runs";
private static final ExecutorService executorService = Executors.newCachedThreadPool();
/**
* Name of the k8s Secret object that holds the credentials to access the docker image registry
*/
private static final String REGISTRY_CREDENTIALS_SECRET_NAME = "regcred";
String dockerTag;
String fullTaskToExecutePath;
String taskToExecuteName;
Boolean printOutput = false;
Integer numberOfCoresPerFork = 4;
Integer memoryGbPerFork = 6;
public volatile List<File> testOutput = Collections.emptyList();
public volatile List<KubePodResult> containerResults = Collections.emptyList();
String namespace = "thisisatest";
int k8sTimeout = 50 * 1_000;
int webSocketTimeout = k8sTimeout * 6;
int numberOfPods = 20;
int timeoutInMinutesForPodToStart = 60;
Distribution distribution = Distribution.METHOD;
@TaskAction
public void runDistributedTests() {
String buildId = System.getProperty("buildId", "0");
String currentUser = System.getProperty("user.name", "UNKNOWN_USER");
String stableRunId = rnd64Base36(new Random(buildId.hashCode() + currentUser.hashCode() + taskToExecuteName.hashCode()));
String random = rnd64Base36(new Random());
final KubernetesClient client = getKubernetesClient();
try {
client.pods().inNamespace(namespace).list().getItems().forEach(podToDelete -> {
if (podToDelete.getMetadata().getName().contains(stableRunId)) {
getProject().getLogger().lifecycle("deleting: " + podToDelete.getMetadata().getName());
client.resource(podToDelete).delete();
}
});
} catch (Exception ignored) {
//it's possible that a pod is being deleted by the original build, this can lead to racey conditions
}
List<Future<KubePodResult>> futures = IntStream.range(0, numberOfPods).mapToObj(i -> {
String podName = taskToExecuteName.toLowerCase() + "-" + stableRunId + "-" + random + "-" + i;
return submitBuild(client, namespace, numberOfPods, i, podName, printOutput, 3);
}).collect(Collectors.toList());
this.testOutput = Collections.synchronizedList(futures.stream().map(it -> {
try {
return it.get().getBinaryResults();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}).flatMap(Collection::stream).collect(Collectors.toList()));
this.containerResults = futures.stream().map(it -> {
try {
return it.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}).collect(Collectors.toList());
}
@NotNull
private KubernetesClient getKubernetesClient() {
io.fabric8.kubernetes.client.Config config = new io.fabric8.kubernetes.client.ConfigBuilder()
.withConnectionTimeout(k8sTimeout)
.withRequestTimeout(k8sTimeout)
.withRollingTimeout(k8sTimeout)
.withWebsocketTimeout(webSocketTimeout)
.withWebsocketPingInterval(webSocketTimeout)
.build();
return new DefaultKubernetesClient(config);
}
private static String rnd64Base36(Random rnd) {
return new BigInteger(64, rnd)
.toString(36)
.toLowerCase();
}
private Future<KubePodResult> submitBuild(
KubernetesClient client,
String namespace,
int numberOfPods,
int podIdx,
String podName,
boolean printOutput,
int numberOfRetries
) {
return executorService.submit(() -> buildRunPodWithRetriesOrThrow(client, namespace, numberOfPods, podIdx, podName, printOutput, numberOfRetries));
}
private static void addShutdownHook(Runnable hook) {
Runtime.getRuntime().addShutdownHook(new Thread(hook));
}
private PersistentVolumeClaim createPvc(KubernetesClient client, String name) {
PersistentVolumeClaim pvc = client.persistentVolumeClaims()
.inNamespace(namespace)
.createNew()
.editOrNewMetadata().withName(name).endMetadata()
.editOrNewSpec()
.withAccessModes("ReadWriteOnce")
.editOrNewResources().addToRequests("storage", new Quantity("100Mi")).endResources()
.endSpec()
.done();
addShutdownHook(() -> {
System.out.println("Deleing PVC: " + pvc.getMetadata().getName());
client.persistentVolumeClaims().delete(pvc);
});
return pvc;
}
private KubePodResult buildRunPodWithRetriesOrThrow(
KubernetesClient client,
String namespace,
int numberOfPods,
int podIdx,
String podName,
boolean printOutput,
int numberOfRetries
) {
addShutdownHook(() -> {
System.out.println("deleting pod: " + podName);
client.pods().inNamespace(namespace).withName(podName).delete();
});
try {
// pods might die, so we retry
return Retry.fixed(numberOfRetries).run(() -> {
// remove pod if exists
PodResource<Pod, DoneablePod> oldPod = client.pods().inNamespace(namespace).withName(podName);
if (oldPod.get() != null) {
getLogger().lifecycle("deleting pod: {}", podName);
oldPod.delete();
while (oldPod.get() != null) {
getLogger().info("waiting for pod {} to be removed", podName);
Thread.sleep(1000);
}
}
// recreate and run
getProject().getLogger().lifecycle("creating pod: " + podName);
Pod createdPod = client.pods().inNamespace(namespace).create(buildPodRequest(podName));
getProject().getLogger().lifecycle("scheduled pod: " + podName);
attachStatusListenerToPod(client, createdPod);
waitForPodToStart(client, createdPod);
PipedOutputStream stdOutOs = new PipedOutputStream();
PipedInputStream stdOutIs = new PipedInputStream(4096);
ByteArrayOutputStream errChannelStream = new ByteArrayOutputStream();
CompletableFuture<Integer> waiter = new CompletableFuture<>();
ExecListener execListener = buildExecListenerForPod(podName, errChannelStream, waiter);
stdOutIs.connect(stdOutOs);
client.pods().inNamespace(namespace).withName(podName)
.writingOutput(stdOutOs)
.writingErrorChannel(errChannelStream)
.usingListener(execListener)
.exec(getBuildCommand(numberOfPods, podIdx));
File podOutput = startLogPumping(stdOutIs, podIdx, printOutput);
int resCode = waiter.join();
getProject().getLogger().lifecycle("build has ended on on pod " + podName + " (" + podIdx + "/" + numberOfPods + "), gathering results");
Collection<File> binaryResults = downloadTestXmlFromPod(client, namespace, createdPod);
return new KubePodResult(resCode, podOutput, binaryResults);
});
} catch (Retry.RetryException e) {
throw new RuntimeException("Failed to build in pod " + podName + " (" + podIdx + "/" + numberOfPods + ") in " + numberOfRetries + " attempts", e);
}
}
private Pod buildPodRequest(String podName) {
return new PodBuilder()
.withNewMetadata().withName(podName).endMetadata()
.withNewSpec()
.addNewVolume()
.withName("gradlecache")
.withNewHostPath()
.withType("DirectoryOrCreate")
.withPath("/tmp/gradle")
.endHostPath()
.endVolume()
.addNewVolume()
.withName("testruns")
.withNewHostPath()
.withType("DirectoryOrCreate")
.withPath("/tmp/testruns")
.endHostPath()
.endVolume()
// .addNewVolume()
// .withName("testruns")
// .withNewPersistentVolumeClaim()
// .withClaimName(pvc.getMetadata().getName())
// .endPersistentVolumeClaim()
// .endVolume()
.addNewContainer()
.withImage(dockerTag)
.withCommand("bash")
.withArgs("-c", "sleep 3600")
.addNewEnv()
.withName("DRIVER_NODE_MEMORY")
.withValue("1024m")
.withName("DRIVER_WEB_MEMORY")
.withValue("1024m")
.endEnv()
.withName(podName)
.withNewResources()
.addToRequests("cpu", new Quantity(numberOfCoresPerFork.toString()))
.addToRequests("memory", new Quantity(memoryGbPerFork.toString() + "Gi"))
.endResources()
.addNewVolumeMount().withName("gradlecache").withMountPath("/tmp/gradle").endVolumeMount()
.addNewVolumeMount().withName("testruns").withMountPath(TEST_RUN_DIR).endVolumeMount()
.endContainer()
.addNewImagePullSecret(REGISTRY_CREDENTIALS_SECRET_NAME)
.withRestartPolicy("Never")
.endSpec()
.build();
}
private File startLogPumping(InputStream stdOutIs, int podIdx, boolean printOutput) throws IOException {
File outputFile = Files.createTempFile("container", ".log").toFile();
Thread loggingThread = new Thread(() -> {
try (BufferedWriter out = new BufferedWriter(new FileWriter(outputFile));
BufferedReader br = new BufferedReader(new InputStreamReader(stdOutIs))) {
String line;
while ((line = br.readLine()) != null) {
String toWrite = ("Container" + podIdx + ": " + line).trim();
if (printOutput) {
getProject().getLogger().lifecycle(toWrite);
}
out.write(line);
out.newLine();
}
} catch (IOException ignored) {
}
});
loggingThread.setDaemon(true);
loggingThread.start();
return outputFile;
}
private Watch attachStatusListenerToPod(KubernetesClient client, Pod pod) {
return client.pods().inNamespace(pod.getMetadata().getNamespace()).withName(pod.getMetadata().getName()).watch(new Watcher<Pod>() {
@Override
public void eventReceived(Watcher.Action action, Pod resource) {
getProject().getLogger().lifecycle("[StatusChange] pod " + resource.getMetadata().getName() + " " + action.name() + " (" + resource.getStatus().getPhase() + ")");
}
@Override
public void onClose(KubernetesClientException cause) {
}
});
}
private void waitForPodToStart(KubernetesClient client, Pod pod) {
getProject().getLogger().lifecycle("Waiting for pod " + pod.getMetadata().getName() + " to start before executing build");
try {
client.pods().inNamespace(pod.getMetadata().getNamespace()).withName(pod.getMetadata().getName()).waitUntilReady(timeoutInMinutesForPodToStart, TimeUnit.MINUTES);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
getProject().getLogger().lifecycle("pod " + pod.getMetadata().getName() + " has started, executing build");
}
private Collection<File> downloadTestXmlFromPod(KubernetesClient client, String namespace, Pod cp) {
String resultsInContainerPath = "/tmp/source/build/test-reports";
String binaryResultsFile = "results.bin";
String podName = cp.getMetadata().getName();
Path tempDir = new File(new File(getProject().getBuildDir(), "test-results-xml"), podName).toPath();
if (!tempDir.toFile().exists()) {
tempDir.toFile().mkdirs();
}
getProject().getLogger().lifecycle("Saving " + podName + " results to: " + tempDir.toAbsolutePath().toFile().getAbsolutePath());
client.pods()
.inNamespace(namespace)
.withName(podName)
.dir(resultsInContainerPath)
.copy(tempDir);
return findFolderContainingBinaryResultsFile(new File(tempDir.toFile().getAbsolutePath()), binaryResultsFile);
}
private String[] getBuildCommand(int numberOfPods, int podIdx) {
String shellScript = "let x=1 ; while [ ${x} -ne 0 ] ; do echo \"Waiting for DNS\" ; curl services.gradle.org > /dev/null 2>&1 ; x=$? ; sleep 1 ; done ; " + "cd /tmp/source ; " +
"let y=1 ; while [ ${y} -ne 0 ] ; do echo \"Preparing build directory\" ; ./gradlew testClasses integrationTestClasses --parallel 2>&1 ; y=$? ; sleep 1 ; done ;" +
"./gradlew -D" + ListTests.DISTRIBUTION_PROPERTY + "=" + distribution.name() + " -Dkubenetize -PdockerFork=" + podIdx + " -PdockerForks=" + numberOfPods + " " + fullTaskToExecutePath + " --info 2>&1 ;" +
"let rs=$? ; sleep 10 ; exit ${rs}";
return new String[]{"bash", "-c", shellScript};
}
private List<File> findFolderContainingBinaryResultsFile(File start, String fileNameToFind) {
Queue<File> filesToInspect = new LinkedList<>(Collections.singletonList(start));
List<File> folders = new ArrayList<>();
while (!filesToInspect.isEmpty()) {
File fileToInspect = filesToInspect.poll();
if (fileToInspect.getAbsolutePath().endsWith(fileNameToFind)) {
folders.add(fileToInspect.getParentFile());
}
if (fileToInspect.isDirectory()) {
filesToInspect.addAll(Arrays.stream(fileToInspect.listFiles()).collect(Collectors.toList()));
}
}
return folders;
}
private ExecListener buildExecListenerForPod(String podName, ByteArrayOutputStream errChannelStream, CompletableFuture<Integer> waitingFuture) {
return new ExecListener() {
final Long start = System.currentTimeMillis();
@Override
public void onOpen(Response response) {
getProject().getLogger().lifecycle("Build started on pod " + podName);
}
@Override
public void onFailure(Throwable t, Response response) {
getProject().getLogger().lifecycle("Received error from rom pod " + podName);
waitingFuture.completeExceptionally(t);
}
@Override
public void onClose(int code, String reason) {
getProject().getLogger().lifecycle("Received onClose() from pod " + podName + " , build took: " + ((System.currentTimeMillis() - start) / 1000) + " seconds");
try {
String errChannelContents = errChannelStream.toString();
Status status = Serialization.unmarshal(errChannelContents, Status.class);
Integer resultCode = Optional.ofNullable(status).map(Status::getDetails)
.map(StatusDetails::getCauses)
.flatMap(c -> c.stream().findFirst())
.map(StatusCause::getMessage)
.map(Integer::parseInt).orElse(0);
waitingFuture.complete(resultCode);
} catch (Exception e) {
waitingFuture.completeExceptionally(e);
}
}
};
}
}

View File

@ -79,7 +79,7 @@ class ListTests extends DefaultTask implements TestLister {
.collect { c -> (c.getSubclasses() + Collections.singletonList(c)) }
.flatten()
.collect { ClassInfo c ->
c.getMethodInfo().filter { m -> m.hasAnnotation("org.junit.Test") }.collect { m -> c.name + "." + m.name + "*" }
c.getMethodInfo().filter { m -> m.hasAnnotation("org.junit.Test") }.collect { m -> c.name + "." + m.name }
}.flatten()
.toSet()
@ -97,7 +97,7 @@ class ListTests extends DefaultTask implements TestLister {
.getClassesWithMethodAnnotation("org.junit.Test")
.collect { c -> (c.getSubclasses() + Collections.singletonList(c)) }
.flatten()
.collect { ClassInfo c -> c.name + "*" }.flatten()
.collect { ClassInfo c -> c.name }.flatten()
.toSet()
this.allTests = results.stream().sorted().collect(Collectors.toList())
break

View File

@ -1,41 +1,29 @@
package net.corda.testing;
import io.fabric8.kubernetes.api.model.Pod;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
public class KubePodResult {
private final Pod createdPod;
private final CompletableFuture<Void> waiter;
private volatile Integer resultCode = 255;
private final int resultCode;
private final File output;
private volatile Collection<File> binaryResults = Collections.emptyList();
private final Collection<File> binaryResults;
KubePodResult(Pod createdPod, CompletableFuture<Void> waiter, File output) {
this.createdPod = createdPod;
this.waiter = waiter;
public KubePodResult(int resultCode, File output, Collection<File> binaryResults) {
this.resultCode = resultCode;
this.output = output;
this.binaryResults = binaryResults;
}
public void setResultCode(Integer code) {
synchronized (createdPod) {
this.resultCode = code;
}
}
public Integer getResultCode() {
synchronized (createdPod) {
return this.resultCode;
}
public int getResultCode() {
return resultCode;
}
public File getOutput() {
synchronized (createdPod) {
return output;
}
return output;
}
};
public Collection<File> getBinaryResults() {
return binaryResults;
}
}

View File

@ -152,10 +152,10 @@ public class KubesReporting extends DefaultTask {
if (!containersWithNonZeroReturnCodes.isEmpty()) {
String reportUrl = new ConsoleRenderer().asClickableFileUrl(new File(destinationDir, "index.html"));
if (shouldPrintOutput){
containersWithNonZeroReturnCodes.forEach(container -> {
containersWithNonZeroReturnCodes.forEach(podResult -> {
try {
System.out.println("\n##### CONTAINER OUTPUT START #####");
IOUtils.copy(new FileInputStream(container.getOutput()), System.out);
IOUtils.copy(new FileInputStream(podResult.getOutput()), System.out);
System.out.println("##### CONTAINER OUTPUT END #####\n");
} catch (IOException ignored) {
}

View File

@ -0,0 +1,48 @@
package net.corda.testing.retry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.Callable;
public final class Retry {
private static final Logger log = LoggerFactory.getLogger(Retry.class);
public interface RetryStrategy {
<T> T run(Callable<T> op) throws RetryException;
}
public static final class RetryException extends RuntimeException {
public RetryException(String message) {
super(message);
}
public RetryException(String message, Throwable cause) {
super(message, cause);
}
}
public static RetryStrategy fixed(int times) {
if (times < 1) throw new IllegalArgumentException();
return new RetryStrategy() {
@Override
public <T> T run(Callable<T> op) {
int run = 0;
Exception last = null;
while (run < times) {
try {
return op.call();
} catch (Exception e) {
last = e;
log.info("Exception caught: " + e.getMessage());
}
run++;
}
throw new RetryException("Operation failed " + run + " times", last);
}
};
}
}

View File

@ -21,12 +21,12 @@ public class BucketingAllocatorTest {
BucketingAllocator bucketingAllocator = new BucketingAllocator(1, Collections::emptyList);
Object task = new Object();
bucketingAllocator.addSource(() -> Arrays.asList("SomeTestingClass*", "AnotherTestingClass*"), task);
bucketingAllocator.addSource(() -> Arrays.asList("SomeTestingClass", "AnotherTestingClass"), task);
bucketingAllocator.generateTestPlan();
List<String> testsForForkAndTestTask = bucketingAllocator.getTestsForForkAndTestTask(0, task);
Assert.assertThat(testsForForkAndTestTask, IsIterableContainingInAnyOrder.containsInAnyOrder("SomeTestingClass*", "AnotherTestingClass*"));
Assert.assertThat(testsForForkAndTestTask, IsIterableContainingInAnyOrder.containsInAnyOrder("SomeTestingClass", "AnotherTestingClass"));
}
@ -37,7 +37,7 @@ public class BucketingAllocatorTest {
BucketingAllocator bucketingAllocator = new BucketingAllocator(2, Collections::emptyList);
Object task = new Object();
bucketingAllocator.addSource(() -> Arrays.asList("SomeTestingClass*", "AnotherTestingClass*"), task);
bucketingAllocator.addSource(() -> Arrays.asList("SomeTestingClass", "AnotherTestingClass"), task);
bucketingAllocator.generateTestPlan();
List<String> testsForForkOneAndTestTask = bucketingAllocator.getTestsForForkAndTestTask(0, task);
@ -48,7 +48,7 @@ public class BucketingAllocatorTest {
List<String> allTests = Stream.of(testsForForkOneAndTestTask, testsForForkTwoAndTestTask).flatMap(Collection::stream).collect(Collectors.toList());
Assert.assertThat(allTests, IsIterableContainingInAnyOrder.containsInAnyOrder("SomeTestingClass*", "AnotherTestingClass*"));
Assert.assertThat(allTests, IsIterableContainingInAnyOrder.containsInAnyOrder("SomeTestingClass", "AnotherTestingClass"));
}
}

View File

@ -51,6 +51,7 @@ import net.corda.core.utilities.toHexString
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.amqp.SerializerFactoryBuilder
import net.corda.serialization.internal.amqp.hasCordaSerializable
import net.corda.serialization.internal.amqp.registerCustomSerializers
import java.math.BigDecimal
import java.security.PublicKey
import java.security.cert.CertPath
@ -116,8 +117,10 @@ private class CordaSerializableClassIntrospector(private val context: Module.Set
}
private class CordaSerializableBeanSerializerModifier : BeanSerializerModifier() {
// We need to pass in a SerializerFactory when scanning for properties, but don't actually do any serialisation so any will do.
private val serializerFactory = SerializerFactoryBuilder.build(AllWhitelist, javaClass.classLoader)
// We need to pass in a SerializerFactory when scanning for properties, but don't actually do any serialisation so any will do.
private val serializerFactory = SerializerFactoryBuilder.build(AllWhitelist, javaClass.classLoader).also {
registerCustomSerializers(it)
}
override fun changeProperties(config: SerializationConfig,
beanDesc: BeanDescription,
@ -125,7 +128,9 @@ private class CordaSerializableBeanSerializerModifier : BeanSerializerModifier()
val beanClass = beanDesc.beanClass
if (hasCordaSerializable(beanClass) && !SerializeAsToken::class.java.isAssignableFrom(beanClass)) {
val typeInformation = serializerFactory.getTypeInformation(beanClass)
val propertyNames = typeInformation.propertiesOrEmptyMap.mapNotNull { if (it.value.isCalculated) null else it.key }
val propertyNames = typeInformation.propertiesOrEmptyMap.mapNotNull {
if (it.value.isCalculated) null else it.key
}
beanProperties.removeIf { it.name !in propertyNames }
}
return beanProperties

View File

@ -3,10 +3,15 @@ package net.corda.client.jfx.model
import javafx.beans.property.SimpleObjectProperty
import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCConnection
import net.corda.client.rpc.GracefulReconnect
import net.corda.core.contracts.ContractState
import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.Party
import net.corda.core.messaging.*
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.StateMachineInfo
import net.corda.core.messaging.StateMachineTransactionMapping
import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.messaging.vaultTrackBy
import net.corda.core.node.services.NetworkMapCache.MapChange
import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.DEFAULT_PAGE_NUM
@ -72,7 +77,7 @@ class NodeMonitorModel : AutoCloseable {
* TODO provide an unsubscribe mechanism
*/
fun register(nodeHostAndPort: NetworkHostAndPort, username: String, password: String) {
rpc = CordaRPCClient(nodeHostAndPort).start(username, password)
rpc = CordaRPCClient(nodeHostAndPort).start(username, password, GracefulReconnect())
proxyObservable.value = rpc.proxy
// Vault snapshot (force single page load with MAX_PAGE_SIZE) + updates

View File

@ -4,6 +4,7 @@ import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCClientConfiguration
import net.corda.client.rpc.CordaRPCClientTest
import net.corda.client.rpc.GracefulReconnect
import net.corda.client.rpc.MaxRpcRetryException
import net.corda.client.rpc.internal.ReconnectingCordaRPCOps
import net.corda.core.messaging.startTrackedFlow
import net.corda.core.utilities.NetworkHostAndPort
@ -21,9 +22,11 @@ import net.corda.testing.driver.internal.incrementalPortAllocation
import net.corda.testing.node.User
import net.corda.testing.node.internal.FINANCE_CORDAPPS
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@ -39,7 +42,7 @@ class CordaRPCClientReconnectionTest {
@Test
fun `rpc client calls and returned observables continue working when the server crashes and restarts`() {
driver(DriverParameters(cordappsForAllNodes = FINANCE_CORDAPPS, startNodesInProcess = false, inMemoryDB = false)) {
driver(DriverParameters(cordappsForAllNodes = FINANCE_CORDAPPS)) {
val latch = CountDownLatch(2)
val address = NetworkHostAndPort("localhost", portAllocator.nextPort())
@ -52,9 +55,7 @@ class CordaRPCClientReconnectionTest {
}
val node = startNode()
val client = CordaRPCClient(node.rpcAddress, CordaRPCClientConfiguration.DEFAULT.copy(
maxReconnectAttempts = 5
))
val client = CordaRPCClient(node.rpcAddress)
(client.start(rpcUser.username, rpcUser.password, gracefulReconnect = gracefulReconnect)).use {
val rpcOps = it.proxy as ReconnectingCordaRPCOps
@ -79,7 +80,7 @@ class CordaRPCClientReconnectionTest {
@Test
fun `a client can successfully unsubscribe a reconnecting observable`() {
driver(DriverParameters(cordappsForAllNodes = FINANCE_CORDAPPS, startNodesInProcess = false, inMemoryDB = false)) {
driver(DriverParameters(cordappsForAllNodes = FINANCE_CORDAPPS)) {
val latch = CountDownLatch(2)
val address = NetworkHostAndPort("localhost", portAllocator.nextPort())
@ -92,9 +93,7 @@ class CordaRPCClientReconnectionTest {
}
val node = startNode()
val client = CordaRPCClient(node.rpcAddress, CordaRPCClientConfiguration.DEFAULT.copy(
maxReconnectAttempts = 5
))
val client = CordaRPCClient(node.rpcAddress)
(client.start(rpcUser.username, rpcUser.password, gracefulReconnect = gracefulReconnect)).use {
val rpcOps = it.proxy as ReconnectingCordaRPCOps
@ -119,7 +118,7 @@ class CordaRPCClientReconnectionTest {
@Test
fun `rpc client calls and returned observables continue working when there is failover between servers`() {
driver(DriverParameters(cordappsForAllNodes = FINANCE_CORDAPPS, startNodesInProcess = false, inMemoryDB = false)) {
driver(DriverParameters(cordappsForAllNodes = FINANCE_CORDAPPS)) {
val latch = CountDownLatch(2)
fun startNode(address: NetworkHostAndPort): NodeHandle {
@ -133,9 +132,7 @@ class CordaRPCClientReconnectionTest {
val addresses = listOf(NetworkHostAndPort("localhost", portAllocator.nextPort()), NetworkHostAndPort("localhost", portAllocator.nextPort()))
val node = startNode(addresses[0])
val client = CordaRPCClient(addresses, CordaRPCClientConfiguration.DEFAULT.copy(
maxReconnectAttempts = 5
))
val client = CordaRPCClient(addresses)
(client.start(rpcUser.username, rpcUser.password, gracefulReconnect = gracefulReconnect)).use {
val rpcOps = it.proxy as ReconnectingCordaRPCOps
@ -157,4 +154,33 @@ class CordaRPCClientReconnectionTest {
}
}
}
@Test
fun `an RPC call fails, when the maximum number of attempts is exceeded`() {
driver(DriverParameters(cordappsForAllNodes = emptyList())) {
val address = NetworkHostAndPort("localhost", portAllocator.nextPort())
fun startNode(): NodeHandle {
return startNode(
providedName = CHARLIE_NAME,
rpcUsers = listOf(CordaRPCClientTest.rpcUser),
customOverrides = mapOf("rpcSettings.address" to address.toString())
).getOrThrow()
}
val node = startNode()
val client = CordaRPCClient(node.rpcAddress)
(client.start(rpcUser.username, rpcUser.password, gracefulReconnect = GracefulReconnect(maxAttempts = 1))).use {
val rpcOps = it.proxy as ReconnectingCordaRPCOps
node.stop()
thread { startNode() }
assertThatThrownBy { rpcOps.networkParameters }
.isInstanceOf(MaxRpcRetryException::class.java)
}
}
}
}

View File

@ -282,10 +282,13 @@ open class CordaRPCClientConfiguration @JvmOverloads constructor(
*
* @param onDisconnect implement this callback to perform logic when the RPC disconnects on connection disconnect
* @param onReconnect implement this callback to perform logic when the RPC has reconnected after connection disconnect
* @param maxAttempts the maximum number of attempts per each individual RPC call. A negative number indicates infinite number of retries.
* The default value is 5.
*/
class GracefulReconnect(val onDisconnect: () -> Unit = {}, val onReconnect: () -> Unit = {}) {
constructor(onDisconnect: Runnable, onReconnect: Runnable ) :
this(onDisconnect = { onDisconnect.run() }, onReconnect = { onReconnect.run() })
class GracefulReconnect(val onDisconnect: () -> Unit = {}, val onReconnect: () -> Unit = {}, val maxAttempts: Int = 5) {
@JvmOverloads
constructor(onDisconnect: Runnable, onReconnect: Runnable, maxAttempts: Int = 5) :
this(onDisconnect = { onDisconnect.run() }, onReconnect = { onReconnect.run() }, maxAttempts = maxAttempts)
}
/**

View File

@ -9,6 +9,14 @@ open class RPCException(message: String?, cause: Throwable?) : CordaRuntimeExcep
constructor(msg: String) : this(msg, null)
}
/**
* Thrown to indicate an RPC operation has been retried for the [maxNumberOfRetries] unsuccessfully.
* @param maxNumberOfRetries the number of retries that had been performed.
* @param cause the cause of the last failed attempt.
*/
class MaxRpcRetryException(maxNumberOfRetries: Int, cause: Throwable?):
RPCException("Max number of retries ($maxNumberOfRetries) was reached.", cause)
/**
* Signals that the underlying [RPCConnection] dropped.
*/

View File

@ -55,7 +55,7 @@ class ReconnectingCordaRPCOps private constructor(
username: String,
password: String,
rpcConfiguration: CordaRPCClientConfiguration,
gracefulReconnect: GracefulReconnect? = null,
gracefulReconnect: GracefulReconnect = GracefulReconnect(),
sslConfiguration: ClientRpcSslOptions? = null,
classLoader: ClassLoader? = null,
observersPool: ExecutorService
@ -117,7 +117,7 @@ class ReconnectingCordaRPCOps private constructor(
val rpcConfiguration: CordaRPCClientConfiguration,
val sslConfiguration: ClientRpcSslOptions? = null,
val classLoader: ClassLoader?,
val gracefulReconnect: GracefulReconnect? = null,
val gracefulReconnect: GracefulReconnect = GracefulReconnect(),
val observersPool: ExecutorService
) : RPCConnection<CordaRPCOps> {
private var currentRPCConnection: CordaRPCConnection? = null
@ -147,13 +147,13 @@ class ReconnectingCordaRPCOps private constructor(
// First one to get here gets to do all the reconnect logic, including calling onDisconnect and onReconnect. This makes sure
// that they're only called once per reconnect.
currentState = DIED
gracefulReconnect?.onDisconnect?.invoke()
gracefulReconnect.onDisconnect.invoke()
//TODO - handle error cases
log.error("Reconnecting to ${this.nodeHostAndPorts} due to error: ${e.message}")
log.debug("", e)
connect()
previousConnection?.forceClose()
gracefulReconnect?.onReconnect?.invoke()
gracefulReconnect.onReconnect.invoke()
}
/**
* Called on external error.
@ -249,9 +249,16 @@ class ReconnectingCordaRPCOps private constructor(
}
}
private fun doInvoke(method: Method, args: Array<out Any>?): Any? {
// will stop looping when [method.invoke] succeeds
while (true) {
/**
* This method retries the invoked operation in a loop by re-establishing the connection when there is a problem
* and checking if the [maxNumberOfAttempts] has been exhausted.
*
* A negative number for [maxNumberOfAttempts] means an unlimited number of retries will be performed.
*/
private fun doInvoke(method: Method, args: Array<out Any>?, maxNumberOfAttempts: Int): Any? {
var remainingAttempts = maxNumberOfAttempts
var lastException: Throwable? = null
while (remainingAttempts != 0) {
try {
log.debug { "Invoking RPC $method..." }
return method.invoke(reconnectingRPCConnection.proxy, *(args ?: emptyArray())).also {
@ -280,15 +287,20 @@ class ReconnectingCordaRPCOps private constructor(
checkIfIsStartFlow(method, e)
}
}
lastException = e.targetException
remainingAttempts--
}
}
throw MaxRpcRetryException(maxNumberOfAttempts, lastException)
}
override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {
return when (method.returnType) {
DataFeed::class.java -> {
// Intercept the data feed methods and return a ReconnectingObservable instance
val initialFeed: DataFeed<Any, Any?> = uncheckedCast(doInvoke(method, args))
val initialFeed: DataFeed<Any, Any?> = uncheckedCast(doInvoke(method, args,
reconnectingRPCConnection.gracefulReconnect.maxAttempts))
val observable = ReconnectingObservable(reconnectingRPCConnection, initialFeed) {
// This handles reconnecting and creates new feeds.
uncheckedCast(this.invoke(reconnectingRPCConnection.proxy, method, args))
@ -296,7 +308,7 @@ class ReconnectingCordaRPCOps private constructor(
initialFeed.copy(updates = observable)
}
// TODO - add handlers for Observable return types.
else -> doInvoke(method, args)
else -> doInvoke(method, args, reconnectingRPCConnection.gracefulReconnect.maxAttempts)
}
}
}

View File

@ -6,6 +6,8 @@ import net.corda.core.CordaInternal
import net.corda.core.DeleteForDJVM
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.*
@ -120,14 +122,18 @@ abstract class FlowLogic<out T> {
* is routed depends on the [Destination] type, including whether this call does any initial communication.
*/
@Suspendable
fun initiateFlow(destination: Destination): FlowSession = stateMachine.initiateFlow(destination)
fun initiateFlow(destination: Destination): FlowSession {
require(destination is Party || destination is AnonymousParty) { "Unsupported destination type ${destination.javaClass.name}" }
return stateMachine.initiateFlow(destination, serviceHub.identityService.wellKnownPartyFromAnonymous(destination as AbstractParty)
?: throw IllegalArgumentException("Could not resolve destination: $destination"))
}
/**
* Creates a communication session with [party]. Subsequently you may send/receive using this session object. Note
* that this function does not communicate in itself, the counter-flow will be kicked off by the first send/receive.
*/
@Suspendable
fun initiateFlow(party: Party): FlowSession = stateMachine.initiateFlow(party)
fun initiateFlow(party: Party): FlowSession = stateMachine.initiateFlow(party, party)
/**
* Specifies the identity, with certificate, to use for this flow. This will be one of the multiple identities that

View File

@ -21,6 +21,9 @@ import java.security.SignatureException
* Please note that it will *not* store the transaction to the vault unless that is explicitly requested and checkSufficientSignatures is true.
* Setting statesToRecord to anything else when checkSufficientSignatures is false will *not* update the vault.
*
* Attention: At the moment, this flow receives a [SignedTransaction] first thing and then proceeds by invoking a [ResolveTransactionsFlow] subflow.
* This is used as a criterion to identify cases, where a counterparty has failed notarising a transact
*
* @property otherSideSession session to the other side which is calling [SendTransactionFlow].
* @property checkSufficientSignatures if true checks all required signatures are present. See [SignedTransaction.verify].
* @property statesToRecord which transaction states should be recorded in the vault, if any.

View File

@ -18,7 +18,7 @@ interface FlowStateMachine<FLOWRETURN> {
fun <SUSPENDRETURN : Any> suspend(ioRequest: FlowIORequest<SUSPENDRETURN>, maySkipCheckpoint: Boolean): SUSPENDRETURN
@Suspendable
fun initiateFlow(destination: Destination): FlowSession
fun initiateFlow(destination: Destination, wellKnownParty: Party): FlowSession
fun checkFlowPermission(permissionName: String, extraAuditData: Map<String, String>)

View File

@ -85,7 +85,12 @@ val _inheritableContextSerializationEnv = InheritableThreadLocalToggleField<Seri
}
}
private val serializationEnvFields = listOf(_nodeSerializationEnv, _driverSerializationEnv, _contextSerializationEnv, _inheritableContextSerializationEnv)
private val serializationEnvFields = listOf(
_nodeSerializationEnv,
_driverSerializationEnv,
_contextSerializationEnv,
_inheritableContextSerializationEnv
)
val _allEnabledSerializationEnvs: List<Pair<String, SerializationEnvironment>>
get() = serializationEnvFields.mapNotNull { it.get()?.let { env -> Pair(it.name, env) } }
@ -94,7 +99,8 @@ val effectiveSerializationEnv: SerializationEnvironment
get() {
return _allEnabledSerializationEnvs.let {
checkNotNull(it.singleOrNull()?.second) {
"Expected exactly 1 of {${serializationEnvFields.joinToString(", ") { it.name }}} but got: {${it.joinToString(", ") { it.first }}}"
"Expected exactly 1 of {${serializationEnvFields.joinToString(", ") { it.name }}} " +
"but got: {${it.joinToString(", ") { it.first }}}"
}
}
}

View File

@ -0,0 +1,6 @@
#Detekt 1.0.1
#Gradle task detektBaseline would always fail due to the fact that the existing baseline would not be taken into
#account so existing issues flare up. We set the max issues allowed to a high number to force detekt to pass when
#generating a baseline.
build:
maxIssues: 9999999

View File

@ -691,8 +691,6 @@
<ID>LongParameterList:Driver.kt$DriverParameters$( isDebug: Boolean, driverDirectory: Path, portAllocation: PortAllocation, debugPortAllocation: PortAllocation, systemProperties: Map&lt;String, String&gt;, useTestClock: Boolean, startNodesInProcess: Boolean, waitForAllNodesToFinish: Boolean, notarySpecs: List&lt;NotarySpec&gt;, extraCordappPackagesToScan: List&lt;String&gt;, jmxPolicy: JmxPolicy, networkParameters: NetworkParameters, cordappsForAllNodes: Set&lt;TestCordapp&gt;? )</ID>
<ID>LongParameterList:DriverDSL.kt$DriverDSL$( defaultParameters: NodeParameters = NodeParameters(), providedName: CordaX500Name? = defaultParameters.providedName, rpcUsers: List&lt;User&gt; = defaultParameters.rpcUsers, verifierType: VerifierType = defaultParameters.verifierType, customOverrides: Map&lt;String, Any?&gt; = defaultParameters.customOverrides, startInSameProcess: Boolean? = defaultParameters.startInSameProcess, maximumHeapSize: String = defaultParameters.maximumHeapSize )</ID>
<ID>LongParameterList:DriverDSL.kt$DriverDSL$( defaultParameters: NodeParameters = NodeParameters(), providedName: CordaX500Name? = defaultParameters.providedName, rpcUsers: List&lt;User&gt; = defaultParameters.rpcUsers, verifierType: VerifierType = defaultParameters.verifierType, customOverrides: Map&lt;String, Any?&gt; = defaultParameters.customOverrides, startInSameProcess: Boolean? = defaultParameters.startInSameProcess, maximumHeapSize: String = defaultParameters.maximumHeapSize, logLevelOverride: String? = defaultParameters.logLevelOverride )</ID>
<ID>LongParameterList:DriverDSLImpl.kt$( isDebug: Boolean = DriverParameters().isDebug, driverDirectory: Path = DriverParameters().driverDirectory, portAllocation: PortAllocation = DriverParameters().portAllocation, debugPortAllocation: PortAllocation = DriverParameters().debugPortAllocation, systemProperties: Map&lt;String, String&gt; = DriverParameters().systemProperties, useTestClock: Boolean = DriverParameters().useTestClock, startNodesInProcess: Boolean = DriverParameters().startNodesInProcess, extraCordappPackagesToScan: List&lt;String&gt; = @Suppress("DEPRECATION") DriverParameters().extraCordappPackagesToScan, waitForAllNodesToFinish: Boolean = DriverParameters().waitForAllNodesToFinish, notarySpecs: List&lt;NotarySpec&gt; = DriverParameters().notarySpecs, jmxPolicy: JmxPolicy = DriverParameters().jmxPolicy, networkParameters: NetworkParameters = DriverParameters().networkParameters, compatibilityZone: CompatibilityZoneParams? = null, notaryCustomOverrides: Map&lt;String, Any?&gt; = DriverParameters().notaryCustomOverrides, inMemoryDB: Boolean = DriverParameters().inMemoryDB, cordappsForAllNodes: Collection&lt;TestCordappInternal&gt;? = null, dsl: DriverDSLImpl.() -&gt; A )</ID>
<ID>LongParameterList:DriverDSLImpl.kt$DriverDSLImpl.Companion$( config: NodeConfig, quasarJarPath: String, debugPort: Int?, overriddenSystemProperties: Map&lt;String, String&gt;, maximumHeapSize: String, logLevelOverride: String?, vararg extraCmdLineFlag: String )</ID>
<ID>LongParameterList:DummyFungibleContract.kt$DummyFungibleContract$(inputs: List&lt;State&gt;, outputs: List&lt;State&gt;, tx: LedgerTransaction, issueCommand: CommandWithParties&lt;Commands.Issue&gt;, currency: Currency, issuer: PartyAndReference)</ID>
<ID>LongParameterList:IRS.kt$FloatingRatePaymentEvent$(date: LocalDate = this.date, accrualStartDate: LocalDate = this.accrualStartDate, accrualEndDate: LocalDate = this.accrualEndDate, dayCountBasisDay: DayCountBasisDay = this.dayCountBasisDay, dayCountBasisYear: DayCountBasisYear = this.dayCountBasisYear, fixingDate: LocalDate = this.fixingDate, notional: Amount&lt;Currency&gt; = this.notional, rate: Rate = this.rate)</ID>
<ID>LongParameterList:IRS.kt$InterestRateSwap$(floatingLeg: FloatingLeg, fixedLeg: FixedLeg, calculation: Calculation, common: Common, oracle: Party, notary: Party)</ID>
@ -729,8 +727,6 @@
<ID>LongParameterList:ParametersUtilities.kt$( notaries: List&lt;NotaryInfo&gt; = emptyList(), minimumPlatformVersion: Int = 1, modifiedTime: Instant = Instant.now(), maxMessageSize: Int = 10485760, // TODO: Make this configurable and consistence across driver, bootstrapper, demobench and NetworkMapServer maxTransactionSize: Int = maxMessageSize * 50, whitelistedContractImplementations: Map&lt;String, List&lt;AttachmentId&gt;&gt; = emptyMap(), epoch: Int = 1, eventHorizon: Duration = 30.days, packageOwnership: Map&lt;String, PublicKey&gt; = emptyMap() )</ID>
<ID>LongParameterList:PersistentUniquenessProvider.kt$PersistentUniquenessProvider$( states: List&lt;StateRef&gt;, txId: SecureHash, callerIdentity: Party, requestSignature: NotarisationRequestSignature, timeWindow: TimeWindow?, references: List&lt;StateRef&gt; )</ID>
<ID>LongParameterList:PhysicalLocationStructures.kt$WorldCoordinate$(screenWidth: Double, screenHeight: Double, topLatitude: Double, bottomLatitude: Double, leftLongitude: Double, rightLongitude: Double)</ID>
<ID>LongParameterList:ProcessUtilities.kt$ProcessUtilities$( arguments: List&lt;String&gt;, classPath: List&lt;String&gt; = defaultClassPath, workingDirectory: Path? = null, jdwpPort: Int? = null, extraJvmArguments: List&lt;String&gt; = emptyList(), maximumHeapSize: String? = null )</ID>
<ID>LongParameterList:ProcessUtilities.kt$ProcessUtilities$( className: String, arguments: List&lt;String&gt;, classPath: List&lt;String&gt; = defaultClassPath, workingDirectory: Path? = null, jdwpPort: Int? = null, extraJvmArguments: List&lt;String&gt; = emptyList(), maximumHeapSize: String? = null )</ID>
<ID>LongParameterList:QueryCriteria.kt$QueryCriteria.FungibleAssetQueryCriteria$( participants: List&lt;AbstractParty&gt;? = this.participants, owner: List&lt;AbstractParty&gt;? = this.owner, quantity: ColumnPredicate&lt;Long&gt;? = this.quantity, issuer: List&lt;AbstractParty&gt;? = this.issuer, issuerRef: List&lt;OpaqueBytes&gt;? = this.issuerRef, status: Vault.StateStatus = this.status, contractStateTypes: Set&lt;Class&lt;out ContractState&gt;&gt;? = this.contractStateTypes )</ID>
<ID>LongParameterList:QueryCriteria.kt$QueryCriteria.FungibleAssetQueryCriteria$( participants: List&lt;AbstractParty&gt;? = this.participants, owner: List&lt;AbstractParty&gt;? = this.owner, quantity: ColumnPredicate&lt;Long&gt;? = this.quantity, issuer: List&lt;AbstractParty&gt;? = this.issuer, issuerRef: List&lt;OpaqueBytes&gt;? = this.issuerRef, status: Vault.StateStatus = this.status, contractStateTypes: Set&lt;Class&lt;out ContractState&gt;&gt;? = this.contractStateTypes, relevancyStatus: Vault.RelevancyStatus = this.relevancyStatus )</ID>
<ID>LongParameterList:QueryCriteria.kt$QueryCriteria.LinearStateQueryCriteria$( participants: List&lt;AbstractParty&gt;? = this.participants, uuid: List&lt;UUID&gt;? = this.uuid, externalId: List&lt;String&gt;? = this.externalId, status: Vault.StateStatus = this.status, contractStateTypes: Set&lt;Class&lt;out ContractState&gt;&gt;? = this.contractStateTypes, relevancyStatus: Vault.RelevancyStatus = this.relevancyStatus )</ID>
@ -738,7 +734,6 @@
<ID>LongParameterList:QueryCriteria.kt$QueryCriteria.VaultQueryCriteria$( status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED, contractStateTypes: Set&lt;Class&lt;out ContractState&gt;&gt;? = null, stateRefs: List&lt;StateRef&gt;? = null, notary: List&lt;AbstractParty&gt;? = null, softLockingCondition: SoftLockingCondition? = null, timeCondition: TimeCondition? = null, relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL, constraintTypes: Set&lt;Vault.ConstraintInfo.Type&gt; = emptySet(), constraints: Set&lt;Vault.ConstraintInfo&gt; = emptySet(), participants: List&lt;AbstractParty&gt;? = null, externalIds: List&lt;UUID&gt; = emptyList() )</ID>
<ID>LongParameterList:QueryCriteria.kt$QueryCriteria.VaultQueryCriteria$( status: Vault.StateStatus = this.status, contractStateTypes: Set&lt;Class&lt;out ContractState&gt;&gt;? = this.contractStateTypes, stateRefs: List&lt;StateRef&gt;? = this.stateRefs, notary: List&lt;AbstractParty&gt;? = this.notary, softLockingCondition: SoftLockingCondition? = this.softLockingCondition, timeCondition: TimeCondition? = this.timeCondition )</ID>
<ID>LongParameterList:RPCClient.kt$RPCClient$( rpcOpsClass: Class&lt;I&gt;, username: String, password: String, externalTrace: Trace? = null, impersonatedActor: Actor? = null, targetLegalIdentity: CordaX500Name? = null )</ID>
<ID>LongParameterList:RPCDriver.kt$( isDebug: Boolean = false, driverDirectory: Path = Paths.get("build") / "rpc-driver" / getTimestampAsDirectoryName(), portAllocation: PortAllocation = globalPortAllocation, debugPortAllocation: PortAllocation = globalDebugPortAllocation, systemProperties: Map&lt;String, String&gt; = emptyMap(), useTestClock: Boolean = false, startNodesInProcess: Boolean = false, waitForNodesToFinish: Boolean = false, extraCordappPackagesToScan: List&lt;String&gt; = emptyList(), notarySpecs: List&lt;NotarySpec&gt; = emptyList(), externalTrace: Trace? = null, @Suppress("DEPRECATION") jmxPolicy: JmxPolicy = JmxPolicy(), networkParameters: NetworkParameters = testNetworkParameters(), notaryCustomOverrides: Map&lt;String, Any?&gt; = emptyMap(), inMemoryDB: Boolean = true, cordappsForAllNodes: Collection&lt;TestCordappInternal&gt;? = null, dsl: RPCDriverDSL.() -&gt; A )</ID>
<ID>LongParameterList:RPCDriver.kt$RPCDriverDSL$( rpcUser: User = rpcTestUser, nodeLegalName: CordaX500Name = fakeNodeLegalName, configuration: RPCServerConfiguration = RPCServerConfiguration.DEFAULT, listOps: List&lt;I&gt;, brokerHandle: RpcBrokerHandle, queueDrainTimeout: Duration = 5.seconds )</ID>
<ID>LongParameterList:RPCDriver.kt$RPCDriverDSL$( rpcUser: User = rpcTestUser, nodeLegalName: CordaX500Name = fakeNodeLegalName, configuration: RPCServerConfiguration = RPCServerConfiguration.DEFAULT, ops: I, brokerHandle: RpcBrokerHandle, queueDrainTimeout: Duration = 5.seconds )</ID>
<ID>LongParameterList:RPCDriver.kt$RPCDriverDSL$( rpcUser: User = rpcTestUser, nodeLegalName: CordaX500Name = fakeNodeLegalName, maxFileSize: Int = MAX_MESSAGE_SIZE, maxBufferedBytesPerClient: Long = 10L * MAX_MESSAGE_SIZE, configuration: RPCServerConfiguration = RPCServerConfiguration.DEFAULT, ops: I, queueDrainTimeout: Duration = 5.seconds )</ID>
@ -1320,11 +1315,8 @@
<ID>MaxLineLength:AMQPClientSerializationScheme.kt$AMQPClientSerializationScheme.Companion$fun createSerializationEnv(classLoader: ClassLoader? = null, customSerializers: Set&lt;SerializationCustomSerializer&lt;*, *&gt;&gt; = emptySet(), serializationWhitelists: Set&lt;SerializationWhitelist&gt; = emptySet(), serializerFactoriesForContexts: MutableMap&lt;SerializationFactoryCacheKey, SerializerFactory&gt; = AccessOrderLinkedHashMap&lt;SerializationFactoryCacheKey, SerializerFactory&gt;(128).toSynchronised()): SerializationEnvironment</ID>
<ID>MaxLineLength:AMQPClientSerializationScheme.kt$AMQPClientSerializationScheme.Companion$nodeSerializationEnv = createSerializationEnv(classLoader, customSerializers, serializationWhitelists, serializerFactoriesForContexts)</ID>
<ID>MaxLineLength:AMQPClientSerializationScheme.kt$AMQPClientSerializationScheme.Companion$registerScheme(AMQPClientSerializationScheme(customSerializers, serializationWhitelists, serializerFactoriesForContexts))</ID>
<ID>MaxLineLength:AMQPSerializationScheme.kt$AbstractAMQPSerializationScheme$@DeleteForDJVM constructor(cordapps: List&lt;Cordapp&gt;) : this(cordapps.customSerializers, cordapps.serializationWhitelists, AccessOrderLinkedHashMap&lt;SerializationFactoryCacheKey, SerializerFactory&gt;(128).toSynchronised())</ID>
<ID>MaxLineLength:AMQPSerializationScheme.kt$AbstractAMQPSerializationScheme$private</ID>
<ID>MaxLineLength:AMQPSerializationScheme.kt$AbstractAMQPSerializationScheme$val key = SerializationFactoryCacheKey(context.whitelist, context.deserializationClassLoader, context.preventDataLoss, context.customSerializers)</ID>
<ID>MaxLineLength:AMQPSerializationScheme.kt$AbstractAMQPSerializationScheme${ // This is a hack introduced in version 3 to fix a spring boot issue - CORDA-1747. // It breaks the shell because it overwrites the CordappClassloader with the system classloader that doesn't know about any CorDapps. // In case a spring boot serialization issue with generics is found, a better solution needs to be found to address it. // var contextToUse = context // if (context.useCase == SerializationContext.UseCase.RPCClient) { // contextToUse = context.withClassLoader(getContextClassLoader()) // } val serializerFactory = getSerializerFactory(context) return DeserializationInput(serializerFactory).deserialize(byteSequence, clazz, context) }</ID>
<ID>MaxLineLength:AMQPSerializationScheme.kt$AbstractAMQPSerializationScheme${ with(factory) { register(publicKeySerializer) register(net.corda.serialization.internal.amqp.custom.PrivateKeySerializer) register(net.corda.serialization.internal.amqp.custom.ThrowableSerializer(this)) register(net.corda.serialization.internal.amqp.custom.BigDecimalSerializer) register(net.corda.serialization.internal.amqp.custom.BigIntegerSerializer) register(net.corda.serialization.internal.amqp.custom.CurrencySerializer) register(net.corda.serialization.internal.amqp.custom.OpaqueBytesSubSequenceSerializer(this)) register(net.corda.serialization.internal.amqp.custom.InstantSerializer(this)) register(net.corda.serialization.internal.amqp.custom.DurationSerializer(this)) register(net.corda.serialization.internal.amqp.custom.LocalDateSerializer(this)) register(net.corda.serialization.internal.amqp.custom.LocalDateTimeSerializer(this)) register(net.corda.serialization.internal.amqp.custom.LocalTimeSerializer(this)) register(net.corda.serialization.internal.amqp.custom.ZonedDateTimeSerializer(this)) register(net.corda.serialization.internal.amqp.custom.ZoneIdSerializer(this)) register(net.corda.serialization.internal.amqp.custom.OffsetTimeSerializer(this)) register(net.corda.serialization.internal.amqp.custom.OffsetDateTimeSerializer(this)) register(net.corda.serialization.internal.amqp.custom.OptionalSerializer(this)) register(net.corda.serialization.internal.amqp.custom.YearSerializer(this)) register(net.corda.serialization.internal.amqp.custom.YearMonthSerializer(this)) register(net.corda.serialization.internal.amqp.custom.MonthDaySerializer(this)) register(net.corda.serialization.internal.amqp.custom.PeriodSerializer(this)) register(net.corda.serialization.internal.amqp.custom.ClassSerializer(this)) register(net.corda.serialization.internal.amqp.custom.X509CertificateSerializer) register(net.corda.serialization.internal.amqp.custom.X509CRLSerializer) register(net.corda.serialization.internal.amqp.custom.CertPathSerializer(this)) register(net.corda.serialization.internal.amqp.custom.StringBufferSerializer) register(net.corda.serialization.internal.amqp.custom.InputStreamSerializer) register(net.corda.serialization.internal.amqp.custom.BitSetSerializer(this)) register(net.corda.serialization.internal.amqp.custom.EnumSetSerializer(this)) register(net.corda.serialization.internal.amqp.custom.ContractAttachmentSerializer(this)) registerNonDeterministicSerializers(factory) } // This step is registering custom serializers, which have been added after node initialisation (i.e. via attachments during transaction verification). // Note: the order between the registration of customSerializers and cordappCustomSerializers must be preserved as-is. The reason is the following: // Currently, the serialization infrastructure does not support multiple versions of a class (the first one that is registered dominates). // As a result, when inside a context with attachments class loader, we prioritize serializers loaded on-demand from attachments to serializers that had been // loaded during node initialisation, by scanning the cordapps folder. context.customSerializers.forEach { customSerializer -&gt; factory.registerExternal(CorDappCustomSerializer(customSerializer, factory)) } cordappCustomSerializers.forEach { customSerializer -&gt; factory.registerExternal(CorDappCustomSerializer(customSerializer, factory)) } context.properties[ContextPropertyKeys.SERIALIZERS]?.apply { uncheckedCast&lt;Any, List&lt;CustomSerializer&lt;out Any&gt;&gt;&gt;(this).forEach { factory.register(it) } } }</ID>
<ID>MaxLineLength:AMQPSerializerFactories.kt$ fun createClassCarpenter(context: SerializationContext): ClassCarpenter</ID>
<ID>MaxLineLength:AMQPServer.kt$AMQPServer$server.group(bossGroup, workerGroup).channel(NioServerSocketChannel::class.java).option(ChannelOption.SO_BACKLOG, 100).handler(LoggingHandler(LogLevel.INFO)).childHandler(ServerChannelInitializer(this))</ID>
<ID>MaxLineLength:AMQPServer.kt$AMQPServer$val channelFuture = server.bind(hostName, port).sync() // block/throw here as better to know we failed to claim port than carry on</ID>
@ -1395,7 +1387,6 @@
<ID>MaxLineLength:AddressBindingFailureTests.kt$AddressBindingFailureTests$@Test fun `rpc admin address`()</ID>
<ID>MaxLineLength:AddressBindingFailureTests.kt$AddressBindingFailureTests$assertThat(exception.addresses).contains(address).withFailMessage("Expected addresses to contain $address but was ${exception.addresses}.")</ID>
<ID>MaxLineLength:AddressBindingFailureTests.kt$AddressBindingFailureTests$assertThatThrownBy { startNode(customOverrides = overrides(address)).getOrThrow() }</ID>
<ID>MaxLineLength:AddressBindingFailureTests.kt$AddressBindingFailureTests$driver</ID>
<ID>MaxLineLength:AliasPrivateKeyTest.kt$AliasPrivateKeyTest$signingCertStore.query { setPrivateKey(alias, aliasPrivateKey, listOf(NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.ECDSAR1_CERT), "entrypassword") }</ID>
<ID>MaxLineLength:AliasPrivateKeyTest.kt$AliasPrivateKeyTest$val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory, "keystorepass").get(createNew = true)</ID>
<ID>MaxLineLength:AliasPrivateKeyTest.kt$AliasPrivateKeyTest${ val alias = "01234567890" val aliasPrivateKey = AliasPrivateKey(alias) val certificatesDirectory = tempFolder.root.toPath() val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory, "keystorepass").get(createNew = true) signingCertStore.query { setPrivateKey(alias, aliasPrivateKey, listOf(NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.ECDSAR1_CERT), "entrypassword") } // We can retrieve the certificate. assertTrue { signingCertStore.contains(alias) } // We can retrieve the certificate. assertEquals(NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.ECDSAR1_CERT, signingCertStore[alias]) // Although we can store an AliasPrivateKey, we cannot retrieve it. But, it's fine as we use certStore for storing/handling certs only. assertThatIllegalArgumentException().isThrownBy { signingCertStore.query { getPrivateKey(alias, "entrypassword") } }.withMessage("Unrecognised algorithm: 1.3.6.1.4.1.50530.1.2") }</ID>
@ -1542,7 +1533,6 @@
<ID>MaxLineLength:BaseTransactions.kt$FullTransaction$"Notary ($notaryParty) specified by the transaction is not on the network parameter whitelist: [${notaryWhitelist.joinToString()}]"</ID>
<ID>MaxLineLength:BaseTransactions.kt$FullTransaction$/** * Network parameters that were in force when this transaction was created. Resolved from the hash of network parameters on the corresponding * wire transaction. */ abstract val networkParameters: NetworkParameters?</ID>
<ID>MaxLineLength:BasicHSMKeyManagementService.kt$BasicHSMKeyManagementService$require(it.private is AliasPrivateKey) { "${this.javaClass.name} supports AliasPrivateKeys only, but ${it.private.algorithm} key was found" }</ID>
<ID>MaxLineLength:BitSetSerializer.kt$BitSetSerializer : Proxy</ID>
<ID>MaxLineLength:BlobInspector.kt$BlobInspector$@Option(names = ["--input-format"], paramLabel = "type", description = ["Input format. If the file can't be decoded with the given value it's auto-detected, so you should never normally need to specify this. Possible values: [BINARY, HEX, BASE64]"])</ID>
<ID>MaxLineLength:BootTests.kt$BootTests$val logFile = logFolder.list { it.filter { a -&gt; a.isRegularFile() &amp;&amp; a.fileName.toString().startsWith("node") }.findFirst().get() }</ID>
<ID>MaxLineLength:BootstrapperView.kt$BootstrapperView$return Pair(mapOf(Constants.REGION_ARG_NAME to ChoiceDialog&lt;Region&gt;(Region.EUROPE_WEST, Region.values().toList().sortedBy { it.name() }).showAndWait().get().name()), networkName1)</ID>
@ -1774,7 +1764,6 @@
<ID>MaxLineLength:ConfigUtilities.kt$ // TODO Move this to KeyStoreConfigHelpers. fun NodeConfiguration.configureWithDevSSLCertificate(cryptoService: CryptoService? = null)</ID>
<ID>MaxLineLength:ConfigUtilities.kt$// Problems: // - Forces you to have a primary constructor with all fields of name and type matching the configuration file structure. // - Encourages weak bean-like types. // - Cannot support a many-to-one relationship between configuration file structures and configuration domain type. This is essential for versioning of the configuration files. // - It's complicated and based on reflection, meaning problems with it are typically found at runtime. // - It doesn't support validation errors in a structured way. If something goes wrong, it throws exceptions, which doesn't support good usability practices like displaying all the errors at once. fun &lt;T : Any&gt; Config.parseAs(clazz: KClass&lt;T&gt;, onUnknownKeys: ((Set&lt;String&gt;, logger: Logger) -&gt; Unit) = UnknownConfigKeysPolicy.FAIL::handle, nestedPath: String? = null, baseDirectory: Path? = null): T</ID>
<ID>MaxLineLength:ConfigUtilities.kt$// TODO Move this to KeyStoreConfigHelpers. fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name, signingCertificateStore: FileBasedCertificateStoreSupplier, certificatesDirectory: Path, cryptoService: CryptoService? = null)</ID>
<ID>MaxLineLength:ConfigUtilities.kt$ConfigHelper$return ConfigFactory.parseMap(toProperties().filterKeys { (it as String).startsWith(CORDA_PROPERTY_PREFIX) }.mapKeys { (it.key as String).removePrefix(CORDA_PROPERTY_PREFIX) })</ID>
<ID>MaxLineLength:ConfigUtilities.kt$ConfigHelper$val smartDevMode = CordaSystemUtils.isOsMac() || (CordaSystemUtils.isOsWindows() &amp;&amp; !CordaSystemUtils.getOsName().toLowerCase().contains("server"))</ID>
<ID>MaxLineLength:ConfigUtilities.kt$fun Any?.toConfigValue(): ConfigValue</ID>
<ID>MaxLineLength:ConfigUtilities.kt$inline fun &lt;reified T : Any&gt; Config.parseAs(noinline onUnknownKeys: ((Set&lt;String&gt;, logger: Logger) -&gt; Unit) = UnknownConfigKeysPolicy.FAIL::handle): T</ID>
@ -1899,8 +1888,6 @@
<ID>MaxLineLength:ConstraintsUtils.kt$input.isAutomaticHashConstraint() || output.isAutomaticHashConstraint() -&gt; throw IllegalArgumentException("Illegal constraint: AutomaticHashConstraint.")</ID>
<ID>MaxLineLength:ConstraintsUtils.kt$when { // These branches should not happen, as this has been already checked. input is AutomaticPlaceholderConstraint || output is AutomaticPlaceholderConstraint -&gt; throw IllegalArgumentException("Illegal constraint: AutomaticPlaceholderConstraint.") input.isAutomaticHashConstraint() || output.isAutomaticHashConstraint() -&gt; throw IllegalArgumentException("Illegal constraint: AutomaticHashConstraint.") // Transition to the same constraint. input == output -&gt; true // You can't transition from the AlwaysAcceptAttachmentConstraint to anything else, as it could hide something illegal. input is AlwaysAcceptAttachmentConstraint &amp;&amp; output !is AlwaysAcceptAttachmentConstraint -&gt; false // Nothing can be migrated from the HashConstraint except a HashConstraint with the same Hash. (This check is redundant, but added for clarity) input is HashAttachmentConstraint &amp;&amp; output is HashAttachmentConstraint -&gt; input == output // Anything (except the AlwaysAcceptAttachmentConstraint) can be transformed to a HashAttachmentConstraint. input !is HashAttachmentConstraint &amp;&amp; output is HashAttachmentConstraint -&gt; true // The SignatureAttachmentConstraint allows migration from a Signature constraint with the same key. // TODO - we don't support currently third party signers. When we do, the output key will have to be stronger then the input key. input is SignatureAttachmentConstraint &amp;&amp; output is SignatureAttachmentConstraint -&gt; input.key == output.key // HashAttachmentConstraint can be transformed to a SignatureAttachmentConstraint when hash constraint verification checking disabled. HashAttachmentConstraint.disableHashConstraints &amp;&amp; input is HashAttachmentConstraint &amp;&amp; output is SignatureAttachmentConstraint -&gt; true // You can transition from the WhitelistConstraint to the SignatureConstraint only if all signers of the JAR are required to sign in the future. input is WhitelistedByZoneAttachmentConstraint &amp;&amp; output is SignatureAttachmentConstraint -&gt; attachment.signerKeys.isNotEmpty() &amp;&amp; output.key.keys.containsAll(attachment.signerKeys) else -&gt; false }</ID>
<ID>MaxLineLength:ContractAttachment.kt$ContractAttachment$return "ContractAttachment(attachment=${attachment.id}, contracts='$allContracts', uploader='$uploader', signed='$isSigned', version='$version')"</ID>
<ID>MaxLineLength:ContractAttachmentSerializer.kt$ContractAttachmentSerializer$return ContractAttachmentProxy(GeneratedAttachment(bytes, obj.uploader), obj.contract, obj.additionalContracts, obj.uploader, obj.signerKeys, obj.version)</ID>
<ID>MaxLineLength:ContractAttachmentSerializer.kt$ContractAttachmentSerializer.ContractAttachmentProxy$@KeepForDJVM data</ID>
<ID>MaxLineLength:ContractHierarchyTest.kt$ContractHierarchyTest$PrepareTransaction : FlowLogic</ID>
<ID>MaxLineLength:ContractHierarchyTest.kt$ContractHierarchyTest$mockNet = InternalMockNetwork(networkSendManuallyPumped = false, threadPerNode = true, cordappsForAllNodes = listOf(enclosedCordapp()))</ID>
<ID>MaxLineLength:ContractJarTestUtils.kt$ContractJarTestUtils$@JvmOverloads fun makeTestContractJar(workingDir: Path, contractNames: List&lt;String&gt;, signed: Boolean = false, version: Int = 1, generateManifest: Boolean = true, jarFileName : String? = null): Path</ID>
@ -2010,7 +1997,6 @@
<ID>MaxLineLength:CordappContext.kt$CordappContext.EmptyCordappConfig$override fun getLong(path: String)</ID>
<ID>MaxLineLength:CordappContext.kt$CordappContext.EmptyCordappConfig$override fun getNumber(path: String)</ID>
<ID>MaxLineLength:CordappContext.kt$CordappContext.EmptyCordappConfig$override fun getString(path: String)</ID>
<ID>MaxLineLength:CordappResolver.kt$CordappResolver$logger.error("ATTENTION: More than one CorDapp installed on the node for contract $className. Please remove the previous version when upgrading to a new version.")</ID>
<ID>MaxLineLength:CordappResolverTest.kt$CordappResolverTest$@Test fun `when the same cordapp is registered for the same class multiple times, the resolver deduplicates and returns it as the current one`()</ID>
<ID>MaxLineLength:CordappSmokeTest.kt$CordappSmokeTest$(additionalNodeInfoDir / "nodeInfo-41408E093F95EAD51F6892C34DEB65AE1A3569A4B0E5744769A1B485AF8E04B5").write(signedNodeInfo.serialize().bytes)</ID>
<ID>MaxLineLength:CordappSmokeTest.kt$CordappSmokeTest$val nodeInfo = createNodeInfoWithSingleIdentity(CordaX500Name(organisation = "Bob Corp", locality = "Madrid", country = "ES"), dummyKeyPair, dummyKeyPair.public)</ID>
@ -2096,9 +2082,8 @@
<ID>MaxLineLength:DriverDSLImpl.kt$DriverDSLImpl$private</ID>
<ID>MaxLineLength:DriverDSLImpl.kt$DriverDSLImpl$val flowOverrideConfig = FlowOverrideConfig(parameters.flowOverrides.map { FlowOverride(it.key.canonicalName, it.value.canonicalName) })</ID>
<ID>MaxLineLength:DriverDSLImpl.kt$DriverDSLImpl$val jdbcUrl = "jdbc:h2:mem:persistence${inMemoryCounter.getAndIncrement()};DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;WRITE_DELAY=100"</ID>
<ID>MaxLineLength:DriverDSLImpl.kt$DriverDSLImpl$val process = startOutOfProcessNode(config, quasarJarPath, debugPort, systemProperties, parameters.maximumHeapSize, parameters.logLevelOverride)</ID>
<ID>MaxLineLength:DriverDSLImpl.kt$DriverDSLImpl.Companion$private operator fun Config.plus(property: Pair&lt;String, Any&gt;)</ID>
<ID>MaxLineLength:DriverDSLImpl.kt$DriverDSLImpl.Companion${ log.info("Starting out-of-process Node ${config.corda.myLegalName.organisation}, debug port is " + (debugPort ?: "not enabled")) // Write node.conf writeConfig(config.corda.baseDirectory, "node.conf", config.typesafe.toNodeOnly()) val systemProperties = mutableMapOf( "name" to config.corda.myLegalName, "visualvm.display.name" to "corda-${config.corda.myLegalName}" ) debugPort?.let { systemProperties += "log4j2.level" to "debug" systemProperties += "log4j2.debug" to "true" } systemProperties += inheritFromParentProcess() systemProperties += overriddenSystemProperties // See experimental/quasar-hook/README.md for how to generate. val excludePattern = "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**;" + "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 extraJvmArguments = systemProperties.removeResolvedClasspath().map { "-D${it.key}=${it.value}" } + "-javaagent:$quasarJarPath=$excludePattern" val loggingLevel = when { logLevelOverride != null -&gt; logLevelOverride debugPort == null -&gt; "INFO" else -&gt; "DEBUG" } val arguments = mutableListOf( "--base-directory=${config.corda.baseDirectory}", "--logging-level=$loggingLevel", "--no-local-shell").also { it += extraCmdLineFlag }.toList() // The following dependencies are excluded from the classpath of the created JVM, so that the environment resembles a real one as close as possible. // These are either classes that will be added as attachments to the node (i.e. samples, finance, opengamma etc.) or irrelevant testing libraries (test, corda-mock etc.). // TODO: There is pending work to fix this issue without custom blacklisting. See: https://r3-cev.atlassian.net/browse/CORDA-2164. val exclude = listOf("samples", "finance", "integrationTest", "test", "corda-mock", "com.opengamma.strata") val cp = ProcessUtilities.defaultClassPath.filterNot { cpEntry -&gt; exclude.any { token -&gt; cpEntry.contains("${File.separatorChar}$token") } || cpEntry.endsWith("-tests.jar") } return ProcessUtilities.startJavaProcess( className = "net.corda.node.Corda", // cannot directly get class for this, so just use string arguments = arguments, jdwpPort = debugPort, extraJvmArguments = extraJvmArguments, workingDirectory = config.corda.baseDirectory, maximumHeapSize = maximumHeapSize, classPath = cp ) }</ID>
<ID>MaxLineLength:DriverDSLImpl.kt$DriverDSLImpl.Companion${ log.info("Starting out-of-process Node ${config.corda.myLegalName.organisation}, " + "debug port is " + (debugPort ?: "not enabled")) // Write node.conf writeConfig(config.corda.baseDirectory, "node.conf", config.typesafe.toNodeOnly()) val systemProperties = mutableMapOf( "name" to config.corda.myLegalName, "visualvm.display.name" to "corda-${config.corda.myLegalName}" ) debugPort?.let { systemProperties += "log4j2.level" to "debug" systemProperties += "log4j2.debug" to "true" } systemProperties += inheritFromParentProcess() systemProperties += overriddenSystemProperties // See experimental/quasar-hook/README.md for how to generate. val excludePattern = "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**;" + "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 extraJvmArguments = systemProperties.removeResolvedClasspath().map { "-D${it.key}=${it.value}" } + "-javaagent:$quasarJarPath=$excludePattern" val loggingLevel = when { logLevelOverride != null -&gt; logLevelOverride debugPort == null -&gt; "INFO" else -&gt; "DEBUG" } val arguments = mutableListOf( "--base-directory=${config.corda.baseDirectory}", "--logging-level=$loggingLevel", "--no-local-shell").also { it.addAll(extraCmdLineFlag) }.toList() // The following dependencies are excluded from the classpath of the created JVM, so that the environment resembles a real one as close as possible. // These are either classes that will be added as attachments to the node (i.e. samples, finance, opengamma etc.) or irrelevant testing libraries (test, corda-mock etc.). // TODO: There is pending work to fix this issue without custom blacklisting. See: https://r3-cev.atlassian.net/browse/CORDA-2164. val exclude = listOf("samples", "finance", "integrationTest", "test", "corda-mock", "com.opengamma.strata") val cp = ProcessUtilities.defaultClassPath.filterNot { cpEntry -&gt; exclude.any { token -&gt; cpEntry.contains("${File.separatorChar}$token") } || cpEntry.endsWith("-tests.jar") } return ProcessUtilities.startJavaProcess( className = "net.corda.node.Corda", // cannot directly get class for this, so just use string arguments = arguments, jdwpPort = debugPort, extraJvmArguments = extraJvmArguments, workingDirectory = config.corda.baseDirectory, maximumHeapSize = maximumHeapSize, classPath = cp, environmentVariables = environmentVariables ) }</ID>
<ID>MaxLineLength:DriverDSLImpl.kt$InternalDriverDSL$ fun &lt;A&gt; pollUntilNonNull(pollName: String, pollInterval: Duration = DEFAULT_POLL_INTERVAL, warnCount: Int = DEFAULT_WARN_COUNT, check: () -&gt; A?): CordaFuture&lt;A&gt;</ID>
<ID>MaxLineLength:DriverDSLImpl.kt$InternalDriverDSL$ fun pollUntilTrue(pollName: String, pollInterval: Duration = DEFAULT_POLL_INTERVAL, warnCount: Int = DEFAULT_WARN_COUNT, check: () -&gt; Boolean): CordaFuture&lt;Unit&gt;</ID>
<ID>MaxLineLength:DriverDSLImpl.kt$fun DriverDSL.startNode(providedName: CordaX500Name, devMode: Boolean, parameters: NodeParameters = NodeParameters()): CordaFuture&lt;NodeHandle&gt;</ID>
@ -2117,12 +2102,10 @@
<ID>MaxLineLength:DummyLinearStateSchemaV1.kt$DummyLinearStateSchemaV1.PersistentDummyLinearState$@Table(name = "dummy_linear_states", indexes = [Index(name = "external_id_idx", columnList = "external_id"), Index(name = "uuid_idx", columnList = "uuid")])</ID>
<ID>MaxLineLength:DummyLinearStateSchemaV2.kt$DummyLinearStateSchemaV2.PersistentDummyLinearState$@CollectionTable(name = "dummy_linear_states_v2_parts", joinColumns = [(JoinColumn(name = "output_index", referencedColumnName = "output_index")), (JoinColumn(name = "transaction_id", referencedColumnName = "transaction_id"))])</ID>
<ID>MaxLineLength:DumpHistoryOnErrorInterceptor.kt$DumpHistoryOnErrorInterceptor$val transitionRecord = TransitionDiagnosticRecord(Instant.now(), fiber.id, previousState, nextState, event, transition, continuation)</ID>
<ID>MaxLineLength:DurationSerializer.kt$DurationSerializer : Proxy</ID>
<ID>MaxLineLength:E2ETestKeyManagementService.kt$E2ETestKeyManagementService : SingletonSerializeAsTokenKeyManagementServiceInternal</ID>
<ID>MaxLineLength:EdDSATests.kt$EdDSATests$assertNotEquals(testVectorEd25519ctx.signatureOutputHex, doSign(privateKey, testVectorEd25519ctx.messageToSignHex.hexToByteArray()).toHex().toLowerCase())</ID>
<ID>MaxLineLength:EnumEvolutionSerializer.kt$EnumEvolutionSerializer$val converted = conversions[enumName] ?: throw AMQPNotSerializableException(type, "No rule to evolve enum constant $type::$enumName")</ID>
<ID>MaxLineLength:EnumEvolvabilityTests.kt$EnumEvolvabilityTests$assertThatThrownBy { SerializationOutput(sf).serialize(C(RejectMultipleRenameFrom.A)) }.isInstanceOf(NotSerializableException::class.java) .hasToString("Unable to serialize/deserialize net.corda.serialization.internal.amqp.EnumEvolvabilityTests\$RejectMultipleRenameFrom: " + "There are multiple transformations from D, which is not allowed")</ID>
<ID>MaxLineLength:EnumSetSerializer.kt$EnumSetSerializer : Proxy</ID>
<ID>MaxLineLength:ErrorCodeLoggingTests.kt$ErrorCodeLoggingTests$val linesWithErrorCode = logFile.useLines { lines -&gt; lines.filter { line -&gt; line.contains("[errorCode=") }.filter { line -&gt; line.contains("moreInformationAt=https://errors.corda.net/") }.toList() }</ID>
<ID>MaxLineLength:ErrorCodeLoggingTests.kt$fun NodeHandle.logFile(): File</ID>
<ID>MaxLineLength:ErrorFlowTransition.kt$ErrorFlowTransition$val (initiatedSessions, newSessions) = bufferErrorMessagesInInitiatingSessions(startingState.checkpoint.sessions, errorMessages)</ID>
@ -2159,11 +2142,6 @@
<ID>MaxLineLength:FlowFrameworkPersistenceTests.kt$FlowFrameworkPersistenceTests$assertEquals(payload + 1, secondFlow.getOrThrow().receivedPayload2, "Received payload does not match the expected second value on Node 2")</ID>
<ID>MaxLineLength:FlowFrameworkPersistenceTests.kt$FlowFrameworkPersistenceTests$assertEquals(payload, secondFlow.getOrThrow().receivedPayload, "Received payload does not match the (restarted) first value on Node 2")</ID>
<ID>MaxLineLength:FlowFrameworkTests.kt$FlowFrameworkTests$assertThat(receivedSessionMessages).hasSize(1)</ID>
<ID>MaxLineLength:FlowFrameworkTests.kt$PingPongFlow$@InitiatingFlow internal</ID>
<ID>MaxLineLength:FlowFrameworkTests.kt$SendAndReceiveFlow$@InitiatingFlow internal</ID>
<ID>MaxLineLength:FlowFrameworkTests.kt$internal fun errorMessage(errorResponse: FlowException? = null)</ID>
<ID>MaxLineLength:FlowFrameworkTests.kt$internal fun sessionConfirm(flowVersion: Int = 1)</ID>
<ID>MaxLineLength:FlowFrameworkTests.kt$internal infix fun Pair&lt;Int, SessionMessage&gt;.to(node: TestStartedNode): SessionTransfer</ID>
<ID>MaxLineLength:FlowLogic.kt$FlowLogic$ @Suspendable @JvmOverloads open fun &lt;R : Any&gt; receiveAll(receiveType: Class&lt;R&gt;, sessions: List&lt;FlowSession&gt;, maySkipCheckpoint: Boolean = false): List&lt;UntrustworthyData&lt;R&gt;&gt;</ID>
<ID>MaxLineLength:FlowLogic.kt$FlowLogic$ @Suspendable @JvmOverloads open fun receiveAllMap(sessions: Map&lt;FlowSession, Class&lt;out Any&gt;&gt;, maySkipCheckpoint: Boolean = false): Map&lt;FlowSession, UntrustworthyData&lt;Any&gt;&gt;</ID>
<ID>MaxLineLength:FlowLogic.kt$FlowLogic$?:</ID>
@ -2371,7 +2349,6 @@
<ID>MaxLineLength:InstallShellExtensionsParser.kt$InstallShellExtensionsParser : CliWrapperBase</ID>
<ID>MaxLineLength:InstallShellExtensionsParser.kt$ShellExtensionsGenerator$printWarning("Cannot install shell extension for bash major version earlier than $minSupportedBashVersion. Please upgrade your bash version. Aliases should still work.")</ID>
<ID>MaxLineLength:InstallShellExtensionsParser.kt$ShellExtensionsGenerator$println("Installation complete, ${parent.alias} is available in bash, but autocompletion was not installed because of an old version of bash.")</ID>
<ID>MaxLineLength:InstantSerializer.kt$InstantSerializer : Proxy</ID>
<ID>MaxLineLength:InteractiveShellIntegrationTest.kt$InteractiveShellIntegrationTest$private</ID>
<ID>MaxLineLength:InterestSwapRestAPI.kt$InterestRateSwapAPI</ID>
<ID>MaxLineLength:InternalAccessTestHelpers.kt$fun &lt;T&gt; ifThrowsAppend(strToAppendFn: () -&gt; String, block: () -&gt; T): T</ID>
@ -2469,9 +2446,6 @@
<ID>MaxLineLength:ListsSerializationTest.kt$ListsSerializationTest.Companion$val envelope = DeserializationInput(SerializerFactoryBuilder.build(context.whitelist, context.deserializationClassLoader)).getEnvelope(serBytes, context)</ID>
<ID>MaxLineLength:ListsSerializationTest.kt$internal inline</ID>
<ID>MaxLineLength:LoadTestConfiguration.kt$RemoteNode</ID>
<ID>MaxLineLength:LocalDateSerializer.kt$LocalDateSerializer : Proxy</ID>
<ID>MaxLineLength:LocalDateTimeSerializer.kt$LocalDateTimeSerializer : Proxy</ID>
<ID>MaxLineLength:LocalDateTimeSerializer.kt$LocalDateTimeSerializer$override val additionalSerializers: Iterable&lt;CustomSerializer&lt;out Any&gt;&gt; = listOf(LocalDateSerializer(factory), LocalTimeSerializer(factory))</ID>
<ID>MaxLineLength:LocalPropertyInformation.kt$LocalPropertyInformation.CalculatedProperty$data</ID>
<ID>MaxLineLength:LocalPropertyInformation.kt$LocalPropertyInformation.ConstructorPairedProperty$data</ID>
<ID>MaxLineLength:LocalPropertyInformation.kt$LocalPropertyInformation.GetterSetterProperty$data</ID>
@ -2481,9 +2455,6 @@
<ID>MaxLineLength:LocalSerializerFactory.kt$DefaultLocalSerializerFactory$is LocalTypeInformation.AMap</ID>
<ID>MaxLineLength:LocalSerializerFactory.kt$DefaultLocalSerializerFactory$private</ID>
<ID>MaxLineLength:LocalSerializerFactory.kt$DefaultLocalSerializerFactory$private val serializersByActualAndDeclaredType: MutableMap&lt;ActualAndDeclaredType, AMQPSerializer&lt;Any&gt;&gt; = DefaultCacheProvider.createCache()</ID>
<ID>MaxLineLength:LocalTimeSerializer.kt$LocalTimeSerializer : Proxy</ID>
<ID>MaxLineLength:LocalTimeSerializer.kt$LocalTimeSerializer$override fun fromProxy(proxy: LocalTimeProxy): LocalTime</ID>
<ID>MaxLineLength:LocalTimeSerializer.kt$LocalTimeSerializer$override fun toProxy(obj: LocalTime): LocalTimeProxy</ID>
<ID>MaxLineLength:LocalTypeInformation.kt$LocalTypeInformation.ACollection$data</ID>
<ID>MaxLineLength:LocalTypeInformation.kt$LocalTypeInformation.AnArray$data</ID>
<ID>MaxLineLength:LocalTypeInformation.kt$LocalTypeInformation.Singleton$data</ID>
@ -2763,7 +2734,7 @@
<ID>MaxLineLength:NodeKeystoreCheckTest.kt$NodeKeystoreCheckTest$setPrivateKey(X509Utilities.CORDA_CLIENT_CA, nodeCA.keyPair.private, listOf(badNodeCACert, badRoot), signingCertStore.entryPassword)</ID>
<ID>MaxLineLength:NodeKeystoreCheckTest.kt$NodeKeystoreCheckTest$val badNodeCACert = X509Utilities.createCertificate(CertificateType.NODE_CA, badRoot, badRootKeyPair, ALICE_NAME.x500Principal, nodeCA.keyPair.public)</ID>
<ID>MaxLineLength:NodeKeystoreCheckTest.kt$NodeKeystoreCheckTest$val p2pSslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory, keyStorePassword = keystorePassword, trustStorePassword = keystorePassword)</ID>
<ID>MaxLineLength:NodeMonitorModel.kt$NodeMonitorModel${ rpc = CordaRPCClient(nodeHostAndPort).start(username, password) proxyObservable.value = rpc.proxy // Vault snapshot (force single page load with MAX_PAGE_SIZE) + updates val ( statesSnapshot, vaultUpdates ) = rpc.proxy.vaultTrackBy&lt;ContractState&gt;( QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL), PageSpecification(DEFAULT_PAGE_NUM, MAX_PAGE_SIZE) ) val unconsumedStates = statesSnapshot.states.filterIndexed { index, _ -&gt; statesSnapshot.statesMetadata[index].status == Vault.StateStatus.UNCONSUMED }.toSet() val consumedStates = statesSnapshot.states.toSet() - unconsumedStates val initialVaultUpdate = Vault.Update(consumedStates, unconsumedStates, references = emptySet()) vaultUpdates.startWith(initialVaultUpdate).subscribe(vaultUpdatesSubject::onNext) // Transactions val (transactions, newTransactions) = @Suppress("DEPRECATION") rpc.proxy.internalVerifiedTransactionsFeed() newTransactions.startWith(transactions).subscribe(transactionsSubject::onNext) // SM -&gt; TX mapping val (smTxMappings, futureSmTxMappings) = rpc.proxy.stateMachineRecordedTransactionMappingFeed() futureSmTxMappings.startWith(smTxMappings).subscribe(stateMachineTransactionMappingSubject::onNext) // Parties on network val (parties, futurePartyUpdate) = rpc.proxy.networkMapFeed() futurePartyUpdate.startWith(parties.map(MapChange::Added)).subscribe(networkMapSubject::onNext) val stateMachines = rpc.proxy.stateMachinesSnapshot() notaryIdentities = rpc.proxy.notaryIdentities() // Extract the flow tracking stream // TODO is there a nicer way of doing this? Stream of streams in general results in code like this... // TODO `progressTrackingSubject` doesn't seem to be used anymore - should it be removed? val currentProgressTrackerUpdates = stateMachines.mapNotNull { stateMachine -&gt; ProgressTrackingEvent.createStreamFromStateMachineInfo(stateMachine) } val futureProgressTrackerUpdates = stateMachineUpdatesSubject.map { stateMachineUpdate -&gt; if (stateMachineUpdate is StateMachineUpdate.Added) { ProgressTrackingEvent.createStreamFromStateMachineInfo(stateMachineUpdate.stateMachineInfo) ?: Observable.empty&lt;ProgressTrackingEvent&gt;() } else { Observable.empty&lt;ProgressTrackingEvent&gt;() } } // We need to retry, because when flow errors, we unsubscribe from progressTrackingSubject. So we end up with stream of state machine updates and no progress trackers. futureProgressTrackerUpdates.startWith(currentProgressTrackerUpdates).flatMap { it }.retry().subscribe(progressTrackingSubject) }</ID>
<ID>MaxLineLength:NodeMonitorModel.kt$NodeMonitorModel${ rpc = CordaRPCClient(nodeHostAndPort).start(username, password, GracefulReconnect()) proxyObservable.value = rpc.proxy // Vault snapshot (force single page load with MAX_PAGE_SIZE) + updates val ( statesSnapshot, vaultUpdates ) = rpc.proxy.vaultTrackBy&lt;ContractState&gt;( QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL), PageSpecification(DEFAULT_PAGE_NUM, MAX_PAGE_SIZE) ) val unconsumedStates = statesSnapshot.states.filterIndexed { index, _ -&gt; statesSnapshot.statesMetadata[index].status == Vault.StateStatus.UNCONSUMED }.toSet() val consumedStates = statesSnapshot.states.toSet() - unconsumedStates val initialVaultUpdate = Vault.Update(consumedStates, unconsumedStates, references = emptySet()) vaultUpdates.startWith(initialVaultUpdate).subscribe(vaultUpdatesSubject::onNext) // Transactions val (transactions, newTransactions) = @Suppress("DEPRECATION") rpc.proxy.internalVerifiedTransactionsFeed() newTransactions.startWith(transactions).subscribe(transactionsSubject::onNext) // SM -&gt; TX mapping val (smTxMappings, futureSmTxMappings) = rpc.proxy.stateMachineRecordedTransactionMappingFeed() futureSmTxMappings.startWith(smTxMappings).subscribe(stateMachineTransactionMappingSubject::onNext) // Parties on network val (parties, futurePartyUpdate) = rpc.proxy.networkMapFeed() futurePartyUpdate.startWith(parties.map(MapChange::Added)).subscribe(networkMapSubject::onNext) val stateMachines = rpc.proxy.stateMachinesSnapshot() notaryIdentities = rpc.proxy.notaryIdentities() // Extract the flow tracking stream // TODO is there a nicer way of doing this? Stream of streams in general results in code like this... // TODO `progressTrackingSubject` doesn't seem to be used anymore - should it be removed? val currentProgressTrackerUpdates = stateMachines.mapNotNull { stateMachine -&gt; ProgressTrackingEvent.createStreamFromStateMachineInfo(stateMachine) } val futureProgressTrackerUpdates = stateMachineUpdatesSubject.map { stateMachineUpdate -&gt; if (stateMachineUpdate is StateMachineUpdate.Added) { ProgressTrackingEvent.createStreamFromStateMachineInfo(stateMachineUpdate.stateMachineInfo) ?: Observable.empty&lt;ProgressTrackingEvent&gt;() } else { Observable.empty&lt;ProgressTrackingEvent&gt;() } } // We need to retry, because when flow errors, we unsubscribe from progressTrackingSubject. So we end up with stream of state machine updates and no progress trackers. futureProgressTrackerUpdates.startWith(currentProgressTrackerUpdates).flatMap { it }.retry().subscribe(progressTrackingSubject) }</ID>
<ID>MaxLineLength:NodeNamedCache.kt$DefaultNamedCacheFactory$name.startsWith("RPCSecurityManagerShiroCache_") -&gt; with(security?.authService?.options?.cache!!) { caffeine.maximumSize(maxEntries).expireAfterWrite(expireAfterSecs, TimeUnit.SECONDS) }</ID>
<ID>MaxLineLength:NodeNamedCache.kt$DefaultNamedCacheFactory$open</ID>
<ID>MaxLineLength:NodeNamedCache.kt$DefaultNamedCacheFactory$override fun bindWithConfig(nodeConfiguration: NodeConfiguration): BindableNamedCacheFactory</ID>
@ -2970,15 +2941,10 @@
<ID>MaxLineLength:ObservableUtilities.kt$ @Suppress("UNCHECKED_CAST") fun &lt;K : Any, A : Any, B&gt; ObservableList&lt;out A&gt;.associateByAggregation(toKey: (A) -&gt; K, assemble: (K, A) -&gt; B): ObservableMap&lt;K, ObservableList&lt;B&gt;&gt;</ID>
<ID>MaxLineLength:ObservableUtilities.kt${ //TODO This is a tactical work round for an issue with SAM conversion (https://youtrack.jetbrains.com/issue/ALL-1552) so that the M10 explorer works. return uncheckedCast(uncheckedCast&lt;Any, ObservableList&lt;A?&gt;&gt;(this).filtered { t -&gt; t != null }) }</ID>
<ID>MaxLineLength:ObserverNodeTransactionTests.kt$ObserverNodeTransactionTests.StartMessageChainFlow$val txBuilder = TransactionBuilder(notary).withItems(StateAndContract(messageState, MESSAGE_CHAIN_CONTRACT_PROGRAM_ID), txCommand)</ID>
<ID>MaxLineLength:OffsetDateTimeSerializer.kt$OffsetDateTimeSerializer : Proxy</ID>
<ID>MaxLineLength:OffsetDateTimeSerializer.kt$OffsetDateTimeSerializer$override val additionalSerializers: Iterable&lt;CustomSerializer&lt;out Any&gt;&gt; = listOf(LocalDateTimeSerializer(factory), ZoneIdSerializer(factory))</ID>
<ID>MaxLineLength:OffsetTimeSerializer.kt$OffsetTimeSerializer : Proxy</ID>
<ID>MaxLineLength:OffsetTimeSerializer.kt$OffsetTimeSerializer$override val additionalSerializers: Iterable&lt;CustomSerializer&lt;out Any&gt;&gt; = listOf(LocalTimeSerializer(factory), ZoneIdSerializer(factory))</ID>
<ID>MaxLineLength:OnLedgerAsset.kt$OnLedgerAsset.Companion$deriveState: (TransactionState&lt;S&gt;, Amount&lt;Issued&lt;T&gt;&gt;, AbstractParty) -&gt; TransactionState&lt;S&gt;</ID>
<ID>MaxLineLength:OnLedgerAsset.kt$OnLedgerAsset.Companion$generateMoveCommand: () -&gt; CommandData</ID>
<ID>MaxLineLength:OpenGammaCordaUtils.kt$ fun InitialMarginTriple.toCordaCompatible()</ID>
<ID>MaxLineLength:OpenGammaCordaUtils.kt$return MultiCurrencyAmount.of(this.amounts.map { CurrencyAmount.of(Currency.of(it.currency.code).serialize().deserialize(), twoDecimalPlaces((it.amount))) })</ID>
<ID>MaxLineLength:OptionalSerializer.kt$OptionalSerializer : Proxy</ID>
<ID>MaxLineLength:OracleNodeTearOffTests.kt$OracleNodeTearOffTests$TransactionBuilder(DUMMY_NOTARY) .withItems(TransactionState(1000.DOLLARS.CASH issuedBy dummyCashIssuer.party ownedBy alice.party, Cash.PROGRAM_ID, DUMMY_NOTARY))</ID>
<ID>MaxLineLength:P2PFlowsDrainingModeTest.kt$P2PFlowsDrainingModeTest$nodeA.rpc.hasCancelledDrainingShutdown().doOnError(Throwable::printStackTrace).doOnError { successful = false }.doOnCompleted { successful = true }.doAfterTerminate(latch::countDown).subscribe()</ID>
<ID>MaxLineLength:P2PFlowsDrainingModeTest.kt$P2PFlowsDrainingModeTest$nodeA.rpc.waitForShutdown().doOnError(Throwable::printStackTrace).doOnError { successful = false }.doOnCompleted(nodeA::stop)</ID>
@ -3008,7 +2974,6 @@
<ID>MaxLineLength:Perceivable.kt$fun interest(@Suppress("UNUSED_PARAMETER") amount: BigDecimal, @Suppress("UNUSED_PARAMETER") dayCountConvention: String, @Suppress("UNUSED_PARAMETER") interest: BigDecimal /* todo - appropriate type */, @Suppress("UNUSED_PARAMETER") start: String, @Suppress("UNUSED_PARAMETER") end: String): Perceivable&lt;BigDecimal&gt;</ID>
<ID>MaxLineLength:Perceivable.kt$fun interest(@Suppress("UNUSED_PARAMETER") amount: BigDecimal, @Suppress("UNUSED_PARAMETER") dayCountConvention: String, @Suppress("UNUSED_PARAMETER") interest: Perceivable&lt;BigDecimal&gt; /* todo - appropriate type */, @Suppress("UNUSED_PARAMETER") start: Perceivable&lt;Instant&gt;, @Suppress("UNUSED_PARAMETER") end: Perceivable&lt;Instant&gt;): Perceivable&lt;BigDecimal&gt;</ID>
<ID>MaxLineLength:Perceivable.kt$fun interest(@Suppress("UNUSED_PARAMETER") amount: BigDecimal, @Suppress("UNUSED_PARAMETER") dayCountConvention: String, @Suppress("UNUSED_PARAMETER") interest: Perceivable&lt;BigDecimal&gt; /* todo - appropriate type */, @Suppress("UNUSED_PARAMETER") start: String, @Suppress("UNUSED_PARAMETER") end: String): Perceivable&lt;BigDecimal&gt;</ID>
<ID>MaxLineLength:PeriodSerializer.kt$PeriodSerializer : Proxy</ID>
<ID>MaxLineLength:PersistentIdentityMigrationNewTable.kt$PersistentIdentityMigrationNewTable : CordaMigration</ID>
<ID>MaxLineLength:PersistentIdentityMigrationNewTable.kt$PersistentIdentityMigrationNewTable$throw PersistentIdentitiesMigrationException("Cannot migrate persistent identities as liquibase failed to provide a suitable database connection")</ID>
<ID>MaxLineLength:PersistentIdentityMigrationNewTableTest.kt$PersistentIdentityMigrationNewTableTest$session.save(PersistentIdentityService.PersistentPublicKeyHashToCertificate(it.owningKey.hash.toString(), it.certPath.encoded))</ID>
@ -3044,7 +3009,6 @@
<ID>MaxLineLength:PortfolioApiUtils.kt$PortfolioApiUtils$val yieldCurveCurrenciesValues = marketData.filter { !it.key.contains("/") }.map { it -&gt; Triple(it.key.split("-")[0], it.key.split("-", limit = 2)[1], it.value) }</ID>
<ID>MaxLineLength:PortfolioState.kt$PortfolioState$return TransactionBuilder(notary).withItems(StateAndContract(copy(), PORTFOLIO_SWAP_PROGRAM_ID), Command(PortfolioSwap.Commands.Agree(), participants.map { it.owningKey }))</ID>
<ID>MaxLineLength:PrintingInterceptor.kt$PrintingInterceptor$val transitionRecord = TransitionDiagnosticRecord(Instant.now(), fiber.id, previousState, nextState, event, transition, continuation)</ID>
<ID>MaxLineLength:PrivateKeySerializer.kt$PrivateKeySerializer$override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), AMQPTypeIdentifiers.primitiveTypeName(ByteArray::class.java), descriptor, emptyList())))</ID>
<ID>MaxLineLength:ProgressTracker.kt$ProgressTracker$log.warnOnce("Found ProgressTracker Step(s) with the same label: ${labels.groupBy { it }.filter { it.value.size &gt; 1 }.map { it.key }}")</ID>
<ID>MaxLineLength:ProgressTracker.kt$ProgressTracker.Step$private fun definitionLocation(): String</ID>
<ID>MaxLineLength:Properties.kt$DelegatedProperty$private abstract</ID>
@ -3077,7 +3041,6 @@
<ID>MaxLineLength:PropertyTest.kt$PropertyTest$val property = Configuration.Property.Definition.long(key).map(::AtomicLong).list().map { list -&gt; list.map(AtomicLong::get).max() }.optional()</ID>
<ID>MaxLineLength:PropertyValidationTest.kt$PropertyValidationTest$return invalid(Configuration.Validation.Error.BadValue.of("Value must be of format \"host(String):port(Int &gt; 0)\" e.g., \"127.0.0.1:8080\""))</ID>
<ID>MaxLineLength:ProviderMap.kt$// This registration is needed for reading back EdDSA key from java keystore. // TODO: Find a way to make JKS work with bouncy castle provider or implement our own provide so we don't have to register bouncy castle provider. Security.addProvider(it)</ID>
<ID>MaxLineLength:PublicKeySerializer.kt$PublicKeySerializer$override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), AMQPTypeIdentifiers.primitiveTypeName(ByteArray::class.java), descriptor, emptyList())))</ID>
<ID>MaxLineLength:PublicKeyToOwningIdentityCacheImpl.kt$PublicKeyToOwningIdentityCacheImpl$criteriaBuilder.equal(queryRoot.get&lt;String&gt;(BasicHSMKeyManagementService.PersistentKey::publicKeyHash.name), key.toStringShort())</ID>
<ID>MaxLineLength:PublicKeyToOwningIdentityCacheImpl.kt$PublicKeyToOwningIdentityCacheImpl$criteriaBuilder.equal(queryRoot.get&lt;String&gt;(PersistentIdentityService.PersistentPublicKeyHashToCertificate::publicKeyHash.name), key.toStringShort())</ID>
<ID>MaxLineLength:PushedNode.kt$PushedNode$return NodeInstanceRequest(configFile, baseDirectory, copiedNodeConfig, copiedNodeDir, nodeConfig, localImageId, remoteImageName, nodeInstanceName, actualX500, expectedFqName)</ID>
@ -3144,8 +3107,6 @@
<ID>MaxLineLength:RPCApi.kt$private fun Trace.mapToExternal(message: ClientMessage)</ID>
<ID>MaxLineLength:RPCApi.kt$return invocationId(RPC_ID_FIELD_NAME, RPC_ID_TIMESTAMP_FIELD_NAME) ?: throw IllegalStateException("Cannot extract reply id from client message.")</ID>
<ID>MaxLineLength:RPCApi.kt$return sessionId(RPC_SESSION_ID_FIELD_NAME, RPC_SESSION_ID_TIMESTAMP_FIELD_NAME) ?: throw IllegalStateException("Cannot extract the session id from client message.")</ID>
<ID>MaxLineLength:RPCClientProxyHandler.kt$RPCClientProxyHandler$private</ID>
<ID>MaxLineLength:RPCClientProxyHandler.kt$RPCClientProxyHandler$private val serializationContextWithObservableContext = RpcClientObservableDeSerializer.createContext(serializationContext, observableContext)</ID>
<ID>MaxLineLength:RPCClientProxyHandler.kt$RPCClientProxyHandler$throw UnsupportedOperationException("Method $calledMethod was added in RPC protocol version $sinceVersion but the server is running $serverProtocolVersion")</ID>
<ID>MaxLineLength:RPCDriver.kt$RPCDriverDSL$val artemisConfig = createRpcServerArtemisConfig(maxFileSize, maxBufferedBytesPerClient, driverDSL.driverDirectory / serverName, hostAndPort)</ID>
<ID>MaxLineLength:RPCDriver.kt$RPCDriverDSL.Companion$fun createRpcServerArtemisConfig(maxFileSize: Int, maxBufferedBytesPerClient: Long, baseDirectory: Path, hostAndPort: NetworkHostAndPort): Configuration</ID>
@ -3285,8 +3246,6 @@
<ID>MaxLineLength:SerializationAPI.kt$SerializationFactory$abstract</ID>
<ID>MaxLineLength:SerializationAPI.kt$context: SerializationContext = serializationFactory.defaultContext</ID>
<ID>MaxLineLength:SerializationAPI.kt$inline</ID>
<ID>MaxLineLength:SerializationEnvironment.kt$"Expected exactly 1 of {${serializationEnvFields.joinToString(", ") { it.name }}} but got: {${it.joinToString(", ") { it.first }}}"</ID>
<ID>MaxLineLength:SerializationEnvironment.kt$private val serializationEnvFields = listOf(_nodeSerializationEnv, _driverSerializationEnv, _contextSerializationEnv, _inheritableContextSerializationEnv)</ID>
<ID>MaxLineLength:SerializationEnvironment.kt$val _inheritableContextSerializationEnv = InheritableThreadLocalToggleField&lt;SerializationEnvironment&gt;("inheritableContextSerializationEnv") { stack -&gt; stack.fold(false) { isAGlobalThreadBeingCreated, e -&gt; isAGlobalThreadBeingCreated || (e.className == "io.netty.util.concurrent.GlobalEventExecutor" &amp;&amp; e.methodName == "startThread") || (e.className == "java.util.concurrent.ForkJoinPool\$DefaultForkJoinWorkerThreadFactory" &amp;&amp; e.methodName == "newThread") } }</ID>
<ID>MaxLineLength:SerializationFactory.kt$SerializationFactory$abstract</ID>
<ID>MaxLineLength:SerializationOutputTests.kt$SerializationOutputTests$assertArrayEquals(data, DeserializationInput(factory).deserialize(compressed, testSerializationContext.withEncodingWhitelist(encodingWhitelist)))</ID>
@ -3468,7 +3427,6 @@
<ID>MaxLineLength:ThrowableSerializer.kt$StackTraceElementSerializer : Proxy</ID>
<ID>MaxLineLength:ThrowableSerializer.kt$StackTraceElementSerializer$override fun fromProxy(proxy: StackTraceElementProxy): StackTraceElement</ID>
<ID>MaxLineLength:ThrowableSerializer.kt$StackTraceElementSerializer$override fun toProxy(obj: StackTraceElement): StackTraceElementProxy</ID>
<ID>MaxLineLength:ThrowableSerializer.kt$ThrowableSerializer : Proxy</ID>
<ID>MaxLineLength:ThrowableSerializer.kt$ThrowableSerializer${ try { // TODO: This will need reworking when we have multiple class loaders val clazz = Class.forName(proxy.exceptionClass, false, factory.classloader) // If it is CordaException or CordaRuntimeException, we can seek any constructor and then set the properties // Otherwise we just make a CordaRuntimeException if (CordaThrowable::class.java.isAssignableFrom(clazz) &amp;&amp; Throwable::class.java.isAssignableFrom(clazz)) { val typeInformation = factory.getTypeInformation(clazz) val constructor = typeInformation.constructor val params = constructor.parameters.map { parameter -&gt; proxy.additionalProperties[parameter.name] ?: proxy.additionalProperties[parameter.name.capitalize()] } val throwable = constructor.observedMethod.newInstance(*params.toTypedArray()) (throwable as CordaThrowable).apply { if (this.javaClass.name != proxy.exceptionClass) this.originalExceptionClassName = proxy.exceptionClass this.setMessage(proxy.message) this.setCause(proxy.cause) this.addSuppressed(proxy.suppressed) } return (throwable as Throwable).apply { this.stackTrace = proxy.stackTrace } } } catch (e: Exception) { logger.warn("Unexpected exception de-serializing throwable: ${proxy.exceptionClass}. Converting to CordaRuntimeException.", e) } // If the criteria are not met or we experience an exception constructing the exception, we fall back to our own unchecked exception. return CordaRuntimeException(proxy.exceptionClass, null, null).apply { this.setMessage(proxy.message) this.setCause(proxy.cause) this.stackTrace = proxy.stackTrace this.addSuppressed(proxy.suppressed) } }</ID>
<ID>MaxLineLength:TimedFlowTests.kt$TimedFlowTests$addOutputState(DummyContract.SingleOwnerState(owner = info.singleIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint)</ID>
<ID>MaxLineLength:TimedFlowTests.kt$TimedFlowTests.Companion$defaultParameters = MockNetworkParameters().withServicePeerAllocationStrategy(InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin())</ID>
@ -3820,13 +3778,6 @@
<ID>MaxLineLength:X509UtilitiesTest.kt$X509UtilitiesTest$val certCaAuthorityKeyIdentifier = AuthorityKeyIdentifier.getInstance(getExtension(Extension.authorityKeyIdentifier).parsedValue)</ID>
<ID>MaxLineLength:X509UtilitiesTest.kt$X509UtilitiesTest.Companion$Triple(ECDSA_SECP256K1_SHA256,java.security.interfaces.ECPrivateKey::class.java, org.bouncycastle.jce.interfaces.ECPrivateKey::class.java)</ID>
<ID>MaxLineLength:X509UtilitiesTest.kt$X509UtilitiesTest.Companion$Triple(ECDSA_SECP256R1_SHA256,java.security.interfaces.ECPrivateKey::class.java, org.bouncycastle.jce.interfaces.ECPrivateKey::class.java)</ID>
<ID>MaxLineLength:YearMonthSerializer.kt$YearMonthSerializer : Proxy</ID>
<ID>MaxLineLength:YearSerializer.kt$YearSerializer : Proxy</ID>
<ID>MaxLineLength:ZoneIdSerializer.kt$ZoneIdSerializer : Proxy</ID>
<ID>MaxLineLength:ZonedDateTimeSerializer.kt$ZonedDateTimeSerializer : Proxy</ID>
<ID>MaxLineLength:ZonedDateTimeSerializer.kt$ZonedDateTimeSerializer$override fun fromProxy(proxy: ZonedDateTimeProxy): ZonedDateTime</ID>
<ID>MaxLineLength:ZonedDateTimeSerializer.kt$ZonedDateTimeSerializer$override val additionalSerializers: Iterable&lt;CustomSerializer&lt;out Any&gt;&gt; = listOf(LocalDateTimeSerializer(factory), ZoneIdSerializer(factory))</ID>
<ID>MaxLineLength:ZonedDateTimeSerializer.kt$ZonedDateTimeSerializer.Companion$val ofLenient: Method = ZonedDateTime::class.java.getDeclaredMethod("ofLenient", LocalDateTime::class.java, ZoneOffset::class.java, ZoneId::class.java)</ID>
<ID>MaxLineLength:internalAccessTestHelpers.kt$( inputs: List&lt;StateAndRef&lt;ContractState&gt;&gt;, outputs: List&lt;TransactionState&lt;ContractState&gt;&gt;, commands: List&lt;CommandWithParties&lt;CommandData&gt;&gt;, attachments: List&lt;Attachment&gt;, id: SecureHash, notary: Party?, timeWindow: TimeWindow?, privacySalt: PrivacySalt, networkParameters: NetworkParameters, references: List&lt;StateAndRef&lt;ContractState&gt;&gt;, componentGroups: List&lt;ComponentGroup&gt;? = null, serializedInputs: List&lt;SerializedStateAndRef&gt;? = null, serializedReferences: List&lt;SerializedStateAndRef&gt;? = null, isAttachmentTrusted: (Attachment) -&gt; Boolean )</ID>
<ID>MaxLineLength:internalAccessTestHelpers.kt$fun createContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable)</ID>
<ID>MaxLineLength:internalAccessTestHelpers.kt$fun createContractRejection(txId: SecureHash, contract: Contract, cause: Throwable)</ID>
@ -3875,64 +3826,6 @@
<ID>NestedBlockDepth:StatusTransitions.kt$StatusTransitions$ fun verify(tx: LedgerTransaction)</ID>
<ID>NestedBlockDepth:ThrowableSerializer.kt$ThrowableSerializer$override fun fromProxy(proxy: ThrowableProxy): Throwable</ID>
<ID>NestedBlockDepth:TransactionVerifierServiceInternal.kt$Verifier$ private fun verifyConstraintsValidity(contractAttachmentsByContract: Map&lt;ContractClassName, ContractAttachment&gt;)</ID>
<ID>ReturnCount:AbstractPartyDescriptor.kt$AbstractPartyDescriptor$override fun &lt;X : Any&gt; unwrap(value: AbstractParty?, type: Class&lt;X&gt;, options: WrapperOptions): X?</ID>
<ID>ReturnCount:AbstractPartyDescriptor.kt$AbstractPartyDescriptor$override fun &lt;X : Any&gt; wrap(value: X?, options: WrapperOptions): AbstractParty?</ID>
<ID>ReturnCount:Address.kt$Address.Companion$fun &lt;ERROR&gt; validFromRawValue(rawValue: String, mapError: (String) -&gt; ERROR): Validated&lt;Address, ERROR&gt;</ID>
<ID>ReturnCount:Amount.kt$Amount.Companion$ @JvmStatic fun getDisplayTokenSize(token: Any): BigDecimal</ID>
<ID>ReturnCount:AttachmentVersionNumberMigration.kt$AttachmentVersionNumberMigration$override fun execute(database: Database?)</ID>
<ID>ReturnCount:AttachmentsClassLoader.kt$AttachmentsClassLoader$private fun containsClasses(attachment: Attachment): Boolean</ID>
<ID>ReturnCount:BankOfCordaWebApi.kt$BankOfCordaWebApi$ @POST @Path("issue-asset-request") @Consumes(MediaType.APPLICATION_JSON) fun issueAssetRequest(params: IssueRequestParams): Response</ID>
<ID>ReturnCount:BridgeControlListener.kt$BridgeControlListener$private fun processControlMessage(msg: ClientMessage)</ID>
<ID>ReturnCount:ByteArrays.kt$fun hexToBin(ch: Char): Int</ID>
<ID>ReturnCount:ByteBufferStreams.kt$ByteBufferInputStream$@Throws(IOException::class) override fun read(b: ByteArray, offset: Int, length: Int): Int</ID>
<ID>ReturnCount:CheckpointAgent.kt$CheckpointHook$private fun &lt;T&gt; getArrayValue(clazz: Class&lt;T&gt;, value: Any?): String?</ID>
<ID>ReturnCount:CheckpointAgent.kt$CheckpointHook$private fun instrumentClass(clazz: CtClass): CtClass?</ID>
<ID>ReturnCount:CloseableTab.kt$CloseableTab$fun requestClose()</ID>
<ID>ReturnCount:CordaAuthenticationPlugin.kt$CordaAuthenticationPlugin$override fun authenticate(username: String?, credential: String?): AuthInfo</ID>
<ID>ReturnCount:CordaClassResolver.kt$CordaClassResolver$private fun checkClass(type: Class&lt;*&gt;): Registration?</ID>
<ID>ReturnCount:Crypto.kt$Crypto$// Custom key pair generator from an entropy required for various tests. It is similar to deriveKeyPairECDSA, // but the accepted range of the input entropy is more relaxed: // 2 &lt;= entropy &lt; N, where N is the order of base-point G. private fun deriveECDSAKeyPairFromEntropy(signatureScheme: SignatureScheme, entropy: BigInteger): KeyPair</ID>
<ID>ReturnCount:Crypto.kt$Crypto$// Given the domain parameters, this routine deterministically generates an ECDSA key pair // in accordance with X9.62 section 5.2.1 pages 26, 27. private fun deriveKeyPairECDSA(parameterSpec: ECParameterSpec, privateKey: PrivateKey, seed: ByteArray): KeyPair</ID>
<ID>ReturnCount:DBRunnerExtension.kt$DBRunnerExtension$private fun getDatabaseContext(context: ExtensionContext?): TestDatabaseContext?</ID>
<ID>ReturnCount:Emoji.kt$Emoji$fun renderIfSupported(obj: Any): String</ID>
<ID>ReturnCount:FlowLogicRefFactoryImpl.kt$FlowLogicRefFactoryImpl$private fun buildParams(constructor: KFunction&lt;FlowLogic&lt;*&gt;&gt;, args: Map&lt;String, Any?&gt;): HashMap&lt;KParameter, Any?&gt;?</ID>
<ID>ReturnCount:FlowManager.kt$NodeFlowManager.FlowWeightComparator$override fun compare(o1: NodeFlowManager.RegisteredFlowContainer, o2: NodeFlowManager.RegisteredFlowContainer): Int</ID>
<ID>ReturnCount:InteractiveShell.kt$InteractiveShell$ @JvmStatic fun runFlowByNameFragment(nameFragment: String, inputData: String, output: RenderPrintWriter, rpcOps: CordaRPCOps, ansiProgressRenderer: ANSIProgressRenderer, inputObjectMapper: ObjectMapper = createYamlInputMapper(rpcOps))</ID>
<ID>ReturnCount:InteractiveShell.kt$InteractiveShell$@JvmStatic fun runRPCFromString(input: List&lt;String&gt;, out: RenderPrintWriter, context: InvocationContext&lt;out Any&gt;, cordaRPCOps: CordaRPCOps, inputObjectMapper: ObjectMapper): Any?</ID>
<ID>ReturnCount:Interpolators.kt$LinearInterpolator$override fun interpolate(x: Double): Double</ID>
<ID>ReturnCount:JarScanningCordappLoader.kt$JarScanningCordappLoader$private fun parseCordappInfo(manifest: Manifest?, defaultName: String): Cordapp.Info</ID>
<ID>ReturnCount:LocalSerializerFactory.kt$DefaultLocalSerializerFactory$override fun get(actualClass: Class&lt;*&gt;, declaredType: Type): AMQPSerializer&lt;Any&gt;</ID>
<ID>ReturnCount:LocalTypeInformationBuilder.kt$ private fun constructorForDeserialization(type: Type): KFunction&lt;Any&gt;?</ID>
<ID>ReturnCount:LocalTypeInformationBuilder.kt$LocalTypeInformationBuilder$ private fun buildNonAtomic(rawType: Class&lt;*&gt;, type: Type, typeIdentifier: TypeIdentifier, typeParameterInformation: List&lt;LocalTypeInformation&gt;): LocalTypeInformation</ID>
<ID>ReturnCount:LocalTypeInformationBuilder.kt$LocalTypeInformationBuilder$private fun makeConstructorPairedProperty(constructorIndex: Int, descriptor: PropertyDescriptor, constructorInformation: LocalConstructorInformation): LocalPropertyInformation?</ID>
<ID>ReturnCount:Main.kt$Node$fun isAccepted(tx: Transaction): Boolean</ID>
<ID>ReturnCount:MockNodeMessagingService.kt$MockNodeMessagingService$ private fun getNextQueue(q: LinkedBlockingQueue&lt;InMemoryMessagingNetwork.MessageTransfer&gt;, block: Boolean): Pair&lt;InMemoryMessagingNetwork.MessageTransfer, List&lt;Handler&gt;&gt;?</ID>
<ID>ReturnCount:NetworkRegistrationHelper.kt$NodeRegistrationHelper$override fun validateAndGetTlsCrlIssuerCert(): X509Certificate?</ID>
<ID>ReturnCount:NodeAttachmentTrustCalculator.kt$NodeAttachmentTrustCalculator$override fun calculate(attachment: Attachment): Boolean</ID>
<ID>ReturnCount:NodeConfigurationImpl.kt$NodeConfigurationImpl$private fun validateDevModeOptions(): List&lt;String&gt;</ID>
<ID>ReturnCount:NodeSchemaService.kt$NodeSchemaService$// Because schema is always one supported by the state, just delegate. override fun generateMappedObject(state: ContractState, schema: MappedSchema): PersistentState</ID>
<ID>ReturnCount:NodeStartup.kt$NodeStartup$fun initialiseAndRun(cmdLineOptions: SharedNodeCmdLineOptions, afterNodeInitialisation: RunAfterNodeInitialisation, requireCertificates: Boolean = false): Int</ID>
<ID>ReturnCount:NodeStartup.kt$NodeStartup$fun isNodeRunningAt(baseDirectory: Path): Boolean</ID>
<ID>ReturnCount:NodeStartup.kt$NodeStartup$private fun canReadCertificatesDirectory(certDirectory: Path, devMode: Boolean): Boolean</ID>
<ID>ReturnCount:NodeStartup.kt$fun CliWrapperBase.initLogging(baseDirectory: Path): Boolean</ID>
<ID>ReturnCount:NodeVaultService.kt$NodeVaultService$@Suspendable @Throws(StatesNotAvailableException::class) override fun &lt;T : FungibleState&lt;*&gt;&gt; tryLockFungibleStatesForSpending( lockId: UUID, eligibleStatesQuery: QueryCriteria, amount: Amount&lt;*&gt;, contractStateType: Class&lt;out T&gt; ): List&lt;StateAndRef&lt;T&gt;&gt;</ID>
<ID>ReturnCount:ObjectDiffer.kt$ObjectDiffer$fun diff(a: Any?, b: Any?): DiffTree?</ID>
<ID>ReturnCount:PartialMerkleTree.kt$PartialMerkleTree$// Helper function to compute the path. False means go to the left and True to the right. // Because the path is updated recursively, the path is returned in reverse order. private fun leafIndexHelper(leaf: SecureHash, node: PartialTree, path: MutableList&lt;Boolean&gt;): Boolean</ID>
<ID>ReturnCount:PersistentNetworkMapCache.kt$PersistentNetworkMapCache$override fun getPartyInfo(party: Party): PartyInfo?</ID>
<ID>ReturnCount:RPCClientProxyHandler.kt$RPCClientProxyHandler$// This is the general function that transforms a client side RPC to internal Artemis messages. override fun invoke(proxy: Any, method: Method, arguments: Array&lt;out Any?&gt;?): Any?</ID>
<ID>ReturnCount:RPCSecurityManagerImpl.kt$RPCPermissionResolver$override fun resolvePermission(representation: String): Permission</ID>
<ID>ReturnCount:RigorousMock.kt$SpectatorDefaultAnswer$override fun answerImpl(invocation: InvocationOnMock): Any?</ID>
<ID>ReturnCount:SerialFilter.kt$SerialFilter$internal fun applyPredicate(acceptClass: (Class&lt;*&gt;) -&gt; Boolean, serialClass: Class&lt;*&gt;?): Boolean</ID>
<ID>ReturnCount:ServicesForResolutionImpl.kt$ServicesForResolutionImpl$// We may need to recursively chase transactions if there are notary changes. fun inner(stateRef: StateRef, forContractClassName: String?): Attachment</ID>
<ID>ReturnCount:SingleThreadedStateMachineManager.kt$SingleThreadedStateMachineManager$override fun retryFlowFromSafePoint(currentState: StateMachineState)</ID>
<ID>ReturnCount:SingleThreadedStateMachineManager.kt$SingleThreadedStateMachineManager$private fun createFlowFromCheckpoint( id: StateMachineRunId, serializedCheckpoint: SerializedBytes&lt;Checkpoint&gt;, isAnyCheckpointPersisted: Boolean, isStartIdempotent: Boolean, initialDeduplicationHandler: DeduplicationHandler? ): Flow?</ID>
<ID>ReturnCount:SpecificationTest.kt$SpecificationTest$fun parseMax(elements: List&lt;Long&gt;): Valid&lt;Long&gt;</ID>
<ID>ReturnCount:StandaloneShell.kt$StandaloneShell$override fun runProgram(): Int</ID>
<ID>ReturnCount:TransactionBuilder.kt$TransactionBuilder$ private fun handleContract( contractClassName: ContractClassName, inputStates: List&lt;TransactionState&lt;ContractState&gt;&gt;?, outputStates: List&lt;TransactionState&lt;ContractState&gt;&gt;?, explicitContractAttachment: AttachmentId?, services: ServicesForResolution ): Pair&lt;AttachmentId, List&lt;TransactionState&lt;ContractState&gt;&gt;?&gt;</ID>
<ID>ReturnCount:TransactionUtils.kt$ fun &lt;T : Any&gt; deserialiseComponentGroup(componentGroups: List&lt;ComponentGroup&gt;, clazz: KClass&lt;T&gt;, groupEnum: ComponentGroupEnum, forceDeserialize: Boolean = false, factory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = factory.defaultContext): List&lt;T&gt;</ID>
<ID>ReturnCount:TransitionExecutorImpl.kt$TransitionExecutorImpl$@Suspendable override fun executeTransition( fiber: FlowFiber, previousState: StateMachineState, event: Event, transition: TransitionResult, actionExecutor: ActionExecutor ): Pair&lt;FlowContinuation, StateMachineState&gt;</ID>
<ID>ReturnCount:TypeParameterUtils.kt$ private fun inferTypeVariables(actualClass: Class&lt;*&gt;, declaredClass: Class&lt;*&gt;, declaredType: ParameterizedType): Type?</ID>
<ID>ReturnCount:Util.kt$fun &lt;T&gt; debugCompare(perLeft: Perceivable&lt;T&gt;, perRight: Perceivable&lt;T&gt;)</ID>
<ID>ReturnCount:Util.kt$fun debugCompare(arrLeft: Arrangement, arrRight: Arrangement)</ID>
<ID>SpreadOperator:AMQPSerializationScheme.kt$AbstractAMQPSerializationScheme$(*it.whitelist.toTypedArray())</ID>
<ID>SpreadOperator:AbstractNode.kt$FlowStarterImpl$(logicType, *args)</ID>
<ID>SpreadOperator:AbstractParty.kt$AbstractParty$(*bytes)</ID>
@ -3969,7 +3862,6 @@
<ID>SpreadOperator:DemoBench.kt$DemoBench.Companion$(DemoBench::class.java, *args)</ID>
<ID>SpreadOperator:DevCertificatesTest.kt$DevCertificatesTest$(*oldX509Certificates)</ID>
<ID>SpreadOperator:DockerInstantiator.kt$DockerInstantiator$(*it.toTypedArray())</ID>
<ID>SpreadOperator:DriverDSLImpl.kt$DriverDSLImpl$( config, quasarJarPath, debugPort, systemProperties, "512m", null, *extraCmdLineFlag )</ID>
<ID>SpreadOperator:DummyContract.kt$DummyContract.Companion$( /* INPUTS */ *priors.toTypedArray(), /* COMMAND */ Command(cmd, priorState.owner.owningKey), /* OUTPUT */ StateAndContract(state, PROGRAM_ID) )</ID>
<ID>SpreadOperator:DummyContract.kt$DummyContract.Companion$(*items)</ID>
<ID>SpreadOperator:DummyContractV2.kt$DummyContractV2.Companion$( /* INPUTS */ *priors.toTypedArray(), /* COMMAND */ Command(cmd, priorState.owners.map { it.owningKey }), /* OUTPUT */ StateAndContract(state, DummyContractV2.PROGRAM_ID) )</ID>
@ -4528,7 +4420,6 @@
<ID>WildcardImport:AMQPClientSerializationScheme.kt$import net.corda.serialization.internal.*</ID>
<ID>WildcardImport:AMQPClientSerializationScheme.kt$import net.corda.serialization.internal.amqp.*</ID>
<ID>WildcardImport:AMQPRemoteTypeModel.kt$import net.corda.serialization.internal.model.*</ID>
<ID>WildcardImport:AMQPSerializationScheme.kt$import net.corda.core.internal.*</ID>
<ID>WildcardImport:AMQPSerializationScheme.kt$import net.corda.core.serialization.*</ID>
<ID>WildcardImport:AMQPServerSerializationScheme.kt$import net.corda.serialization.internal.amqp.*</ID>
<ID>WildcardImport:AMQPTestSerialiationScheme.kt$import net.corda.serialization.internal.amqp.*</ID>
@ -4971,7 +4862,6 @@
<ID>WildcardImport:NodeInterestRates.kt$import net.corda.core.flows.*</ID>
<ID>WildcardImport:NodeInterestRatesTest.kt$import net.corda.testing.core.*</ID>
<ID>WildcardImport:NodeInterestRatesTest.kt$import org.junit.Assert.*</ID>
<ID>WildcardImport:NodeMonitorModel.kt$import net.corda.core.messaging.*</ID>
<ID>WildcardImport:NodeProcess.kt$import net.corda.core.internal.*</ID>
<ID>WildcardImport:NodeRegistrationTest.kt$import javax.ws.rs.*</ID>
<ID>WildcardImport:NodeSchedulerService.kt$import net.corda.core.internal.*</ID>

View File

@ -183,12 +183,6 @@ style:
OptionalAbstractKeyword:
active: true
excludes: "**/buildSrc/**"
ReturnCount:
active: true
excludes: "**/buildSrc/**"
max: 2
excludedFunctions: "equals"
excludeReturnFromLambda: true
SafeCast:
active: true
excludes: "**/buildSrc/**"

View File

@ -37,6 +37,7 @@ shadowJar {
classifier = null
version = null
zip64 true
exclude '**/Log4j2Plugins.dat'
}
docker{

View File

@ -60,8 +60,7 @@ function downloadTestnetCerts() {
: ${ONE_TIME_DOWNLOAD_KEY:? '$ONE_TIME_DOWNLOAD_KEY must be set as environment variable'}
: ${LOCALITY:? '$LOCALITY (the locality used when registering for Testnet) must be set as environment variable'}
: ${COUNTRY:? '$COUNTRY (the country used when registering for Testnet) must be set as environment variable'}
curl -L -d "{\"x500Name\":{\"locality\":\"${LOCALITY}\", \"country\":\"${COUNTRY}\"}, \"configType\": \"INSTALLSCRIPT\", \"include\": { \"systemdServices\": false, \"cordapps\": false, \"cordaJar\": false, \"cordaWebserverJar\": false, \"scripts\": false} }" \
-H 'Content-Type: application/json' \
curl \
-X POST "https://onboarder.prod.ws.r3.com/api/user/node/generate/one-time-key/redeem/$ONE_TIME_DOWNLOAD_KEY" \
-o "${CERTIFICATES_FOLDER}/certs.zip"
fi

View File

@ -27,6 +27,7 @@ We currently support the following placeholders; they get substituted with the c
```groovy
"|corda_version|"
"|corda_version_lower|"
"|java_version|"
"|kotlin_version|"
"|gradle_plugins_version|"
@ -34,6 +35,7 @@ We currently support the following placeholders; they get substituted with the c
```
If you put one of these in an rst file anywhere (including in a code tag), it will be substituted with the value from `constants.properties`
(which is in the root of the project) at build time.
(which is in the root of the project) at build time. `corda_version_lower` returns the current Corda version in lowercase which is useful
for case sensitive artifacts such as docker images.
The code for this can be found near the top of the conf.py file in the `docs/source` directory.

View File

@ -88,6 +88,9 @@ Unreleased
Note that it's a responsibility of a client application to handle RPC reconnection in case this happens.
See :ref:`setting_jvm_args` and :ref:`memory_usage_and_tuning` for further details.
* Environment variables and system properties can now be provided with underscore separators instead of dots. Neither are case sensitive.
See :ref:`overriding config values <corda_configuration_file_overriding_config>` for more information.
.. _changelog_v4.1:
Version 4.1

View File

@ -371,13 +371,19 @@ More specifically, the behaviour in the second case is a bit more subtle:
.. warning:: In this approach, some events might be lost during a reconnection and not sent from the subscribed ``Observable``\s.
You can enable this graceful form of reconnection by using the ``gracefulReconnect`` parameter in the following way:
You can enable this graceful form of reconnection by using the ``gracefulReconnect`` parameter, which is an object containing 3 optional fields:
* ``onDisconnect``: A callback handler that will be invoked every time the connection is disconnected.
* ``onReconnect``: A callback handler that will be invoked every time the connection is established again after a disconnection.
* ``maxAttempts``: The maximum number of attempts that will be performed per RPC operation. A negative value implies infinite retries. The default value is 5.
This can be used in the following way:
.. container:: codeset
.. sourcecode:: kotlin
val gracefulReconnect = GracefulReconnect(onDisconnect={/*insert disconnect handling*/}, onReconnect{/*insert reconnect handling*/})
val gracefulReconnect = GracefulReconnect(onDisconnect={/*insert disconnect handling*/}, onReconnect{/*insert reconnect handling*/}, maxAttempts = 3)
val cordaClient = CordaRPCClient(nodeRpcAddress)
val cordaRpcOps = cordaClient.start(rpcUserName, rpcUserPassword, gracefulReconnect = gracefulReconnect).proxy
@ -392,7 +398,7 @@ You can enable this graceful form of reconnection by using the ``gracefulReconne
}
void method() {
GracefulReconnect gracefulReconnect = new GracefulReconnect(this::onDisconnect, this::onReconnect);
GracefulReconnect gracefulReconnect = new GracefulReconnect(this::onDisconnect, this::onReconnect, 3);
CordaRPCClient cordaClient = new CordaRPCClient(nodeRpcAddress);
CordaRPCConnection cordaRpcOps = cordaClient.start(rpcUserName, rpcUserPassword, gracefulReconnect);
}

View File

@ -19,6 +19,7 @@ def cordaSourceReadReplace(app, docname, source):
corda_substitutions = {
"|corda_version|" : constants_properties_dict["cordaVersion"],
"|corda_version_lower|" : constants_properties_dict["cordaVersion"].lower(),
"|java_version|" : "8u"+constants_properties_dict["java8MinUpdateVersion"],
"|kotlin_version|" : constants_properties_dict["kotlinVersion"],
"|gradle_plugins_version|" : constants_properties_dict["gradlePluginsVersion"],

View File

@ -39,6 +39,8 @@ To alter this behaviour, the ``on-unknown-config-keys`` command-line argument ca
Overriding values from node.conf
--------------------------------
.. _corda_configuration_file_overriding_config:
Environment variables
For example: ``${NODE_TRUST_STORE_PASSWORD}`` would be replaced by the contents of environment variable ``NODE_TRUST_STORE_PASSWORD`` (see: :ref:`hiding-sensitive-data` section).
@ -54,6 +56,11 @@ JVM options
.. note:: If the same field is overriden by both an environment variable and system property, the system property
takes precedence.
.. note:: Underscores can be used in instead of dots. For example overriding the ``p2pAddress`` with an environment variable can be done
by specifying ``CORDA_P2PADDRESS=host:port``. Variables and properties are not case sensitive. Corda will warn you if a variable
prefixed with ``CORDA`` cannot be mapped to a valid property. Shadowing occurs when two properties
of the same type with the same key are defined. For example having ``CORDA_P2PADDRESS=host:port`` and ``corda_p2paddress=host1:port1``
will raise an exception on startup. This is to prevent hard to spot mistakes.
Configuration file fields
-------------------------

View File

@ -342,7 +342,7 @@ It is also important that we capture the correct amount of contextual informatio
- Flow id (runId, also referred to as `StateMachineRunId`), if logging within a flow
- Other contextual Flow information (eg. counterparty), if logging within a flow
- `FlowStackSnapshot` information for catastrophic flow failures.
Note: this information is not currently supposed to be used in production (???).
Note: this information is not currently supposed to be used in production.
- Session id information for RPC calls
- CorDapp name, if logging from within a CorDapp

View File

@ -21,7 +21,7 @@ In this example, the certificates are stored at ``/home/user/cordaBase/certifica
-v /path/to/cordapps:/opt/corda/cordapps \
-p 10200:10200 \
-p 10201:10201 \
corda/corda-zulu-4.4-snapshot:latest
corda/corda-zulu-java1.8-|corda_version_lower|:latest
As the node runs within a container, several mount points are required:
@ -59,7 +59,7 @@ In this example, we have previously generated a network-parameters file using th
-v /home/user/sharedFolder/network-parameters:/opt/corda/network-parameters \
-p 10200:10200 \
-p 10201:10201 \
corda/corda-zulu-4.4-snapshot:latest
corda/corda-zulu-java1.8-|corda_version_lower|:latest
There is a new mount ``/home/user/sharedFolder/node-infos:/opt/corda/additional-node-infos`` which is used to hold the ``nodeInfo`` of all the nodes within the network.
As the node within the container starts up, it will place it's own nodeInfo into this directory. This will allow other nodes also using this folder to see this new node.
@ -83,7 +83,7 @@ Joining TestNet
-e LOCALITY="London" -e COUNTRY="GB" \
-v /home/user/docker/config:/etc/corda \
-v /home/user/docker/certificates:/opt/corda/certificates \
corda/corda-zulu-4.4-snapshot:latest config-generator --testnet
corda/corda-zulu-java1.8-|corda_version_lower|:latest config-generator --testnet
``$MY_PUBLIC_ADDRESS`` will be the public address that this node will be advertised on.
``$ONE_TIME_DOWNLOAD_KEY`` is the one-time code provided for joining TestNet.
@ -108,7 +108,7 @@ It is now possible to start the node using the generated config and certificates
-v /home/user/corda/samples/bank-of-corda-demo/build/nodes/BankOfCorda/cordapps:/opt/corda/cordapps \
-p 10200:10200 \
-p 10201:10201 \
corda/corda-zulu-4.4-snapshot:latest
corda/corda-zulu-java1.8-|corda_version_lower|:latest
Joining an existing Compatibility Zone
@ -132,7 +132,7 @@ It is possible to configure the name of the Trust Root file by setting the ``TRU
-e MY_EMAIL_ADDRESS="cordauser@r3.com" \
-v /home/user/docker/config:/etc/corda \
-v /home/user/docker/certificates:/opt/corda/certificates \
corda/corda-zulu-4.4-snapshot:latest config-generator --generic
corda/corda-zulu-java1.8-|corda_version_lower|:latest config-generator --generic
Several environment variables must also be passed to the container to allow it to register:
@ -163,5 +163,5 @@ Once the container has finished performing the initial registration, the node ca
-v /home/user/corda/samples/bank-of-corda-demo/build/nodes/BankOfCorda/cordapps:/opt/corda/cordapps \
-p 10200:10200 \
-p 10201:10201 \
corda/corda-zulu-4.4-snapshot:latest
corda/corda-zulu-java1.8-|corda_version_lower|:latest

View File

@ -10,7 +10,7 @@ containers to abstract the complexity of managing a distributed network away fro
The network you build will either be made up of local ``Docker`` nodes *or* of nodes spread across Azure
containers.
For each node a separate Docker image is built based on `corda/corda-zulu-|corda_version| <https://hub.docker.com/r/corda/corda-zulu-|corda_version|>`_.
For each node a separate Docker image is built based on `corda/corda-zulu-java1.8-|corda_version| <https://hub.docker.com/r/corda/corda-zulu-java1.8-|corda_version_lower|>`_.
Unlike the official image, a `node.conf` file and CorDapps are embedded into the image
(they are not externally provided to the running container via volumes/mount points).
More backends may be added in future. The tool is open source, so contributions to add more

View File

@ -195,20 +195,20 @@ parameters will be accepted without user input. The following parameters with th
:start-after: DOCSTART 1
:end-before: DOCEND 1
This behaviour can be turned off by setting the optional node configuration property ``NetworkParameterAcceptanceSettings.autoAcceptEnabled``
This behaviour can be turned off by setting the optional node configuration property ``networkParameterAcceptanceSettings.autoAcceptEnabled``
to ``false``. For example:
.. sourcecode:: guess
...
NetworkParameterAcceptanceSettings {
networkParameterAcceptanceSettings {
autoAcceptEnabled = false
}
...
It is also possible to switch off this behaviour at a more granular parameter level. This can be achieved by specifying the set of
``@AutoAcceptable`` parameters that should not be auto-acceptable in the optional
``NetworkParameterAcceptanceSettings.excludedAutoAcceptableParameters`` node configuration property.
``networkParameterAcceptanceSettings.excludedAutoAcceptableParameters`` node configuration property.
For example, auto-acceptance can be switched off for any updates that change the ``packageOwnership`` map by adding the following to the
node configuration:
@ -216,7 +216,7 @@ node configuration:
.. sourcecode:: guess
...
NetworkParameterAcceptanceSettings {
networkParameterAcceptanceSettings {
excludedAutoAcceptableParameters: ["packageOwnership"]
}
...

View File

@ -71,25 +71,41 @@ PARTY_NAME FK to NODE_INFO_PARTY_CERT
Node identities
^^^^^^^^^^^^^^^
The following two tables are used by the ``IdentityService`` and are created from the NodeInfos.
The following four tables are used by the ``IdentityService`` and are created from the NodeInfos.
They are append only tables used for persistent caching.
They will also be cleared on ``rpc.clearNetworkMapCache()``.
Read more in :doc:`api-identity` and :doc:`node-services`
============================== ==========================================================================================
NODE_IDENTITIES Maps public keys to identities
NODE_IDENTITIES Maps a public key hash to an identity.
============================== ==========================================================================================
PK_HASH The public key.
PK_HASH The public key hash.
IDENTITY_VALUE The certificate chain.
============================== ==========================================================================================
============================== ==========================================================================================
NODE_NAMED_IDENTITIES Maps the X500 name of a participant to a public key.
NODE_NAMED_IDENTITIES Maps the X500 name of a participant to a public key hash.
============================== ==========================================================================================
NAME The x500 name
PK_HASH The public key
NAME The x500 name.
PK_HASH The public key hash.
============================== ==========================================================================================
============================== ==========================================================================================
NODE_IDENTITIES_NO_CERT Maps a public key hash to the X500 name of a participant.
============================== ==========================================================================================
PK_HASH The public key hash.
NAME The x500 name.
============================== ==========================================================================================
============================== ==========================================================================================
NODE_HASH_TO_KEY Maps a public key hash to a public key.
============================== ==========================================================================================
PK_HASH The public key hash.
PUBLIC_KEY The public key.
============================== ==========================================================================================
@ -173,6 +189,7 @@ NODE_TRANSACTIONS Corda transactions in a binary format
TX_ID The hash of the transaction. Primary key.
TRANSACTION_VALUE The binary representation of the transaction.
STATE_MACHINE_RUN_ID The flow id associated with this transaction.
STATUS ``VERIFIED`` or ``UNVERIFIED``
============================== ==========================================================================================
|
@ -224,7 +241,6 @@ PUBLIC_KEY Binary public key
============================== ==========================================================================================
PK_HASH_TO_EXT_ID_MAP Maps public keys to external ids. Mainly used by CorDapps that need to simulate accounts.
============================== ==========================================================================================
ID Primary key
EXTERNAL_ID External id
PUBLIC_KEY_HASH Public key hash
============================== ==========================================================================================

View File

@ -0,0 +1,87 @@
package net.corda.node
import net.corda.core.utilities.getOrThrow
import net.corda.node.logging.logFile
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.NodeParameters
import net.corda.testing.driver.driver
import net.corda.testing.driver.internal.incrementalPortAllocation
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import org.junit.jupiter.api.assertThrows
class NodeConfigParsingTests {
@Test
fun `config is overriden by underscore variable`() {
val portAllocator = incrementalPortAllocation()
val sshPort = portAllocator.nextPort()
driver(DriverParameters(
environmentVariables = mapOf("corda_sshd_port" to sshPort.toString()),
startNodesInProcess = false,
portAllocation = portAllocator)) {
val hasSsh = startNode().get()
.logFile()
.readLines()
.filter { it.contains("SSH server listening on port") }
.any { it.contains(sshPort.toString()) }
assert(hasSsh)
}
}
@Test
fun `config is overriden by case insensitive underscore variable`() {
val portAllocator = incrementalPortAllocation()
val sshPort = portAllocator.nextPort()
driver(DriverParameters(
environmentVariables = mapOf("cORDa_sShD_pOrt" to sshPort.toString()),
startNodesInProcess = false,
portAllocation = portAllocator)) {
val hasSsh = startNode().get()
.logFile()
.readLines()
.filter { it.contains("SSH server listening on port") }
.any { it.contains(sshPort.toString()) }
assert(hasSsh)
}
}
@Test
fun `config is overriden by case insensitive dot variable`() {
val portAllocator = incrementalPortAllocation()
val sshPort = portAllocator.nextPort()
driver(DriverParameters(
environmentVariables = mapOf("cOrda.sShD.pOrt" to sshPort.toString()),
startNodesInProcess = false,
portAllocation = portAllocator)) {
val hasSsh = startNode(NodeParameters()).get()
.logFile()
.readLines()
.filter { it.contains("SSH server listening on port") }
.any { it.contains(sshPort.toString()) }
assert(hasSsh)
}
}
@Test
fun `shadowing is forbidden`() {
val portAllocator = incrementalPortAllocation()
val sshPort = portAllocator.nextPort()
driver(DriverParameters(
environmentVariables = mapOf(
"cOrda_sShD_POrt" to sshPort.toString(),
"cOrda.sShD.pOrt" to sshPort.toString()),
startNodesInProcess = false,
portAllocation = portAllocator,
notarySpecs = emptyList())) {
assertThatThrownBy {
startNode().getOrThrow()
}
}
}
}

View File

@ -10,7 +10,6 @@ import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@Ignore
class NodeRPCTests {
private val CORDA_VERSION_REGEX = "\\d+(\\.\\d+)?(-\\w+)?".toRegex()
private val CORDA_VENDOR = "Corda Open Source"
@ -28,7 +27,6 @@ class NodeRPCTests {
driver(DriverParameters(notarySpecs = emptyList(), cordappsForAllNodes = CORDAPPS, extraCordappPackagesToScan = emptyList())) {
val nodeDiagnosticInfo = startNode().get().rpc.nodeDiagnosticInfo()
assertTrue(nodeDiagnosticInfo.version.matches(CORDA_VERSION_REGEX))
assertTrue(nodeDiagnosticInfo.revision.matches(HEXADECIMAL_REGEX))
assertEquals(PLATFORM_VERSION, nodeDiagnosticInfo.platformVersion)
assertEquals(CORDA_VENDOR, nodeDiagnosticInfo.vendor)
nodeDiagnosticInfo.cordapps.forEach { println("${it.shortName} ${it.type}") }

View File

@ -24,4 +24,4 @@ class NodeStartupPerformanceTests {
println(times.map { it / 1_000_000.0 })
}
}
}
}

View File

@ -0,0 +1,165 @@
package net.corda.node.services.statemachine
import co.paralleluniverse.fibers.Suspendable
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.contracts.PartyAndReference
import net.corda.core.contracts.StateAndRef
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.NotaryException
import net.corda.core.flows.ReceiveFinalityFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party
import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.node.services.Permissions
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyContract.SingleOwnerState
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.CHARLIE_NAME
import net.corda.testing.driver.driver
import net.corda.testing.node.User
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import java.util.Random
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
class FlowHospitalTest {
private val rpcUser = User("user1", "test", permissions = setOf(Permissions.all()))
@Test
fun `when double spend occurs, the flow is successfully deleted on the counterparty`() {
driver {
val charlie = startNode(providedName = CHARLIE_NAME, rpcUsers = listOf(rpcUser)).getOrThrow()
val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(rpcUser)).getOrThrow()
val charlieClient = CordaRPCClient(charlie.rpcAddress).start(rpcUser.username, rpcUser.password).proxy
val aliceClient = CordaRPCClient(alice.rpcAddress).start(rpcUser.username, rpcUser.password).proxy
val aliceParty = aliceClient.nodeInfo().legalIdentities.first()
val (firstLatch, secondLatch) = arrayOf(CountDownLatch(1), CountDownLatch(1))
// case 1: the notary exception is not caught
val stateAndRef = charlieClient.startFlow(::IssueFlow, defaultNotaryIdentity).returnValue.get()
charlieClient.startFlow(::SpendFlow, stateAndRef, aliceParty).returnValue.get()
val firstSubscription = aliceClient.stateMachinesFeed().updates.subscribe {
if (it is StateMachineUpdate.Removed && it.result.isFailure)
firstLatch.countDown()
}
assertThatThrownBy {
charlieClient.startFlow(::SpendFlow, stateAndRef, aliceParty).returnValue.getOrThrow()
}.isInstanceOf(NotaryException::class.java)
assertThat(firstLatch.await(5, TimeUnit.SECONDS)).isTrue()
firstSubscription.unsubscribe()
assertThat(aliceClient.stateMachinesSnapshot()).isEmpty()
// case 2: the notary exception is caught and wrapped in a custom exception
val secondStateAndRef = charlieClient.startFlow(::IssueFlow, defaultNotaryIdentity).returnValue.get()
charlieClient.startFlow(::SpendFlowWithCustomException, secondStateAndRef, aliceParty).returnValue.get()
val secondSubscription = aliceClient.stateMachinesFeed().updates.subscribe{
if (it is StateMachineUpdate.Removed && it.result.isFailure)
secondLatch.countDown()
}
assertThatThrownBy {
charlieClient.startFlow(::SpendFlowWithCustomException, secondStateAndRef, aliceParty).returnValue.getOrThrow()
}.isInstanceOf(DoubleSpendException::class.java)
assertThat(secondLatch.await(5, TimeUnit.SECONDS)).isTrue()
secondSubscription.unsubscribe()
assertThat(aliceClient.stateMachinesSnapshot()).isEmpty()
}
}
@StartableByRPC
class IssueFlow(val notary: Party): FlowLogic<StateAndRef<SingleOwnerState>>() {
@Suspendable
override fun call(): StateAndRef<SingleOwnerState> {
val partyAndReference = PartyAndReference(ourIdentity, OpaqueBytes.of(1))
val txBuilder = DummyContract.generateInitial(Random().nextInt(), notary, partyAndReference)
val signedTransaction = serviceHub.signInitialTransaction(txBuilder, ourIdentity.owningKey)
val notarised = subFlow(FinalityFlow(signedTransaction, emptySet<FlowSession>()))
return notarised.coreTransaction.outRef(0)
}
}
@StartableByRPC
@InitiatingFlow
class SpendFlow(private val stateAndRef: StateAndRef<SingleOwnerState>, private val newOwner: Party): FlowLogic<Unit>() {
@Suspendable
override fun call() {
val txBuilder = DummyContract.move(stateAndRef, newOwner)
val signedTransaction = serviceHub.signInitialTransaction(txBuilder, ourIdentity.owningKey)
val sessionWithCounterParty = initiateFlow(newOwner)
sessionWithCounterParty.sendAndReceive<String>("initial-message")
subFlow(FinalityFlow(signedTransaction, setOf(sessionWithCounterParty)))
}
}
@InitiatedBy(SpendFlow::class)
class AcceptSpendFlow(private val otherSide: FlowSession): FlowLogic<Unit>() {
@Suspendable
override fun call() {
otherSide.receive<String>()
otherSide.send("initial-response")
subFlow(ReceiveFinalityFlow(otherSide))
}
}
@StartableByRPC
@InitiatingFlow
class SpendFlowWithCustomException(private val stateAndRef: StateAndRef<SingleOwnerState>, private val newOwner: Party):
FlowLogic<Unit>() {
@Suspendable
override fun call() {
val txBuilder = DummyContract.move(stateAndRef, newOwner)
val signedTransaction = serviceHub.signInitialTransaction(txBuilder, ourIdentity.owningKey)
val sessionWithCounterParty = initiateFlow(newOwner)
sessionWithCounterParty.sendAndReceive<String>("initial-message")
try {
subFlow(FinalityFlow(signedTransaction, setOf(sessionWithCounterParty)))
} catch (e: NotaryException) {
throw DoubleSpendException("double spend!", e)
}
}
}
@InitiatedBy(SpendFlowWithCustomException::class)
class AcceptSpendFlowWithCustomException(private val otherSide: FlowSession): FlowLogic<Unit>() {
@Suspendable
override fun call() {
otherSide.receive<String>()
otherSide.send("initial-response")
subFlow(ReceiveFinalityFlow(otherSide))
}
}
class DoubleSpendException(message: String, cause: Throwable): FlowException(message, cause)
}

View File

@ -1,6 +1,7 @@
package net.corda.node.services.config
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import net.corda.cliutils.CordaSystemUtils
@ -8,6 +9,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.node.internal.Node
import net.corda.nodeapi.internal.DEV_CA_KEY_STORE_PASS
import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier
import net.corda.nodeapi.internal.config.MutualSslConfiguration
@ -19,7 +21,10 @@ import net.corda.nodeapi.internal.installDevNodeCaCertPath
import net.corda.nodeapi.internal.loadDevCaTrustStore
import net.corda.nodeapi.internal.registerDevP2pCertificates
import org.slf4j.LoggerFactory
import java.lang.IllegalStateException
import java.nio.file.Path
import kotlin.reflect.KClass
import kotlin.reflect.full.memberProperties
fun configOf(vararg pairs: Pair<String, Any?>): Config = ConfigFactory.parseMap(mapOf(*pairs))
operator fun Config.plus(overrides: Map<String, Any?>): Config = ConfigFactory.parseMap(overrides).withFallback(this)
@ -62,9 +67,69 @@ object ConfigHelper {
return finalConfig
}
private fun <T: Any> getCaseSensitivePropertyPath(target : KClass<T>?, path : List<String>) : String {
require(path.isNotEmpty()) { "Path to config property cannot be empty." }
val lookFor = path.first()
target?.memberProperties?.forEach {
if (it.name.toLowerCase() == lookFor.toLowerCase()) {
return if (path.size > 1)
"${it.name}." +
getCaseSensitivePropertyPath(
(it.getter.returnType.classifier as KClass<*>),
path.subList(1, path.size))
else
it.name
}
}
return ""
}
/*
* Gets
*/
private fun Config.cordaEntriesOnly(): Config {
return ConfigFactory.parseMap(toProperties().filterKeys { (it as String).startsWith(CORDA_PROPERTY_PREFIX) }.mapKeys { (it.key as String).removePrefix(CORDA_PROPERTY_PREFIX) })
val cordaPropOccurrences = mutableSetOf<String>()
val badKeyConversions = mutableSetOf<String>()
return ConfigFactory.parseMap(
toProperties()
.mapKeys {
val newKey = (it.key as String)
.replace("_", ".")
.toLowerCase()
if (newKey.contains(CORDA_PROPERTY_PREFIX) && cordaPropOccurrences.contains(newKey)) {
throw ShadowingException(it.key.toString(), newKey)
}
cordaPropOccurrences.add(newKey)
newKey.let { key ->
if (!key.contains(CORDA_PROPERTY_PREFIX))
return@let key
val nodeConfKey = key.removePrefix(CORDA_PROPERTY_PREFIX)
val configPath = getCaseSensitivePropertyPath(
NodeConfigurationImpl::class,
nodeConfKey.split(".")
)
if (nodeConfKey.length != configPath.length) {
Node.printWarning(
"${it.key} (property or environment variable) cannot be mapped to an existing Corda" +
" config property and thus won't be used as a config override!" +
" It won't be passed as a config override! If that was the intention " +
" double check the spelling and ensure there is such config key.")
badKeyConversions.add(configPath)
}
CORDA_PROPERTY_PREFIX + configPath
}
}.filterKeys { it.startsWith(CORDA_PROPERTY_PREFIX) }
.mapKeys { it.key.removePrefix(CORDA_PROPERTY_PREFIX) }
.filterKeys { !badKeyConversions.contains(it) })
}
}

View File

@ -0,0 +1,7 @@
package net.corda.node.services.config
import com.typesafe.config.ConfigException
class ShadowingException(definedProperty : String, convertedProperty : String)
: ConfigException(
"Environment variable $definedProperty is shadowing another property transformed to $convertedProperty")

View File

@ -71,7 +71,7 @@ sealed class Event {
* Initiate a flow. This causes a new session object to be created and returned to the flow. Note that no actual
* communication takes place at this time, only on the first send/receive operation on the session.
*/
data class InitiateFlow(val destination: Destination) : Event()
data class InitiateFlow(val destination: Destination, val wellKnownParty: Party) : Event()
/**
* Signal the entering into a subflow.

View File

@ -17,10 +17,11 @@ import net.corda.core.utilities.UntrustworthyData
class FlowSessionImpl(
override val destination: Destination,
private val wellKnownParty: Party,
val sourceSessionId: SessionId
) : FlowSession() {
override val counterparty: Party get() = checkNotNull(destination as? Party) { "$destination is not a Party" }
override val counterparty: Party get() = wellKnownParty
override fun toString(): String = "FlowSessionImpl(destination=$destination, sourceSessionId=$sourceSessionId)"

View File

@ -355,10 +355,10 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
}
@Suspendable
override fun initiateFlow(destination: Destination): FlowSession {
override fun initiateFlow(destination: Destination, wellKnownParty: Party): FlowSession {
require(destination is Party || destination is AnonymousParty) { "Unsupported destination type ${destination.javaClass.name}" }
val resume = processEventImmediately(
Event.InitiateFlow(destination),
Event.InitiateFlow(destination, wellKnownParty),
isDbTransactionOpenOnEntry = true,
isDbTransactionOpenOnExit = true
) as FlowContinuation.Resume

View File

@ -466,7 +466,7 @@ class SingleThreadedStateMachineManager(
try {
val initiatedFlowFactory = getInitiatedFlowFactory(sessionMessage)
val initiatedSessionId = SessionId.createRandom(secureRandom)
val senderSession = FlowSessionImpl(sender, initiatedSessionId)
val senderSession = FlowSessionImpl(sender, sender, initiatedSessionId)
val flowLogic = initiatedFlowFactory.createFlow(senderSession)
val initiatedFlowInfo = when (initiatedFlowFactory) {
is InitiatedFlowFactory.Core -> FlowInfo(serviceHub.myInfo.platformVersion, "corda")

View File

@ -1,9 +1,13 @@
package net.corda.node.services.statemachine
import net.corda.core.crypto.newSecureRandom
import net.corda.core.flows.FlowException
import net.corda.core.flows.ReceiveFinalityFlow
import net.corda.core.flows.ReceiveTransactionFlow
import net.corda.core.flows.StateMachineRunId
import net.corda.core.flows.UnexpectedFlowEndException
import net.corda.core.identity.Party
import net.corda.core.internal.DeclaredField
import net.corda.core.internal.ThreadBox
import net.corda.core.internal.TimedFlow
import net.corda.core.internal.bufferUntilSubscribed
@ -307,10 +311,19 @@ class StaffedFlowHospital(private val flowMessaging: FlowMessaging, private val
object FinalityDoctor : Staff {
override fun consult(flowFiber: FlowFiber, currentState: StateMachineState, newError: Throwable, history: FlowMedicalHistory): Diagnosis {
return if (currentState.flowLogic is FinalityHandler || isFromReceiveFinalityFlow(newError)) {
return if (currentState.flowLogic is FinalityHandler) {
log.warn("Flow ${flowFiber.id} failed to be finalised. Manual intervention may be required before retrying " +
"the flow by re-starting the node. State machine state: $currentState", newError)
Diagnosis.OVERNIGHT_OBSERVATION
} else if (isFromReceiveFinalityFlow(newError)) {
if (isErrorPropagatedFromCounterparty(newError) && isErrorThrownDuringReceiveFinality(newError)) {
// no need to keep around the flow, since notarisation has already failed at the counterparty.
Diagnosis.NOT_MY_SPECIALTY
} else {
log.warn("Flow ${flowFiber.id} failed to be finalised. Manual intervention may be required before retrying " +
"the flow by re-starting the node. State machine state: $currentState", newError)
Diagnosis.OVERNIGHT_OBSERVATION
}
} else {
Diagnosis.NOT_MY_SPECIALTY
}
@ -319,6 +332,36 @@ class StaffedFlowHospital(private val flowMessaging: FlowMessaging, private val
private fun isFromReceiveFinalityFlow(throwable: Throwable): Boolean {
return throwable.stackTrace.any { it.className == ReceiveFinalityFlow::class.java.name }
}
private fun isErrorPropagatedFromCounterparty(error: Throwable): Boolean {
return when(error) {
is UnexpectedFlowEndException -> {
val peer = DeclaredField<Party?>(UnexpectedFlowEndException::class.java, "peer", error).value
peer != null
}
is FlowException -> {
val peer = DeclaredField<Party?>(FlowException::class.java, "peer", error).value
peer != null
}
else -> false
}
}
/**
* This method will return true if [ReceiveTransactionFlow] is at the top of the stack during the error.
* As a result, if the failure happened during a sub-flow invoked from [ReceiveTransactionFlow], the method will return false.
*
* This is because in the latter case, the transaction might have already been finalised and deleting the flow
* would introduce risk for inconsistency between nodes.
*/
private fun isErrorThrownDuringReceiveFinality(error: Throwable): Boolean {
val strippedStacktrace = error.stackTrace
.filterNot { it?.className?.contains("counter-flow exception from peer") ?: false }
.filterNot { it?.className?.startsWith("net.corda.node.services.statemachine.") ?: false }
return strippedStacktrace.isNotEmpty() &&
strippedStacktrace.first().className.startsWith(ReceiveTransactionFlow::class.qualifiedName!! )
}
}
/**

View File

@ -235,7 +235,7 @@ class TopLevelTransition(
return@builder FlowContinuation.ProcessEvents
}
val sourceSessionId = SessionId.createRandom(context.secureRandom)
val sessionImpl = FlowSessionImpl(event.destination, sourceSessionId)
val sessionImpl = FlowSessionImpl(event.destination, event.wellKnownParty, sourceSessionId)
val newSessions = checkpoint.sessions + (sourceSessionId to SessionState.Uninitiated(event.destination, initiatingSubFlow, sourceSessionId, context.secureRandom.nextLong()))
currentState = currentState.copy(checkpoint = checkpoint.copy(sessions = newSessions))
actions.add(Action.AddSessionBinding(context.id, sourceSessionId))

View File

@ -69,8 +69,8 @@ class FlowFrameworkTests {
@Before
fun setUpMockNet() {
mockNet = InternalMockNetwork(
cordappsForAllNodes = listOf(DUMMY_CONTRACTS_CORDAPP),
servicePeerAllocationStrategy = RoundRobin()
cordappsForAllNodes = listOf(DUMMY_CONTRACTS_CORDAPP),
servicePeerAllocationStrategy = RoundRobin()
)
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
@ -139,13 +139,13 @@ class FlowFrameworkTests {
mockNet.runNetwork()
assertSessionTransfers(
aliceNode sent sessionInit(PingPongFlow::class, payload = 10L) to bobNode,
bobNode sent sessionConfirm() to aliceNode,
bobNode sent sessionData(20L) to aliceNode,
aliceNode sent sessionData(11L) to bobNode,
bobNode sent sessionData(21L) to aliceNode,
aliceNode sent normalEnd to bobNode,
bobNode sent normalEnd to aliceNode
aliceNode sent sessionInit(PingPongFlow::class, payload = 10L) to bobNode,
bobNode sent sessionConfirm() to aliceNode,
bobNode sent sessionData(20L) to aliceNode,
aliceNode sent sessionData(11L) to bobNode,
bobNode sent sessionData(21L) to aliceNode,
aliceNode sent normalEnd to bobNode,
bobNode sent normalEnd to aliceNode
)
}
@ -167,7 +167,8 @@ class FlowFrameworkTests {
it.message is ExistingSessionMessage && it.message.payload === EndSessionMessage
}.subscribe { sessionEndReceived.release() }
val resultFuture = aliceNode.services.startFlow(
WaitForOtherSideEndBeforeSendAndReceive(bob, sessionEndReceived)).resultFuture
WaitForOtherSideEndBeforeSendAndReceive(bob, sessionEndReceived)
).resultFuture
mockNet.runNetwork()
assertThatExceptionOfType(UnexpectedFlowEndException::class.java).isThrownBy {
resultFuture.getOrThrow()
@ -186,10 +187,10 @@ class FlowFrameworkTests {
mockNet.runNetwork()
assertThatExceptionOfType(MyFlowException::class.java)
.isThrownBy { receivingFiber.resultFuture.getOrThrow() }
.withMessage("Nothing useful")
.withStackTraceContaining(ReceiveFlow::class.java.name) // Make sure the stack trace is that of the receiving flow
.withStackTraceContaining("Received counter-flow exception from peer")
.isThrownBy { receivingFiber.resultFuture.getOrThrow() }
.withMessage("Nothing useful")
.withStackTraceContaining(ReceiveFlow::class.java.name) // Make sure the stack trace is that of the receiving flow
.withStackTraceContaining("Received counter-flow exception from peer")
bobNode.database.transaction {
assertThat(bobNode.internals.checkpointStorage.checkpoints()).isEmpty()
}
@ -197,15 +198,15 @@ class FlowFrameworkTests {
assertThat(receivingFiber.state).isEqualTo(Strand.State.WAITING)
assertThat((erroringFlow.get().stateMachine as FlowStateMachineImpl).state).isEqualTo(Strand.State.WAITING)
assertThat(erroringFlowSteps.get()).containsExactly(
Notification.createOnNext(ProgressTracker.STARTING),
Notification.createOnNext(ExceptionFlow.START_STEP),
Notification.createOnError(erroringFlow.get().exceptionThrown)
Notification.createOnNext(ProgressTracker.STARTING),
Notification.createOnNext(ExceptionFlow.START_STEP),
Notification.createOnError(erroringFlow.get().exceptionThrown)
)
assertSessionTransfers(
aliceNode sent sessionInit(ReceiveFlow::class) to bobNode,
bobNode sent sessionConfirm() to aliceNode,
bobNode sent errorMessage(erroringFlow.get().exceptionThrown) to aliceNode
aliceNode sent sessionInit(ReceiveFlow::class) to bobNode,
bobNode sent sessionConfirm() to aliceNode,
bobNode sent errorMessage(erroringFlow.get().exceptionThrown) to aliceNode
)
// Make sure the original stack trace isn't sent down the wire
val lastMessage = receivedSessionMessages.last().message as ExistingSessionMessage
@ -296,8 +297,8 @@ class FlowFrameworkTests {
@Test
fun waitForLedgerCommit() {
val ptx = TransactionBuilder(notary = notaryIdentity)
.addOutputState(DummyState(), DummyContract.PROGRAM_ID)
.addCommand(dummyCommand(alice.owningKey))
.addOutputState(DummyState(), DummyContract.PROGRAM_ID)
.addCommand(dummyCommand(alice.owningKey))
val stx = aliceNode.services.signInitialTransaction(ptx)
val committerStx = aliceNode.registerCordappFlowFactory(CommitterFlow::class) {
@ -313,8 +314,8 @@ class FlowFrameworkTests {
@Test
fun `waitForLedgerCommit throws exception if any active session ends in error`() {
val ptx = TransactionBuilder(notary = notaryIdentity)
.addOutputState(DummyState(), DummyContract.PROGRAM_ID)
.addCommand(dummyCommand())
.addOutputState(DummyState(), DummyContract.PROGRAM_ID)
.addCommand(dummyCommand())
val stx = aliceNode.services.signInitialTransaction(ptx)
aliceNode.registerCordappFlowFactory(WaitForLedgerCommitFlow::class) { ExceptionFlow { throw Exception("Error") } }
@ -354,8 +355,8 @@ class FlowFrameworkTests {
val result = aliceNode.services.startFlow(UpgradedFlow(bob)).resultFuture
mockNet.runNetwork()
assertThat(receivedSessionMessages).startsWith(
aliceNode sent sessionInit(UpgradedFlow::class, flowVersion = 2) to bobNode,
bobNode sent sessionConfirm(flowVersion = 1) to aliceNode
aliceNode sent sessionInit(UpgradedFlow::class, flowVersion = 2) to bobNode,
bobNode sent sessionConfirm(flowVersion = 1) to aliceNode
)
val (receivedPayload, node2FlowVersion) = result.getOrThrow()
assertThat(receivedPayload).isEqualTo("Old initiated")
@ -369,8 +370,8 @@ class FlowFrameworkTests {
val flowInfo = aliceNode.services.startFlow(initiatingFlow).resultFuture
mockNet.runNetwork()
assertThat(receivedSessionMessages).startsWith(
aliceNode sent sessionInit(SendFlow::class, flowVersion = 1, payload = "Old initiating") to bobNode,
bobNode sent sessionConfirm(flowVersion = 2) to aliceNode
aliceNode sent sessionInit(SendFlow::class, flowVersion = 1, payload = "Old initiating") to bobNode,
bobNode sent sessionConfirm(flowVersion = 2) to aliceNode
)
assertThat(flowInfo.get().flowVersion).isEqualTo(2)
}
@ -380,8 +381,8 @@ class FlowFrameworkTests {
val future = aliceNode.services.startFlow(NeverRegisteredFlow("Hello", bob)).resultFuture
mockNet.runNetwork()
assertThatExceptionOfType(UnexpectedFlowEndException::class.java)
.isThrownBy { future.getOrThrow() }
.withMessageEndingWith("${NeverRegisteredFlow::class.java.name} is not registered")
.isThrownBy { future.getOrThrow() }
.withMessageEndingWith("${NeverRegisteredFlow::class.java.name} is not registered")
}
@Test
@ -441,9 +442,9 @@ class FlowFrameworkTests {
erroringFlowFuture.getOrThrow()
val flowSteps = erroringFlowSteps.get()
assertThat(flowSteps).containsExactly(
Notification.createOnNext(ProgressTracker.STARTING),
Notification.createOnNext(ExceptionFlow.START_STEP),
Notification.createOnError(erroringFlowFuture.get().exceptionThrown)
Notification.createOnNext(ProgressTracker.STARTING),
Notification.createOnNext(ExceptionFlow.START_STEP),
Notification.createOnError(erroringFlowFuture.get().exceptionThrown)
)
val receiveFlowException = assertFailsWith(UnexpectedFlowEndException::class) {
@ -451,28 +452,28 @@ class FlowFrameworkTests {
}
assertThat(receiveFlowException.message).doesNotContain("evil bug!")
assertThat(receiveFlowSteps.get()).containsExactly(
Notification.createOnNext(ProgressTracker.STARTING),
Notification.createOnNext(ReceiveFlow.START_STEP),
Notification.createOnError(receiveFlowException)
Notification.createOnNext(ProgressTracker.STARTING),
Notification.createOnNext(ReceiveFlow.START_STEP),
Notification.createOnError(receiveFlowException)
)
assertSessionTransfers(
aliceNode sent sessionInit(ReceiveFlow::class) to bobNode,
bobNode sent sessionConfirm() to aliceNode,
bobNode sent errorMessage() to aliceNode
aliceNode sent sessionInit(ReceiveFlow::class) to bobNode,
bobNode sent sessionConfirm() to aliceNode,
bobNode sent errorMessage() to aliceNode
)
}
@Test
fun `initiating flow using unknown AnonymousParty`() {
val anonymousBob = bobNode.services.keyManagementService.freshKeyAndCert(bobNode.info.legalIdentitiesAndCerts.single(), false)
.party.anonymise()
.party.anonymise()
bobNode.registerCordappFlowFactory(SendAndReceiveFlow::class) { SingleInlinedSubFlow(it) }
val result = aliceNode.services.startFlow(SendAndReceiveFlow(anonymousBob, "Hello")).resultFuture
mockNet.runNetwork()
assertThatIllegalArgumentException()
.isThrownBy { result.getOrThrow() }
.withMessage("We do not know who $anonymousBob belongs to")
.isThrownBy { result.getOrThrow() }
.withMessage("Could not resolve destination: $anonymousBob")
}
@Test
@ -497,16 +498,18 @@ class FlowFrameworkTests {
private val FlowLogic<*>.progressSteps: CordaFuture<List<Notification<ProgressTracker.Step>>>
get() {
return progressTracker!!.changes
.ofType(Change.Position::class.java)
.map { it.newStep }
.materialize()
.toList()
.toFuture()
.ofType(Change.Position::class.java)
.map { it.newStep }
.materialize()
.toList()
.toFuture()
}
@InitiatingFlow
private class WaitForOtherSideEndBeforeSendAndReceive(val otherParty: Party,
@Transient val receivedOtherFlowEnd: Semaphore) : FlowLogic<Unit>() {
private class WaitForOtherSideEndBeforeSendAndReceive(
val otherParty: Party,
@Transient val receivedOtherFlowEnd: Semaphore
) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
// Kick off the flow on the other side ...
@ -626,7 +629,8 @@ class FlowFrameworkTests {
//endregion Helpers
}
internal fun sessionConfirm(flowVersion: Int = 1) = ExistingSessionMessage(SessionId(0), ConfirmSessionMessage(SessionId(0), FlowInfo(flowVersion, "")))
internal fun sessionConfirm(flowVersion: Int = 1) =
ExistingSessionMessage(SessionId(0), ConfirmSessionMessage(SessionId(0), FlowInfo(flowVersion, "")))
internal inline fun <reified P : FlowLogic<*>> TestStartedNode.getSingleFlow(): Pair<P, CordaFuture<*>> {
return smm.findStateMachines(P::class.java).single()
@ -637,17 +641,17 @@ private fun sanitise(message: SessionMessage) = when (message) {
is ExistingSessionMessage -> {
val payload = message.payload
message.copy(
recipientSessionId = SessionId(0),
payload = when (payload) {
is ConfirmSessionMessage -> payload.copy(
initiatedSessionId = SessionId(0),
initiatedFlowInfo = payload.initiatedFlowInfo.copy(appName = "")
)
is ErrorSessionMessage -> payload.copy(
errorId = 0
)
else -> payload
}
recipientSessionId = SessionId(0),
payload = when (payload) {
is ConfirmSessionMessage -> payload.copy(
initiatedSessionId = SessionId(0),
initiatedFlowInfo = payload.initiatedFlowInfo.copy(appName = "")
)
is ErrorSessionMessage -> payload.copy(
errorId = 0
)
else -> payload
}
)
}
}
@ -667,10 +671,12 @@ internal fun TestStartedNode.sendSessionMessage(message: SessionMessage, destina
}
}
internal fun errorMessage(errorResponse: FlowException? = null) = ExistingSessionMessage(SessionId(0), ErrorSessionMessage(errorResponse, 0))
internal fun errorMessage(errorResponse: FlowException? = null) =
ExistingSessionMessage(SessionId(0), ErrorSessionMessage(errorResponse, 0))
internal infix fun TestStartedNode.sent(message: SessionMessage): Pair<Int, SessionMessage> = Pair(internals.id, message)
internal infix fun Pair<Int, SessionMessage>.to(node: TestStartedNode): SessionTransfer = SessionTransfer(first, second, node.network.myAddress)
internal infix fun Pair<Int, SessionMessage>.to(node: TestStartedNode): SessionTransfer =
SessionTransfer(first, second, node.network.myAddress)
internal data class SessionTransfer(val from: Int, val message: SessionMessage, val to: MessageRecipients) {
val isPayloadTransfer: Boolean
@ -785,7 +791,11 @@ internal class MyFlowException(override val message: String) : FlowException() {
internal class MyPeerFlowException(override val message: String, val peer: Party) : FlowException()
@InitiatingFlow
internal class SendAndReceiveFlow(private val destination: Destination, private val payload: Any, private val otherPartySession: FlowSession? = null) : FlowLogic<Any>() {
internal class SendAndReceiveFlow(
private val destination: Destination,
private val payload: Any,
private val otherPartySession: FlowSession? = null
) : FlowLogic<Any>() {
constructor(otherPartySession: FlowSession, payload: Any) : this(otherPartySession.counterparty, payload, otherPartySession)
@Suspendable
@ -795,7 +805,8 @@ internal class SendAndReceiveFlow(private val destination: Destination, private
}
@InitiatingFlow
internal class PingPongFlow(private val otherParty: Party, private val payload: Long, private val otherPartySession: FlowSession? = null) : FlowLogic<Unit>() {
internal class PingPongFlow(private val otherParty: Party, private val payload: Long, private val otherPartySession: FlowSession? = null) :
FlowLogic<Unit>() {
constructor(otherPartySession: FlowSession, payload: Long) : this(otherPartySession.counterparty, payload, otherPartySession)
@Transient

View File

@ -6,7 +6,8 @@ import net.corda.core.DeleteForDJVM
import net.corda.core.KeepForDJVM
import net.corda.core.StubOutForDJVM
import net.corda.core.cordapp.Cordapp
import net.corda.core.internal.*
import net.corda.core.internal.toSynchronised
import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.*
import net.corda.core.utilities.ByteSequence
import net.corda.serialization.internal.CordaSerializationMagic
@ -46,14 +47,20 @@ abstract class AbstractAMQPSerializationScheme(
val sff: SerializerFactoryFactory = createSerializerFactoryFactory()
) : SerializationScheme {
@DeleteForDJVM
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, cordapps.serializationWhitelists, AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised())
constructor(cordapps: List<Cordapp>) : this(
cordapps.customSerializers,
cordapps.serializationWhitelists,
AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised()
)
// This is a bit gross but a broader check for ConcurrentMap is not allowed inside DJVM.
private val serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory> = if (maybeNotConcurrentSerializerFactoriesForContexts is AccessOrderLinkedHashMap<*, *>) {
Collections.synchronizedMap(maybeNotConcurrentSerializerFactoriesForContexts)
} else {
maybeNotConcurrentSerializerFactoriesForContexts
}
private val serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory> =
if (maybeNotConcurrentSerializerFactoriesForContexts is
AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>) {
Collections.synchronizedMap(maybeNotConcurrentSerializerFactoriesForContexts)
} else {
maybeNotConcurrentSerializerFactoriesForContexts
}
companion object {
private val serializationWhitelists: List<SerializationWhitelist> by lazy { listOf(DefaultWhitelist) }
@ -68,45 +75,16 @@ abstract class AbstractAMQPSerializationScheme(
}
private fun registerCustomSerializers(context: SerializationContext, factory: SerializerFactory) {
with(factory) {
register(publicKeySerializer)
register(net.corda.serialization.internal.amqp.custom.PrivateKeySerializer)
register(net.corda.serialization.internal.amqp.custom.ThrowableSerializer(this))
register(net.corda.serialization.internal.amqp.custom.BigDecimalSerializer)
register(net.corda.serialization.internal.amqp.custom.BigIntegerSerializer)
register(net.corda.serialization.internal.amqp.custom.CurrencySerializer)
register(net.corda.serialization.internal.amqp.custom.OpaqueBytesSubSequenceSerializer(this))
register(net.corda.serialization.internal.amqp.custom.InstantSerializer(this))
register(net.corda.serialization.internal.amqp.custom.DurationSerializer(this))
register(net.corda.serialization.internal.amqp.custom.LocalDateSerializer(this))
register(net.corda.serialization.internal.amqp.custom.LocalDateTimeSerializer(this))
register(net.corda.serialization.internal.amqp.custom.LocalTimeSerializer(this))
register(net.corda.serialization.internal.amqp.custom.ZonedDateTimeSerializer(this))
register(net.corda.serialization.internal.amqp.custom.ZoneIdSerializer(this))
register(net.corda.serialization.internal.amqp.custom.OffsetTimeSerializer(this))
register(net.corda.serialization.internal.amqp.custom.OffsetDateTimeSerializer(this))
register(net.corda.serialization.internal.amqp.custom.OptionalSerializer(this))
register(net.corda.serialization.internal.amqp.custom.YearSerializer(this))
register(net.corda.serialization.internal.amqp.custom.YearMonthSerializer(this))
register(net.corda.serialization.internal.amqp.custom.MonthDaySerializer(this))
register(net.corda.serialization.internal.amqp.custom.PeriodSerializer(this))
register(net.corda.serialization.internal.amqp.custom.ClassSerializer(this))
register(net.corda.serialization.internal.amqp.custom.X509CertificateSerializer)
register(net.corda.serialization.internal.amqp.custom.X509CRLSerializer)
register(net.corda.serialization.internal.amqp.custom.CertPathSerializer(this))
register(net.corda.serialization.internal.amqp.custom.StringBufferSerializer)
register(net.corda.serialization.internal.amqp.custom.InputStreamSerializer)
register(net.corda.serialization.internal.amqp.custom.BitSetSerializer(this))
register(net.corda.serialization.internal.amqp.custom.EnumSetSerializer(this))
register(net.corda.serialization.internal.amqp.custom.ContractAttachmentSerializer(this))
registerNonDeterministicSerializers(factory)
}
factory.register(publicKeySerializer)
registerCustomSerializers(factory)
// This step is registering custom serializers, which have been added after node initialisation (i.e. via attachments during transaction verification).
// Note: the order between the registration of customSerializers and cordappCustomSerializers must be preserved as-is. The reason is the following:
// Currently, the serialization infrastructure does not support multiple versions of a class (the first one that is registered dominates).
// As a result, when inside a context with attachments class loader, we prioritize serializers loaded on-demand from attachments to serializers that had been
// loaded during node initialisation, by scanning the cordapps folder.
// This step is registering custom serializers, which have been added after node initialisation (i.e. via attachments during
// transaction verification).
// Note: the order between the registration of customSerializers and cordappCustomSerializers must be preserved as-is. The reason
// is the following:
// Currently, the serialization infrastructure does not support multiple versions of a class (the first one that is
// registered dominates). As a result, when inside a context with attachments class loader, we prioritize serializers loaded
// on-demand from attachments to serializers that had been loaded during node initialisation, by scanning the cordapps folder.
context.customSerializers.forEach { customSerializer ->
factory.registerExternal(CorDappCustomSerializer(customSerializer, factory))
}
@ -126,22 +104,12 @@ abstract class AbstractAMQPSerializationScheme(
factory.addToWhitelist(*it.whitelist.toTypedArray())
}
cordappSerializationWhitelists.forEach {
it.whitelist.forEach {
clazz -> factory.addToWhitelist(clazz)
it.whitelist.forEach { clazz ->
factory.addToWhitelist(clazz)
}
}
}
/*
* Register the serializers which will be excluded from the DJVM.
*/
@StubOutForDJVM
private fun registerNonDeterministicSerializers(factory: SerializerFactory) {
with(factory) {
register(net.corda.serialization.internal.amqp.custom.SimpleStringSerializer)
}
}
protected abstract fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory
protected abstract fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory
@ -189,3 +157,49 @@ abstract class AbstractAMQPSerializationScheme(
protected fun canDeserializeVersion(magic: CordaSerializationMagic) = magic == amqpMagic
}
fun registerCustomSerializers(factory: SerializerFactory) {
with(factory) {
register(net.corda.serialization.internal.amqp.custom.PrivateKeySerializer)
register(net.corda.serialization.internal.amqp.custom.ThrowableSerializer(this))
register(net.corda.serialization.internal.amqp.custom.BigDecimalSerializer)
register(net.corda.serialization.internal.amqp.custom.BigIntegerSerializer)
register(net.corda.serialization.internal.amqp.custom.CurrencySerializer)
register(net.corda.serialization.internal.amqp.custom.OpaqueBytesSubSequenceSerializer(this))
register(net.corda.serialization.internal.amqp.custom.InstantSerializer(this))
register(net.corda.serialization.internal.amqp.custom.DurationSerializer(this))
register(net.corda.serialization.internal.amqp.custom.LocalDateSerializer(this))
register(net.corda.serialization.internal.amqp.custom.LocalDateTimeSerializer(this))
register(net.corda.serialization.internal.amqp.custom.LocalTimeSerializer(this))
register(net.corda.serialization.internal.amqp.custom.ZonedDateTimeSerializer(this))
register(net.corda.serialization.internal.amqp.custom.ZoneIdSerializer(this))
register(net.corda.serialization.internal.amqp.custom.OffsetTimeSerializer(this))
register(net.corda.serialization.internal.amqp.custom.OffsetDateTimeSerializer(this))
register(net.corda.serialization.internal.amqp.custom.OptionalSerializer(this))
register(net.corda.serialization.internal.amqp.custom.YearSerializer(this))
register(net.corda.serialization.internal.amqp.custom.YearMonthSerializer(this))
register(net.corda.serialization.internal.amqp.custom.MonthDaySerializer(this))
register(net.corda.serialization.internal.amqp.custom.PeriodSerializer(this))
register(net.corda.serialization.internal.amqp.custom.ClassSerializer(this))
register(net.corda.serialization.internal.amqp.custom.X509CertificateSerializer)
register(net.corda.serialization.internal.amqp.custom.X509CRLSerializer)
register(net.corda.serialization.internal.amqp.custom.CertPathSerializer(this))
register(net.corda.serialization.internal.amqp.custom.StringBufferSerializer)
register(net.corda.serialization.internal.amqp.custom.InputStreamSerializer)
register(net.corda.serialization.internal.amqp.custom.BitSetSerializer(this))
register(net.corda.serialization.internal.amqp.custom.EnumSetSerializer(this))
register(net.corda.serialization.internal.amqp.custom.ContractAttachmentSerializer(this))
}
registerNonDeterministicSerializers(factory)
}
/*
* Register the serializers which will be excluded from the DJVM.
*/
@StubOutForDJVM
private fun registerNonDeterministicSerializers(factory: SerializerFactory) {
with(factory) {
register(net.corda.serialization.internal.amqp.custom.SimpleStringSerializer)
}
}

View File

@ -9,7 +9,13 @@ import java.util.*
* A serializer that writes out a [BitSet] as an integer number of bits, plus the necessary number of bytes to encode that
* many bits.
*/
class BitSetSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<BitSet, BitSetSerializer.BitSetProxy>(BitSet::class.java, BitSetProxy::class.java, factory) {
class BitSetSerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<BitSet, BitSetSerializer.BitSetProxy>(
BitSet::class.java,
BitSetProxy::class.java,
factory
) {
override fun toProxy(obj: BitSet): BitSetProxy = BitSetProxy(obj.toByteArray())
override fun fromProxy(proxy: BitSetProxy): BitSet = BitSet.valueOf(proxy.bytes)

View File

@ -8,8 +8,13 @@ import java.security.cert.CertPath
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
class CertPathSerializer(factory: SerializerFactory)
: CustomSerializer.Proxy<CertPath, CertPathSerializer.CertPathProxy>(CertPath::class.java, CertPathProxy::class.java, factory) {
class CertPathSerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<CertPath, CertPathSerializer.CertPathProxy>(
CertPath::class.java,
CertPathProxy::class.java,
factory
) {
override fun toProxy(obj: CertPath): CertPathProxy = CertPathProxy(obj.type, obj.encoded)
override fun fromProxy(proxy: CertPathProxy): CertPath {

View File

@ -6,7 +6,6 @@ import net.corda.core.utilities.trace
import net.corda.serialization.internal.amqp.AMQPNotSerializableException
import net.corda.serialization.internal.amqp.CustomSerializer
import net.corda.serialization.internal.amqp.LocalSerializerFactory
import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.custom.ClassSerializer.ClassProxy
/**

View File

@ -15,16 +15,27 @@ import java.security.PublicKey
* A serializer for [ContractAttachment] that uses a proxy object to write out the full attachment eagerly.
* @param factory the serializerFactory
*/
class ContractAttachmentSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<ContractAttachment,
ContractAttachmentSerializer.ContractAttachmentProxy>(ContractAttachment::class.java,
ContractAttachmentProxy::class.java, factory) {
class ContractAttachmentSerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<ContractAttachment, ContractAttachmentSerializer.ContractAttachmentProxy>(
ContractAttachment::class.java,
ContractAttachmentProxy::class.java,
factory
) {
override fun toProxy(obj: ContractAttachment): ContractAttachmentProxy {
val bytes = try {
obj.attachment.open().readFully()
} catch (e: Exception) {
throw MissingAttachmentsException(listOf(obj.id))
}
return ContractAttachmentProxy(GeneratedAttachment(bytes, obj.uploader), obj.contract, obj.additionalContracts, obj.uploader, obj.signerKeys, obj.version)
return ContractAttachmentProxy(
GeneratedAttachment(bytes, obj.uploader),
obj.contract,
obj.additionalContracts,
obj.uploader,
obj.signerKeys,
obj.version
)
}
override fun fromProxy(proxy: ContractAttachmentProxy): ContractAttachment {
@ -32,5 +43,12 @@ class ContractAttachmentSerializer(factory: SerializerFactory) : CustomSerialize
}
@KeepForDJVM
data class ContractAttachmentProxy(val attachment: Attachment, val contract: ContractClassName, val contracts: Set<ContractClassName>, val uploader: String?, val signers: List<PublicKey>, val version: Int)
data class ContractAttachmentProxy(
val attachment: Attachment,
val contract: ContractClassName,
val contracts: Set<ContractClassName>,
val uploader: String?,
val signers: List<PublicKey>,
val version: Int
)
}

View File

@ -6,7 +6,10 @@ import java.util.*
/**
* A custom serializer for the [Currency] class, utilizing the currency code string representation.
*/
object CurrencySerializer : CustomSerializer.ToString<Currency>(Currency::class.java,
object CurrencySerializer
: CustomSerializer.ToString<Currency>(
Currency::class.java,
withInheritance = false,
maker = { Currency.getInstance(it) },
unmaker = { it.currencyCode })
unmaker = { it.currencyCode }
)

View File

@ -8,7 +8,13 @@ import java.time.Duration
/**
* A serializer for [Duration] that uses a proxy object to write out the seconds and the nanos.
*/
class DurationSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<Duration, DurationSerializer.DurationProxy>(Duration::class.java, DurationProxy::class.java, factory) {
class DurationSerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<Duration, DurationSerializer.DurationProxy>(
Duration::class.java,
DurationProxy::class.java,
factory
) {
override fun toProxy(obj: Duration): DurationProxy = DurationProxy(obj.seconds, obj.nano)
override fun fromProxy(proxy: DurationProxy): Duration = Duration.ofSeconds(proxy.seconds, proxy.nanos.toLong())

View File

@ -10,7 +10,13 @@ import java.util.*
/**
* A serializer that writes out an [EnumSet] as a type, plus list of instances in the set.
*/
class EnumSetSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<EnumSet<*>, EnumSetSerializer.EnumSetProxy>(EnumSet::class.java, EnumSetProxy::class.java, factory) {
class EnumSetSerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<EnumSet<*>, EnumSetSerializer.EnumSetProxy>(
EnumSet::class.java,
EnumSetProxy::class.java,
factory
) {
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = listOf(ClassSerializer(factory))
override fun toProxy(obj: EnumSet<*>): EnumSetProxy = EnumSetProxy(elementType(obj), obj.toList())

View File

@ -11,7 +11,10 @@ import java.lang.reflect.Type
/**
* A serializer that writes out the content of an input stream as bytes and deserializes into a [ByteArrayInputStream].
*/
object InputStreamSerializer : CustomSerializer.Implements<InputStream>(InputStream::class.java) {
object InputStreamSerializer
: CustomSerializer.Implements<InputStream>(
InputStream::class.java
) {
override val revealSubclassesInSchema: Boolean = true
override val schemaForDocumentation = Schema(

View File

@ -8,7 +8,13 @@ import java.time.Instant
/**
* A serializer for [Instant] that uses a proxy object to write out the seconds since the epoch and the nanos.
*/
class InstantSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<Instant, InstantSerializer.InstantProxy>(Instant::class.java, InstantProxy::class.java, factory) {
class InstantSerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<Instant, InstantSerializer.InstantProxy>(
Instant::class.java,
InstantProxy::class.java,
factory
) {
override fun toProxy(obj: Instant): InstantProxy = InstantProxy(obj.epochSecond, obj.nano)
override fun fromProxy(proxy: InstantProxy): Instant = Instant.ofEpochSecond(proxy.epochSeconds, proxy.nanos.toLong())

View File

@ -8,7 +8,13 @@ import java.time.LocalDate
/**
* A serializer for [LocalDate] that uses a proxy object to write out the year, month and day.
*/
class LocalDateSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<LocalDate, LocalDateSerializer.LocalDateProxy>(LocalDate::class.java, LocalDateProxy::class.java, factory) {
class LocalDateSerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<LocalDate, LocalDateSerializer.LocalDateProxy>(
LocalDate::class.java,
LocalDateProxy::class.java,
factory
) {
override fun toProxy(obj: LocalDate): LocalDateProxy = LocalDateProxy(obj.year, obj.monthValue.toByte(), obj.dayOfMonth.toByte())
override fun fromProxy(proxy: LocalDateProxy): LocalDate = LocalDate.of(proxy.year, proxy.month.toInt(), proxy.day.toInt())

View File

@ -10,8 +10,17 @@ import java.time.LocalTime
/**
* A serializer for [LocalDateTime] that uses a proxy object to write out the date and time.
*/
class LocalDateTimeSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<LocalDateTime, LocalDateTimeSerializer.LocalDateTimeProxy>(LocalDateTime::class.java, LocalDateTimeProxy::class.java, factory) {
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = listOf(LocalDateSerializer(factory), LocalTimeSerializer(factory))
class LocalDateTimeSerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<LocalDateTime, LocalDateTimeSerializer.LocalDateTimeProxy>(
LocalDateTime::class.java,
LocalDateTimeProxy::class.java,
factory
) {
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = listOf(
LocalDateSerializer(factory),
LocalTimeSerializer(factory)
)
override fun toProxy(obj: LocalDateTime): LocalDateTimeProxy = LocalDateTimeProxy(obj.toLocalDate(), obj.toLocalTime())

View File

@ -8,10 +8,26 @@ import java.time.LocalTime
/**
* A serializer for [LocalTime] that uses a proxy object to write out the hours, minutes, seconds and the nanos.
*/
class LocalTimeSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<LocalTime, LocalTimeSerializer.LocalTimeProxy>(LocalTime::class.java, LocalTimeProxy::class.java, factory) {
override fun toProxy(obj: LocalTime): LocalTimeProxy = LocalTimeProxy(obj.hour.toByte(), obj.minute.toByte(), obj.second.toByte(), obj.nano)
class LocalTimeSerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<LocalTime, LocalTimeSerializer.LocalTimeProxy>(
LocalTime::class.java,
LocalTimeProxy::class.java,
factory
) {
override fun toProxy(obj: LocalTime): LocalTimeProxy = LocalTimeProxy(
obj.hour.toByte(),
obj.minute.toByte(),
obj.second.toByte(),
obj.nano
)
override fun fromProxy(proxy: LocalTimeProxy): LocalTime = LocalTime.of(proxy.hour.toInt(), proxy.minute.toInt(), proxy.second.toInt(), proxy.nano)
override fun fromProxy(proxy: LocalTimeProxy): LocalTime = LocalTime.of(
proxy.hour.toInt(),
proxy.minute.toInt(),
proxy.second.toInt(),
proxy.nano
)
@KeepForDJVM
data class LocalTimeProxy(val hour: Byte, val minute: Byte, val second: Byte, val nano: Int)

View File

@ -8,8 +8,9 @@ import java.time.MonthDay
/**
* A serializer for [MonthDay] that uses a proxy object to write out the integer form.
*/
class MonthDaySerializer(factory: SerializerFactory)
: CustomSerializer.Proxy<MonthDay, MonthDaySerializer.MonthDayProxy>(
class MonthDaySerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<MonthDay, MonthDaySerializer.MonthDayProxy>(
MonthDay::class.java, MonthDayProxy::class.java, factory
) {
override fun toProxy(obj: MonthDay): MonthDayProxy = MonthDayProxy(obj.monthValue.toByte(), obj.dayOfMonth.toByte())

View File

@ -10,8 +10,17 @@ import java.time.ZoneOffset
/**
* A serializer for [OffsetDateTime] that uses a proxy object to write out the date and zone offset.
*/
class OffsetDateTimeSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<OffsetDateTime, OffsetDateTimeSerializer.OffsetDateTimeProxy>(OffsetDateTime::class.java, OffsetDateTimeProxy::class.java, factory) {
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = listOf(LocalDateTimeSerializer(factory), ZoneIdSerializer(factory))
class OffsetDateTimeSerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<OffsetDateTime, OffsetDateTimeSerializer.OffsetDateTimeProxy>(
OffsetDateTime::class.java,
OffsetDateTimeProxy::class.java,
factory
) {
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = listOf(
LocalDateTimeSerializer(factory),
ZoneIdSerializer(factory)
)
override fun toProxy(obj: OffsetDateTime): OffsetDateTimeProxy = OffsetDateTimeProxy(obj.toLocalDateTime(), obj.offset)

View File

@ -10,8 +10,17 @@ import java.time.ZoneOffset
/**
* A serializer for [OffsetTime] that uses a proxy object to write out the time and zone offset.
*/
class OffsetTimeSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<OffsetTime, OffsetTimeSerializer.OffsetTimeProxy>(OffsetTime::class.java, OffsetTimeProxy::class.java, factory) {
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = listOf(LocalTimeSerializer(factory), ZoneIdSerializer(factory))
class OffsetTimeSerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<OffsetTime, OffsetTimeSerializer.OffsetTimeProxy>(
OffsetTime::class.java,
OffsetTimeProxy::class.java,
factory
) {
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = listOf(
LocalTimeSerializer(factory),
ZoneIdSerializer(factory)
)
override fun toProxy(obj: OffsetTime): OffsetTimeProxy = OffsetTimeProxy(obj.toLocalTime(), obj.offset)

View File

@ -10,8 +10,13 @@ import net.corda.serialization.internal.amqp.SerializerFactory
* to save on network bandwidth
* Uses [OpaqueBytes] as a proxy
*/
class OpaqueBytesSubSequenceSerializer(factory: SerializerFactory) :
CustomSerializer.Proxy<OpaqueBytesSubSequence, OpaqueBytes>(OpaqueBytesSubSequence::class.java, OpaqueBytes::class.java, factory) {
class OpaqueBytesSubSequenceSerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<OpaqueBytesSubSequence, OpaqueBytes>(
OpaqueBytesSubSequence::class.java,
OpaqueBytes::class.java,
factory
) {
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = emptyList()
override fun toProxy(obj: OpaqueBytesSubSequence): OpaqueBytes = OpaqueBytes(obj.copyBytes())
override fun fromProxy(proxy: OpaqueBytes): OpaqueBytesSubSequence = OpaqueBytesSubSequence(proxy.bytes, proxy.offset, proxy.size)

View File

@ -3,13 +3,18 @@ package net.corda.serialization.internal.amqp.custom
import net.corda.core.KeepForDJVM
import net.corda.serialization.internal.amqp.CustomSerializer
import net.corda.serialization.internal.amqp.SerializerFactory
import java.time.OffsetTime
import java.util.*
/**
* A serializer for [Optional] that uses a proxy object to write out the value stored in the optional or [Optional.EMPTY].
*/
class OptionalSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<Optional<*>, OptionalSerializer.OptionalProxy>(Optional::class.java, OptionalProxy::class.java, factory) {
class OptionalSerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<Optional<*>, OptionalSerializer.OptionalProxy>(
Optional::class.java,
OptionalProxy::class.java,
factory
) {
public override fun toProxy(obj: java.util.Optional<*>): OptionalProxy {
return OptionalProxy(obj.orElse(null))

View File

@ -8,7 +8,13 @@ import java.time.Period
/**
* A serializer for [Period] that uses a proxy object to write out the integer form.
*/
class PeriodSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<Period, PeriodSerializer.PeriodProxy>(Period::class.java, PeriodProxy::class.java, factory) {
class PeriodSerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<Period, PeriodSerializer.PeriodProxy>(
Period::class.java,
PeriodProxy::class.java,
factory
) {
override fun toProxy(obj: Period): PeriodProxy = PeriodProxy(obj.years, obj.months, obj.days)
override fun fromProxy(proxy: PeriodProxy): Period = Period.of(proxy.years, proxy.months, proxy.days)

View File

@ -9,9 +9,19 @@ import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type
import java.security.PrivateKey
object PrivateKeySerializer : CustomSerializer.Implements<PrivateKey>(PrivateKey::class.java) {
object PrivateKeySerializer
: CustomSerializer.Implements<PrivateKey>(
PrivateKey::class.java
) {
override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), AMQPTypeIdentifiers.primitiveTypeName(ByteArray::class.java), descriptor, emptyList())))
override val schemaForDocumentation = Schema(listOf(RestrictedType(
type.toString(),
"",
listOf(type.toString()),
AMQPTypeIdentifiers.primitiveTypeName(ByteArray::class.java),
descriptor,
emptyList()
)))
override fun writeDescribedObject(obj: PrivateKey, data: Data, type: Type, output: SerializationOutput,
context: SerializationContext

View File

@ -10,8 +10,18 @@ import java.security.PublicKey
/**
* A serializer that writes out a public key in X.509 format.
*/
object PublicKeySerializer : CustomSerializer.Implements<PublicKey>(PublicKey::class.java) {
override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), AMQPTypeIdentifiers.primitiveTypeName(ByteArray::class.java), descriptor, emptyList())))
object PublicKeySerializer
: CustomSerializer.Implements<PublicKey>(
PublicKey::class.java
) {
override val schemaForDocumentation = Schema(listOf(RestrictedType(
type.toString(),
"",
listOf(type.toString()),
AMQPTypeIdentifiers.primitiveTypeName(ByteArray::class.java),
descriptor,
emptyList()
)))
override fun writeDescribedObject(obj: PublicKey, data: Data, type: Type, output: SerializationOutput,
context: SerializationContext

View File

@ -11,7 +11,13 @@ import net.corda.serialization.internal.model.LocalTypeInformation
import java.io.NotSerializableException
@KeepForDJVM
class ThrowableSerializer(factory: LocalSerializerFactory) : CustomSerializer.Proxy<Throwable, ThrowableSerializer.ThrowableProxy>(Throwable::class.java, ThrowableProxy::class.java, factory) {
class ThrowableSerializer(
factory: LocalSerializerFactory
) : CustomSerializer.Proxy<Throwable, ThrowableSerializer.ThrowableProxy>(
Throwable::class.java,
ThrowableProxy::class.java,
factory
) {
companion object {
private val logger = contextLogger()

View File

@ -7,7 +7,10 @@ import java.lang.reflect.Type
import java.security.cert.CertificateFactory
import java.security.cert.X509CRL
object X509CRLSerializer : CustomSerializer.Implements<X509CRL>(X509CRL::class.java) {
object X509CRLSerializer
: CustomSerializer.Implements<X509CRL>(
X509CRL::class.java
) {
override val schemaForDocumentation = Schema(listOf(RestrictedType(
type.toString(),
"",

View File

@ -7,7 +7,10 @@ import java.lang.reflect.Type
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
object X509CertificateSerializer : CustomSerializer.Implements<X509Certificate>(X509Certificate::class.java) {
object X509CertificateSerializer
: CustomSerializer.Implements<X509Certificate>(
X509Certificate::class.java
) {
override val schemaForDocumentation = Schema(listOf(RestrictedType(
type.toString(),
"",

View File

@ -8,7 +8,13 @@ import java.time.YearMonth
/**
* A serializer for [YearMonth] that uses a proxy object to write out the integer form.
*/
class YearMonthSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<YearMonth, YearMonthSerializer.YearMonthProxy>(YearMonth::class.java, YearMonthProxy::class.java, factory) {
class YearMonthSerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<YearMonth, YearMonthSerializer.YearMonthProxy>(
YearMonth::class.java,
YearMonthProxy::class.java,
factory
) {
override fun toProxy(obj: YearMonth): YearMonthProxy = YearMonthProxy(obj.year, obj.monthValue.toByte())
override fun fromProxy(proxy: YearMonthProxy): YearMonth = YearMonth.of(proxy.year, proxy.month.toInt())

View File

@ -8,7 +8,13 @@ import java.time.Year
/**
* A serializer for [Year] that uses a proxy object to write out the integer form.
*/
class YearSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<Year, YearSerializer.YearProxy>(Year::class.java, YearProxy::class.java, factory) {
class YearSerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<Year, YearSerializer.YearProxy>(
Year::class.java,
YearProxy::class.java,
factory
) {
override fun toProxy(obj: Year): YearProxy = YearProxy(obj.value)
override fun fromProxy(proxy: YearProxy): Year = Year.of(proxy.year)

View File

@ -8,7 +8,13 @@ import java.time.ZoneId
/**
* A serializer for [ZoneId] that uses a proxy object to write out the string form.
*/
class ZoneIdSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<ZoneId, ZoneIdSerializer.ZoneIdProxy>(ZoneId::class.java, ZoneIdProxy::class.java, factory) {
class ZoneIdSerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<ZoneId, ZoneIdSerializer.ZoneIdProxy>(
ZoneId::class.java,
ZoneIdProxy::class.java,
factory
) {
override val revealSubclassesInSchema: Boolean = true
override fun toProxy(obj: ZoneId): ZoneIdProxy = ZoneIdProxy(obj.id)

View File

@ -12,22 +12,41 @@ import java.time.ZonedDateTime
/**
* A serializer for [ZonedDateTime] that uses a proxy object to write out the date, time, offset and zone.
*/
class ZonedDateTimeSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<ZonedDateTime, ZonedDateTimeSerializer.ZonedDateTimeProxy>(ZonedDateTime::class.java, ZonedDateTimeProxy::class.java, factory) {
class ZonedDateTimeSerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<ZonedDateTime, ZonedDateTimeSerializer.ZonedDateTimeProxy>(
ZonedDateTime::class.java,
ZonedDateTimeProxy::class.java,
factory
) {
// Java deserialization of `ZonedDateTime` uses a private method. We will resolve this somewhat statically
// so that any change to internals of `ZonedDateTime` is detected early.
companion object {
val ofLenient: Method = ZonedDateTime::class.java.getDeclaredMethod("ofLenient", LocalDateTime::class.java, ZoneOffset::class.java, ZoneId::class.java)
val ofLenient: Method = ZonedDateTime::class.java.getDeclaredMethod(
"ofLenient",
LocalDateTime::class.java,
ZoneOffset::class.java,
ZoneId::class.java
)
init {
ofLenient.isAccessible = true
}
}
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = listOf(LocalDateTimeSerializer(factory), ZoneIdSerializer(factory))
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = listOf(
LocalDateTimeSerializer(factory),
ZoneIdSerializer(factory)
)
override fun toProxy(obj: ZonedDateTime): ZonedDateTimeProxy = ZonedDateTimeProxy(obj.toLocalDateTime(), obj.offset, obj.zone)
override fun fromProxy(proxy: ZonedDateTimeProxy): ZonedDateTime = ofLenient.invoke(null, proxy.dateTime, proxy.offset, proxy.zone) as ZonedDateTime
override fun fromProxy(proxy: ZonedDateTimeProxy): ZonedDateTime = ofLenient.invoke(
null,
proxy.dateTime,
proxy.offset,
proxy.zone
) as ZonedDateTime
@KeepForDJVM
data class ZonedDateTimeProxy(val dateTime: LocalDateTime, val offset: ZoneOffset, val zone: ZoneId)

View File

@ -3,8 +3,17 @@ ENV GRADLE_USER_HOME=/tmp/gradle
RUN mkdir /tmp/gradle && mkdir -p /home/root/.m2/repository
RUN apt-get update && apt-get install -y curl libatomic1 && \
curl -O https://d3pxv6yz143wms.cloudfront.net/8.222.10.1/java-1.8.0-amazon-corretto-jdk_8.222.10-1_amd64.deb && \
apt-get install -y java-common && dpkg -i java-1.8.0-amazon-corretto-jdk_8.222.10-1_amd64.deb && \
curl -O https://cdn.azul.com/zulu/bin/zulu8.40.0.25-ca-jdk8.0.222-linux_amd64.deb && \
apt-get install -y java-common && apt install -y ./zulu8.40.0.25-ca-jdk8.0.222-linux_amd64.deb && \
apt-get clean && \
mkdir -p /tmp/source
rm -f zulu8.40.0.25-ca-jdk8.0.222-linux_amd64.deb && \
curl -O https://cdn.azul.com/zulu/bin/zulu8.40.0.25-ca-fx-jdk8.0.222-linux_x64.tar.gz && \
mv /zulu8.40.0.25-ca-fx-jdk8.0.222-linux_x64.tar.gz /usr/lib/jvm/ && \
cd /usr/lib/jvm/ && \
tar -zxvf zulu8.40.0.25-ca-fx-jdk8.0.222-linux_x64.tar.gz && \
rm -rf zulu-8-amd64 && \
mv zulu8.40.0.25-ca-fx-jdk8.0.222-linux_x64 zulu-8-amd64 && \
rm -f zulu8.40.0.25-ca-fx-jdk8.0.222-linux_x64.tar.gz && \
cd / && mkdir -p /tmp/source

View File

@ -199,7 +199,8 @@ fun <A> driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr
networkParameters = defaultParameters.networkParameters,
notaryCustomOverrides = defaultParameters.notaryCustomOverrides,
inMemoryDB = defaultParameters.inMemoryDB,
cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes)
cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes),
environmentVariables = defaultParameters.environmentVariables
),
coerce = { it },
dsl = dsl
@ -255,10 +256,46 @@ data class DriverParameters(
val networkParameters: NetworkParameters = testNetworkParameters(notaries = emptyList()),
val notaryCustomOverrides: Map<String, Any?> = emptyMap(),
val inMemoryDB: Boolean = true,
val cordappsForAllNodes: Collection<TestCordapp>? = null
val cordappsForAllNodes: Collection<TestCordapp>? = null,
val environmentVariables : Map<String, String> = emptyMap()
) {
constructor(cordappsForAllNodes: Collection<TestCordapp>) : this(isDebug = false, cordappsForAllNodes = cordappsForAllNodes)
constructor(
isDebug: Boolean = false,
driverDirectory: Path = Paths.get("build") / "node-driver" / getTimestampAsDirectoryName(),
portAllocation: PortAllocation = incrementalPortAllocation(),
debugPortAllocation: PortAllocation = incrementalPortAllocation(),
systemProperties: Map<String, String> = emptyMap(),
useTestClock: Boolean = false,
startNodesInProcess: Boolean = false,
waitForAllNodesToFinish: Boolean = false,
notarySpecs: List<NotarySpec> = listOf(NotarySpec(DUMMY_NOTARY_NAME)),
extraCordappPackagesToScan: List<String> = emptyList(),
@Suppress("DEPRECATION") jmxPolicy: JmxPolicy = JmxPolicy(),
networkParameters: NetworkParameters = testNetworkParameters(notaries = emptyList()),
notaryCustomOverrides: Map<String, Any?> = emptyMap(),
inMemoryDB: Boolean = true,
cordappsForAllNodes: Collection<TestCordapp>? = null
) : this(
isDebug,
driverDirectory,
portAllocation,
debugPortAllocation,
systemProperties,
useTestClock,
startNodesInProcess,
waitForAllNodesToFinish,
notarySpecs,
extraCordappPackagesToScan,
jmxPolicy,
networkParameters,
notaryCustomOverrides,
inMemoryDB,
cordappsForAllNodes,
environmentVariables = emptyMap()
)
constructor(
isDebug: Boolean = false,
driverDirectory: Path = Paths.get("build") / "node-driver" / getTimestampAsDirectoryName(),
@ -373,6 +410,7 @@ data class DriverParameters(
fun withNotaryCustomOverrides(notaryCustomOverrides: Map<String, Any?>): DriverParameters = copy(notaryCustomOverrides = notaryCustomOverrides)
fun withInMemoryDB(inMemoryDB: Boolean): DriverParameters = copy(inMemoryDB = inMemoryDB)
fun withCordappsForAllNodes(cordappsForAllNodes: Collection<TestCordapp>?): DriverParameters = copy(cordappsForAllNodes = cordappsForAllNodes)
fun withEnvironmentVariables(variables : Map<String, String>): DriverParameters = copy(environmentVariables = variables)
fun copy(
isDebug: Boolean,
@ -433,4 +471,40 @@ data class DriverParameters(
notaryCustomOverrides = emptyMap(),
cordappsForAllNodes = cordappsForAllNodes
)
@Suppress("LongParameterList")
fun copy(
isDebug: Boolean,
driverDirectory: Path,
portAllocation: PortAllocation,
debugPortAllocation: PortAllocation,
systemProperties: Map<String, String>,
useTestClock: Boolean,
startNodesInProcess: Boolean,
waitForAllNodesToFinish: Boolean,
notarySpecs: List<NotarySpec>,
extraCordappPackagesToScan: List<String>,
jmxPolicy: JmxPolicy,
networkParameters: NetworkParameters,
notaryCustomOverrides: Map<String, Any?>,
inMemoryDB: Boolean,
cordappsForAllNodes: Collection<TestCordapp>?
) = this.copy(
isDebug = isDebug,
driverDirectory = driverDirectory,
portAllocation = portAllocation,
debugPortAllocation = debugPortAllocation,
systemProperties = systemProperties,
useTestClock = useTestClock,
startNodesInProcess = startNodesInProcess,
waitForAllNodesToFinish = waitForAllNodesToFinish,
notarySpecs = notarySpecs,
extraCordappPackagesToScan = extraCordappPackagesToScan,
jmxPolicy = jmxPolicy,
networkParameters = networkParameters,
notaryCustomOverrides = notaryCustomOverrides,
inMemoryDB = inMemoryDB,
cordappsForAllNodes = cordappsForAllNodes,
environmentVariables = emptyMap()
)
}

View File

@ -91,7 +91,8 @@ class DriverDSLImpl(
val networkParameters: NetworkParameters,
val notaryCustomOverrides: Map<String, Any?>,
val inMemoryDB: Boolean,
val cordappsForAllNodes: Collection<TestCordappInternal>?
val cordappsForAllNodes: Collection<TestCordappInternal>?,
val environmentVariables : Map<String, String>
) : InternalDriverDSL {
private var _executorService: ScheduledExecutorService? = null
@ -288,9 +289,11 @@ class DriverDSLImpl(
} else {
startOutOfProcessMiniNode(
config,
"initial-registration",
"--network-root-truststore=${rootTruststorePath.toAbsolutePath()}",
"--network-root-truststore-password=$rootTruststorePassword"
arrayOf(
"initial-registration",
"--network-root-truststore=${rootTruststorePath.toAbsolutePath()}",
"--network-root-truststore-password=$rootTruststorePassword"
)
).map { config }
}
}
@ -450,7 +453,7 @@ class DriverDSLImpl(
} else {
// TODO The config we use here is uses a hardocded p2p port which changes when the node is run proper
// This causes two node info files to be generated.
startOutOfProcessMiniNode(config, "generate-node-info").map {
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()
@ -536,7 +539,7 @@ class DriverDSLImpl(
* Start the node with the given flag which is expected to start the node for some function, which once complete will
* terminate the node.
*/
private fun startOutOfProcessMiniNode(config: NodeConfig, vararg extraCmdLineFlag: String): CordaFuture<Unit> {
private fun startOutOfProcessMiniNode(config: NodeConfig, extraCmdLineFlag: Array<String> = emptyArray()): CordaFuture<Unit> {
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
val process = startOutOfProcessNode(
config,
@ -545,7 +548,8 @@ class DriverDSLImpl(
systemProperties,
"512m",
null,
*extraCmdLineFlag
environmentVariables,
extraCmdLineFlag
)
return poll(executorService, "$extraCmdLineFlag (${config.corda.myLegalName})") {
@ -602,7 +606,15 @@ class DriverDSLImpl(
nodeFuture
} else {
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
val process = startOutOfProcessNode(config, quasarJarPath, debugPort, systemProperties, parameters.maximumHeapSize, parameters.logLevelOverride)
val process = startOutOfProcessNode(
config,
quasarJarPath,
debugPort,
systemProperties,
parameters.maximumHeapSize,
parameters.logLevelOverride,
environmentVariables
)
// Destroy the child process when the parent exits.This is needed even when `waitForAllNodesToFinish` is
// true because we don't want orphaned processes in the case that the parent process is terminated by the
@ -726,6 +738,7 @@ class DriverDSLImpl(
}
}
@Suppress("LongParameterList")
private fun startOutOfProcessNode(
config: NodeConfig,
quasarJarPath: String,
@ -733,9 +746,11 @@ class DriverDSLImpl(
overriddenSystemProperties: Map<String, String>,
maximumHeapSize: String,
logLevelOverride: String?,
vararg extraCmdLineFlag: String
): Process {
log.info("Starting out-of-process Node ${config.corda.myLegalName.organisation}, debug port is " + (debugPort ?: "not enabled"))
environmentVariables : Map<String,String>,
extraCmdLineFlag: Array<String> = emptyArray()
): Process {
log.info("Starting out-of-process Node ${config.corda.myLegalName.organisation}, " +
"debug port is " + (debugPort ?: "not enabled"))
// Write node.conf
writeConfig(config.corda.baseDirectory, "node.conf", config.typesafe.toNodeOnly())
@ -774,7 +789,7 @@ class DriverDSLImpl(
"--base-directory=${config.corda.baseDirectory}",
"--logging-level=$loggingLevel",
"--no-local-shell").also {
it += extraCmdLineFlag
it.addAll(extraCmdLineFlag)
}.toList()
// The following dependencies are excluded from the classpath of the created JVM, so that the environment resembles a real one as close as possible.
@ -792,7 +807,8 @@ class DriverDSLImpl(
extraJvmArguments = extraJvmArguments,
workingDirectory = config.corda.baseDirectory,
maximumHeapSize = maximumHeapSize,
classPath = cp
classPath = cp,
environmentVariables = environmentVariables
)
}
@ -1013,7 +1029,8 @@ fun <DI : DriverDSL, D : InternalDriverDSL, A> genericDriver(
networkParameters = defaultParameters.networkParameters,
notaryCustomOverrides = defaultParameters.notaryCustomOverrides,
inMemoryDB = defaultParameters.inMemoryDB,
cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes)
cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes),
environmentVariables = defaultParameters.environmentVariables
)
)
val shutdownHook = addShutdownHook(driverDsl::shutdown)
@ -1090,6 +1107,7 @@ class SplitCompatibilityZoneParams(
override fun config() : NetworkServicesConfig = config
}
@Suppress("LongParameterList")
fun <A> internalDriver(
isDebug: Boolean = DriverParameters().isDebug,
driverDirectory: Path = DriverParameters().driverDirectory,
@ -1107,6 +1125,7 @@ fun <A> internalDriver(
notaryCustomOverrides: Map<String, Any?> = DriverParameters().notaryCustomOverrides,
inMemoryDB: Boolean = DriverParameters().inMemoryDB,
cordappsForAllNodes: Collection<TestCordappInternal>? = null,
environmentVariables: Map<String, String> = emptyMap(),
dsl: DriverDSLImpl.() -> A
): A {
return genericDriver(
@ -1126,7 +1145,8 @@ fun <A> internalDriver(
networkParameters = networkParameters,
notaryCustomOverrides = notaryCustomOverrides,
inMemoryDB = inMemoryDB,
cordappsForAllNodes = cordappsForAllNodes
cordappsForAllNodes = cordappsForAllNodes,
environmentVariables = environmentVariables
),
coerce = { it },
dsl = dsl

View File

@ -7,17 +7,29 @@ import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
object ProcessUtilities {
@Suppress("LongParameterList")
inline fun <reified C : Any> startJavaProcess(
arguments: List<String>,
classPath: List<String> = defaultClassPath,
workingDirectory: Path? = null,
jdwpPort: Int? = null,
extraJvmArguments: List<String> = emptyList(),
maximumHeapSize: String? = null
maximumHeapSize: String? = null,
environmentVariables: Map<String, String> = emptyMap()
): Process {
return startJavaProcess(C::class.java.name, arguments, classPath, workingDirectory, jdwpPort, extraJvmArguments, maximumHeapSize)
return startJavaProcess(
C::class.java.name,
arguments,
classPath,
workingDirectory,
jdwpPort,
extraJvmArguments,
maximumHeapSize,
environmentVariables
)
}
@Suppress("LongParameterList")
fun startJavaProcess(
className: String,
arguments: List<String>,
@ -25,7 +37,8 @@ object ProcessUtilities {
workingDirectory: Path? = null,
jdwpPort: Int? = null,
extraJvmArguments: List<String> = emptyList(),
maximumHeapSize: String? = null
maximumHeapSize: String? = null,
environmentVariables: Map<String,String> = emptyMap()
): Process {
val command = mutableListOf<String>().apply {
add(javaPath)
@ -38,6 +51,7 @@ object ProcessUtilities {
}
return ProcessBuilder(command).apply {
inheritIO()
environment().putAll(environmentVariables)
environment()["CLASSPATH"] = classPath.joinToString(File.pathSeparator)
if (workingDirectory != null) {
// Timestamp may be handy if the same process started, killed and then re-started. Without timestamp

View File

@ -104,6 +104,7 @@ val fakeNodeLegalName = CordaX500Name(organisation = "Not:a:real:name", locality
private val globalPortAllocation = incrementalPortAllocation()
private val globalDebugPortAllocation = incrementalPortAllocation()
@Suppress("LongParameterList")
fun <A> rpcDriver(
isDebug: Boolean = false,
driverDirectory: Path = Paths.get("build") / "rpc-driver" / getTimestampAsDirectoryName(),
@ -121,6 +122,7 @@ fun <A> rpcDriver(
notaryCustomOverrides: Map<String, Any?> = emptyMap(),
inMemoryDB: Boolean = true,
cordappsForAllNodes: Collection<TestCordappInternal>? = null,
environmentVariables: Map<String, String> = emptyMap(),
dsl: RPCDriverDSL.() -> A
): A {
return genericDriver(
@ -141,7 +143,8 @@ fun <A> rpcDriver(
networkParameters = networkParameters,
notaryCustomOverrides = notaryCustomOverrides,
inMemoryDB = inMemoryDB,
cordappsForAllNodes = cordappsForAllNodes
cordappsForAllNodes = cordappsForAllNodes,
environmentVariables = environmentVariables
), externalTrace
),
coerce = { it },

View File

@ -138,7 +138,7 @@ object InteractiveShell {
ExternalResolver.INSTANCE.addCommand("hashLookup", "Checks if a transaction with matching Id hash exists.", HashLookupShellCommand::class.java)
ExternalResolver.INSTANCE.addCommand("attachments", "Commands to extract information about attachments stored within the node", AttachmentShellCommand::class.java)
ExternalResolver.INSTANCE.addCommand("checkpoints", "Commands to extract information about checkpoints stored within the node", CheckpointShellCommand::class.java)
shell = ShellLifecycle(configuration.commandsDirectory).start(config, configuration.user, configuration.password)
shell = ShellLifecycle(configuration.commandsDirectory).start(config, configuration.user, configuration.password, runSshDaemon)
}
fun runLocalShell(onExit: () -> Unit = {}) {
@ -166,7 +166,7 @@ object InteractiveShell {
}
class ShellLifecycle(private val shellCommands: Path) : PluginLifeCycle() {
fun start(config: Properties, localUserName: String = "", localUserPassword: String = ""): Shell {
fun start(config: Properties, localUserName: String = "", localUserPassword: String = "", runSshDaemon: Boolean): Shell {
val classLoader = this.javaClass.classLoader
val classpathDriver = ClassPathMountFactory(classLoader)
val fileDriver = FileMountFactory(Utils.getCurrentDirectory())
@ -192,7 +192,8 @@ object InteractiveShell {
return super.getPlugins().filterNot { it is JavaLanguage } + CordaAuthenticationPlugin(rpcOps)
}
}
val attributes = emptyMap<String, Any>()
val attributes = mutableMapOf<String, Any>()
attributes["crash.localShell"] = runSshDaemon
val context = PluginContext(discovery, attributes, commandsFS, confFS, classLoader)
context.refresh()
this.config = config

View File

@ -175,11 +175,13 @@ abstract class ANSIProgressRenderer {
var indent = 0
while (errorToPrint != null) {
ansi.fgRed()
ansi.a("${IntStream.range(indent, indent).mapToObj { "\t" }.toList().joinToString(separator = "") { s -> s }} $errorIcon ${error.message}")
ansi.reset()
ansi.a("${"\t".repeat(indent)}$errorIcon ${errorToPrint.message}")
ansi.newline()
errorToPrint = errorToPrint.cause
indent++
}
ansi.reset()
ansi.eraseLine(Ansi.Erase.FORWARD)
ansi.newline()
newLinesDrawn++

View File

@ -3,12 +3,23 @@ package net.corda.tools.shell.base
// Note that this file MUST be in a sub-directory called "base" relative to the path
// given in the configuration code in InteractiveShell.
welcome = """
Welcome to the Corda interactive shell.
Useful commands include 'help' to see what is available, and 'bye' to shut down the node.
"""
welcome = { ->
if (crash.context.attributes["crash.localShell"] == true) {
"""
Welcome to the Corda interactive shell.
Useful commands include 'help' to see what is available, and 'bye' to exit the shell.
""".stripIndent()
} else {
"""
Welcome to the Corda interactive shell.
Useful commands include 'help' to see what is available, and 'bye' to shut down the node.
""".stripIndent()
}
}
prompt = { ->
return "${new Date()}>>> "