Merge branch 'release/os/4.9' into merge-release/os/4.8-release/os/4.9-2023-11-19-6

This commit is contained in:
Ronan Browne 2023-11-19 19:56:09 +00:00 committed by GitHub
commit 18d98760b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
157 changed files with 2029 additions and 5015 deletions

View File

@ -6,4 +6,4 @@ RUN apt-get update && apt-get install -y curl apt-transport-https \
software-properties-common \
wget
ARG USER="stresstester"
RUN useradd -m ${USER}
RUN useradd -m ${USER}

View File

@ -13,13 +13,13 @@
* the branch name of origin branch, it should match the current branch
* and it acts as a fail-safe inside {@code forwardMerger} pipeline
*/
String originBranch = 'release/os/4.8'
String originBranch = 'release/os/4.9'
/**
* the branch name of target branch, it should be the branch with the next version
* after the one in current branch.
*/
String targetBranch = 'release/os/4.9'
String targetBranch = 'release/os/4.10'
/**
* Forward merge any changes between #originBranch and #targetBranch

View File

@ -92,6 +92,7 @@ pipeline {
}
stage('Recompile') {
steps {
authenticateGradleWrapper()
sh script: [
'./gradlew',
COMMON_GRADLE_PARAMS,

View File

@ -6,9 +6,11 @@
@Library('corda-shared-build-pipeline-steps')
import com.r3.build.utils.GitUtils
import com.r3.build.enums.SnykOrganisation
import com.r3.build.utils.SnykUtils
GitUtils gitUtils = new GitUtils(this)
SnykUtils snykUtils = new SnykUtils(this)
/**
* Sense environment
*/
@ -16,7 +18,7 @@ boolean isReleaseBranch = (env.BRANCH_NAME =~ /^release\/os\/.*/)
boolean isReleaseTag = (env.TAG_NAME =~ /^release-.*(?<!_JDK11)$/)
boolean isInternalRelease = (env.TAG_NAME =~ /^internal-release-.*$/)
boolean isReleaseCandidate = (env.TAG_NAME =~ /^(release-.*(RC|HC).*(?<!_JDK11))$/)
boolean isReleasePatch = (env.TAG_NAME =~ /^release.*([1-9]\d*|0)(\.([1-9]\d*|0)){2}$/)
def buildEdition = (isReleaseTag || isReleaseCandidate) ? "Corda Community Edition" : "Corda Open Source"
/**
* Common Gradle arguments for all Gradle executions
@ -27,7 +29,8 @@ String COMMON_GRADLE_PARAMS = [
'--info',
'-Pcompilation.warningsAsErrors=false',
'-Ptests.failFast=true',
'--build-cache'
'--build-cache',
'-DexcludeShell'
].join(' ')
pipeline {
@ -59,11 +62,13 @@ pipeline {
CORDA_ARTIFACTORY_PASSWORD = "${env.ARTIFACTORY_CREDENTIALS_PSW}"
CORDA_ARTIFACTORY_USERNAME = "${env.ARTIFACTORY_CREDENTIALS_USR}"
CORDA_GRADLE_SCAN_KEY = credentials('gradle-build-scans-key')
CORDA_BUILD_EDITION = "${buildEdition}"
CORDA_USE_CACHE = "corda-remotes"
DOCKER_URL = "https://index.docker.io/v1/"
EMAIL_RECIPIENTS = credentials('corda4-email-recipient')
INTEGRATION_ID = credentials('snyk-artifactory-c4')
SNYK_API_KEY = "c4-os-snyk" //Jenkins credential type: Snyk Api token
SNYK_API_TOKEN = credentials('c4-os-snyk-api-token-secret') //Jenkins credential type: Secret text
SNYK_TOKEN = credentials('c4-os-snyk-api-token-secret') //Jenkins credential type: Secret text
C4_OS_SNYK_ORG_ID = credentials('corda4-os-snyk-org-id')
}
@ -110,7 +115,7 @@ pipeline {
expression { isReleaseTag || isReleaseCandidate || isReleaseBranch }
}
steps {
snykLicenseGeneration(env.SNYK_API_TOKEN, env.C4_OS_SNYK_ORG_ID)
snykLicenseGeneration(env.SNYK_TOKEN, env.C4_OS_SNYK_ORG_ID)
}
post {
always {
@ -274,7 +279,7 @@ pipeline {
rtGradleRun(
usesPlugin: true,
useWrapper: true,
switches: '-s --info',
switches: '-s --info -DpublishApiDocs',
tasks: 'artifactoryPublish',
deployerId: 'deployer',
buildName: env.ARTIFACTORY_BUILD_NAME
@ -288,7 +293,7 @@ pipeline {
stage('Publish Release Candidate to Internal Repository') {
when {
expression { return false} // keeping stage to preserve Jenkins history on release branches, but not supported for patch builds pre 4.9
expression { isReleaseCandidate }
}
steps {
withCredentials([
@ -310,7 +315,7 @@ pipeline {
stage('Publish Release to Docker Hub') {
when {
expression { isReleaseTag && !isInternalRelease && !isReleaseCandidate && !isReleasePatch}
expression { isReleaseTag && !isInternalRelease && !isReleaseCandidate}
}
steps {
withCredentials([
@ -322,7 +327,7 @@ pipeline {
'./gradlew',
COMMON_GRADLE_PARAMS,
'docker:pushDockerImage',
'-Pdocker.image.repository=corda/corda',
'-Pdocker.image.repository=corda/community',
'--image OFFICIAL'
].join(' ')
}
@ -397,6 +402,13 @@ pipeline {
if (isReleaseTag || isReleaseCandidate || isReleaseBranch) {
snykSecurityScan.generateHtmlElements()
}
if (isReleaseTag || isReleaseCandidate) {
// auto import and scanning of Docker images tag is dictated by below properties, so retrieve these first to scan the approproate tag
String cordaVersion = sh(script: 'grep "cordaVersion" constants.properties | awk -F= \'{print $2}\'', returnStdout: true).trim()
String versionSuffix = sh(script: 'grep "versionSuffix" constants.properties | awk -F= \'{print $2}\'', returnStdout: true).trim()
snykUtils.SnykApiImport(!versionSuffix.isEmpty() ? "${cordaVersion}-${versionSuffix}" : cordaVersion, SnykOrganisation.CORDA_4_OS, env.C4_OS_SNYK_ORG_ID)
}
}
}
unstable {

View File

@ -3,9 +3,9 @@
# PR Checklist:
- [ ] Have you run the unit, integration and smoke tests as described [here](https://docs.corda.net/head/testing.html)?
- [ ] If you added public APIs, did you write the JavaDocs?
- [ ] If the changes are of interest to application developers, have you added them to the [changelog](https://docs.corda.net/head/changelog.html) (`/docs/source/changelog.rst`), and potentially the [release notes](https://docs.corda.net/head/release-notes.html) (`/docs/source/release-notes.rst`)?
- [ ] If you are contributing for the first time, please read the [contributor agreement](https://docs.corda.net/head/contributing.html) now and add a comment to this pull request stating that your PR is in accordance with the [Developer's Certificate of Origin](https://docs.corda.net/head/contributing.html#merging-the-changes-back-into-corda).
- [ ] Have you run the unit, integration and smoke tests as described [here](https://docs.r3.com/en/platform/corda/4.8/open-source/testing.html)?
- [ ] If you added public APIs, did you write the JavaDocs/kdocs?
- [ ] If the changes are of interest to application developers, have you added them to the changelog, and potentially the [release notes](https://docs.corda.net/head/release-notes.html) (`https://docs.r3.com/en/platform/corda/4.8/open-source/release-notes.html`)?
- [ ] If you are contributing for the first time, please read the [contributor agreement](https://docs.r3.com/en/platform/corda/4.8/open-source/contributing.html) now and add a comment to this pull request stating that your PR is in accordance with the [Developer's Certificate of Origin](https://docs.r3.com/en/platform/corda/4.8/open-source/contributing.html#merging-the-changes-back-into-corda).
Thanks for your code, it's appreciated! :)

231
.snyk Normal file
View File

@ -0,0 +1,231 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.25.0
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
ignore:
SNYK-JAVA-COMGOOGLEGUAVA-1015415:
- '*':
reason: >-
Guavas Files.createTempDir() is used during integration tests only.
Users of Corda are advised not to use Guavas Files.createTempDir()
when building applications on Corda.
expires: 2023-09-01T11:38:11.478Z
created: 2022-12-29T11:38:11.489Z
SNYK-JAVA-COMH2DATABASE-31685:
- '*':
reason: >-
H2 console is not enabled for any of the applications we are running.
When it comes to DB connectivity parameters, we do not allow changing
them as they are supplied by Corda Node configuration file.
expires: 2023-09-01T11:39:26.763Z
created: 2022-12-29T11:39:26.775Z
SNYK-JAVA-COMH2DATABASE-2331071:
- '*':
reason: >-
H2 console is not enabled for any of the applications we are running.
When it comes to DB connectivity parameters, we do not allow changing
them as they are supplied by Corda Node configuration file.
expires: 2023-09-01T11:41:05.707Z
created: 2022-12-29T11:41:05.723Z
SNYK-JAVA-COMSQUAREUPOKHTTP3-2958044:
- '*':
reason: >-
The vulnerability in okhttps error handling is only exploitable in
services that receive and parse HTTP requests. Corda does not receive
HTTP requests and thus is not exposed to this issue.
expires: 2023-09-01T11:42:55.546Z
created: 2022-12-29T11:42:55.556Z
SNYK-JAVA-IONETTY-1042268:
- '*':
reason: >-
Corda does not rely on hostname verification in the P2P protocol to
identify a host, so is not impacted by this vulnerability. Corda uses
its own SSL identity check logic for the network model. Corda
validates based on the full X500 subject name and the fact that P2P
links use mutually authenticated TLS with the same trust roots. For
RPC SSL client connections Artemis is used which calls into netty. The
default value for verifyHost is true for Artemis client connectors so
verification of the host name in netty does occur.
expires: 2023-09-01T11:45:42.976Z
created: 2022-12-29T11:45:42.981Z
SNYK-JAVA-ORGJETBRAINSKOTLIN-2628385:
- '*':
reason: >-
This is a build time vulnerability. It relates to the inability to
lock dependencies for Kotlin Multiplatform Gradle Projects. At build
time for Corda we do not use Multiplatform Gradle Projects so are not
affected by this vulnerability. In addition as it is a build time
vulnerability released artifacts are not affected.
expires: 2023-09-01T11:52:35.855Z
created: 2022-12-29T11:52:35.870Z
SNYK-JAVA-ORGJETBRAINSKOTLIN-2393744:
- '*':
reason: >-
This vulnerability relates to information exposure via creation of
temporary files (via Kotlin functions) with insecure permissions.
Corda does not use any of the vulnerable functions so it not
susceptible to this vulnerability.
expires: 2023-09-01T13:39:03.244Z
created: 2022-12-29T13:39:03.262Z
SNYK-JAVA-ORGLIQUIBASE-2419059:
- '*':
reason: >-
This component is used to upgrade the node database schema either at
node startup or via the database migration tool. The XML input for the
database migration is generated by Corda from either R3 supplied XML
files included in corda.jar or those XML files written by the CorDapp
author included in a CorDapp that is installed in the node CorDapps
directory. Contract CorDapps received over the network are not a
source of XML files for this generation step. An attacker trying to
exploit this vulnerability would need access to the server with the
XML input files, and specifically the access and ability to change JAR
files on the file system that make up the Corda installation.
expires: 2023-09-01T13:42:11.552Z
created: 2022-12-29T13:42:11.570Z
SNYK-JAVA-COMH2DATABASE-2348247:
- '*':
reason: >-
H2 console is not enabled for any of the applications we are running.
When it comes to DB connectivity parameters, we do not allow changing
them as they are supplied by Corda Node configuration file.
expires: 2023-09-01T11:36:39.068Z
created: 2022-12-29T11:36:39.089Z
SNYK-JAVA-COMH2DATABASE-1769238:
- '*':
reason: >-
H2 is not invoked by Corda unless the node deployment configures an H2
database. This is not a supported configuration in Production and so
this vulnerability should be irrelevant except during development on
Corda. Corda itself does not store XML data within the database so
Corda is not susceptible to this vulnerability. If CorDapp developers
store XML data to the database they need to ascertain themselves that
they are not susceptible.
expires: 2023-09-01T11:40:29.871Z
created: 2022-12-29T11:40:29.896Z
SNYK-JAVA-ORGYAML-3152153:
- '*':
reason: >-
There is a transitive dependency on snakeyaml from the third party
components jackson-dataformat-yaml and liquidbase-core. The
jackson-dataformat-yaml component does not use the snakeyaml
databinding layer. For liquidbase we use xml in the changelog files
not yaml. So given this Corda is not susceptible to this
vulnerability.Cordapp authors should exercise their own judgment if
using this library directly in their cordapp.
expires: 2023-09-01T11:35:04.385Z
created: 2023-01-04T11:35:04.414Z
SNYK-JAVA-COMH2DATABASE-3146851:
- '*':
reason: >-
Corda does not make use of the H2 web admin console, so it not
susceptible to this reported vulnerability
expires: 2023-09-01T11:45:11.295Z
created: 2023-01-04T11:45:11.322Z
SNYK-JAVA-ORGBOUNCYCASTLE-2841508:
- '*':
reason: >-
This vulnerability relates to weak key-hash message authentication
code due to an error within the BKS version 1 keystore files. Corda
does not use BKS-V1 for its keystore files so is not susceptible to
this vulnerability.
expires: 2023-09-01T11:32:38.120Z
created: 2022-09-21T11:32:38.125Z
SNYK-JAVA-COMFASTERXMLJACKSONCORE-3038424:
- '*':
reason: >-
Corda does not set the non-default UNWRAP_SINGLE_VALUE_ARRAYS required
for this vulnerability. In addition Corda does not use Jackson for
deserialization except in the optional shell which we recommend using
standalone. The Corda node itself is not exposed. Corda does however
provide mappings of Corda types to allow CorDapps to use Jackson, and
CorDapps using Jackson should make their own assessment. This
vulnerability relates to deeply nested untyped Object or Array values
(3000 levels deep). Only CorDapps with these types at this level of
nesting are potentially susceptible.
expires: 2023-09-01T12:04:40.180Z
created: 2023-02-09T12:04:40.209Z
SNYK-JAVA-COMFASTERXMLJACKSONCORE-3038426:
- '*':
reason: >-
Corda does not set the non-default UNWRAP_SINGLE_VALUE_ARRAYS required
for this vulnerability. In addition Corda does not use Jackson for
deserialization except in the optional shell which we recommend using
standalone. The Corda node itself is not exposed. Corda does however
provide mappings of Corda types to allow CorDapps to use Jackson, and
CorDapps using Jackson should make their own assessment. This
vulnerability relates to deeply nested untyped Object or Array values
(3000 levels deep). Only CorDapps with these types at this level of
nesting are potentially susceptible.
expires: 2023-09-01T12:05:03.931Z
created: 2023-02-09T12:05:03.962Z
SNYK-JAVA-ORGYAML-2806360:
- '*':
reason: >-
Snakeyaml is being used by Jackson and liquidbase. Corda does not use
Jackson except in the optional shell which we recommend using
standalone. The Corda node itself is not exposed. Corda does however
provide mappings of Corda types to allow CorDapps to use Jackson, and
CorDapps using Jackson should make their own assessment. Liquibase is
used to apply the database migration changes. XML files are used here
to define the changes not YAML and therefore the Corda node itself is
not exposed to this DOS vulnerability.
expires: 2023-09-01T13:40:55.262Z
created: 2022-09-21T13:40:55.279Z
SNYK-JAVA-ORGYAML-3016891:
- '*':
reason: >-
Snakeyaml is being used by Jackson and liquidbase. Corda does not use
Jackson for deserialization except in the optional shell which we
recommend using standalone. The Corda node itself is not exposed.
Corda does however provide mappings of Corda types to allow CorDapps
to use Jackson, and CorDapps using Jackson should make their own
assessment. Liquibase is used to apply the database migration changes.
XML files are used here to define the changes not YAML and therefore
the Corda node itself is not exposed to this deserialisation
vulnerability.
expires: 2023-09-01T16:37:28.911Z
created: 2023-02-06T16:37:28.933Z
SNYK-JAVA-ORGYAML-3016888:
- '*':
reason: >-
Snakeyaml is being used by Jackson and liquidbase. Corda does not use
Jackson for deserialization except in the optional shell which we
recommend using standalone. The Corda node itself is not exposed.
Corda does however provide mappings of Corda types to allow CorDapps
to use Jackson, and CorDapps using Jackson should make their own
assessment. Liquibase is used to apply the database migration changes.
XML files are used here to define the changes not YAML and therefore
the Corda node itself is not exposed to this deserialisation
vulnerability.
expires: 2023-09-01T13:39:49.450Z
created: 2022-09-21T13:39:49.470Z
SNYK-JAVA-ORGYAML-3016889:
- '*':
reason: >-
Snakeyaml is being used by Jackson and liquidbase. Corda does not use
Jackson for deserialization except in the optional shell which we
recommend using standalone. The Corda node itself is not exposed.
Corda does however provide mappings of Corda types to allow CorDapps
to use Jackson, and CorDapps using Jackson should make their own
assessment. Liquibase is used to apply the database migration changes.
XML files are used here to define the changes not YAML and therefore
the Corda node itself is not exposed to this deserialisation
vulnerability.
expires: 2023-09-01T16:35:13.840Z
created: 2023-02-06T16:35:13.875Z
SNYK-JAVA-ORGYAML-3113851:
- '*':
reason: >-
Snakeyaml is being used by Jackson and liquidbase. Corda does not use
Jackson for deserialization except in the optional shell which we
recommend using standalone. The Corda node itself is not exposed.
Corda does however provide mappings of Corda types to allow CorDapps
to use Jackson, and CorDapps using Jackson should make their own
assessment. Liquibase is used to apply the database migration changes.
XML files are used here to define the changes not YAML and therefore
the Corda node itself is not exposed to this deserialisation
vulnerability.
expires: 2024-04-01T00:00:00.000Z
created: 2022-11-29T14:55:03.623Z
patch: {}

View File

@ -2,4 +2,4 @@
Corda is an open-source project and contributions are welcome!
To find out how to contribute, please see our [contributing docs](https://docs.corda.net/head/contributing-index.html).
To find out how to contribute, please see our [contributing docs](https://docs.r3.com/en/platform/corda/4.8/open-source/contributing.html).

View File

@ -39,7 +39,7 @@ Corda is an open source blockchain project, designed for business from the start
Corda is an open-source project and contributions are welcome!
To find out how to contribute, please see our [contributing docs](https://docs.corda.net/head/contributing-index.html).
To find out how to contribute, please see our [contributing docs](https://docs.r3.com/en/platform/corda/4.8/open-source/contributing.html).
## License

View File

@ -13,6 +13,7 @@ buildscript {
ext.baseVersion = constants.getProperty("cordaVersion")
ext.versionSuffix = constants.getProperty("versionSuffix")
ext.corda_build_edition = System.getenv("CORDA_BUILD_EDITION")?.trim() ?: "Corda Open Source"
ext.corda_platform_version = constants.getProperty("platformVersion")
ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
@ -60,11 +61,11 @@ buildscript {
ext.capsule_version = '1.0.3'
ext.asm_version = '7.1'
ext.artemis_version = '2.6.2'
// TODO Upgrade to Jackson Kotlin 2.10+ only when corda is using kotlin 1.3.10
ext.artemis_version = '2.19.1'
// TODO Upgrade Jackson only when corda is using kotlin 1.3.10
ext.jackson_version = '2.13.5'
ext.jackson_kotlin_version = '2.9.7'
ext.jetty_version = '9.4.19.v20190610'
ext.jetty_version = '9.4.52.v20230823'
ext.jersey_version = '2.25'
ext.servlet_version = '4.0.1'
ext.assertj_version = '3.12.2'
@ -80,7 +81,7 @@ buildscript {
ext.deterministic_rt_version = constants.getProperty('deterministicRtVersion')
ext.okhttp_version = '3.14.2'
ext.netty_version = '4.1.77.Final'
ext.tcnative_version = '2.0.48.Final'
ext.tcnative_version = constants.getProperty("tcnativeVersion")
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
ext.fileupload_version = '1.4'
ext.kryo_version = '4.0.2'
@ -106,7 +107,6 @@ buildscript {
ext.dependency_checker_version = '5.2.0'
ext.commons_collections_version = '4.3'
ext.beanutils_version = '1.9.4'
ext.crash_version = '1.7.6'
ext.jsr305_version = constants.getProperty("jsr305Version")
ext.shiro_version = '1.10.0'
ext.artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion')
@ -130,6 +130,8 @@ buildscript {
ext.controlsfx_version = '8.40.15'
ext.detekt_version = constants.getProperty('detektVersion')
ext.docker_java_version = constants.getProperty("dockerJavaVersion")
ext.commons_configuration2_version = "2.8.0"
ext.commons_text_version = "1.10.0"
if (JavaVersion.current().isJava8()) {
ext.fontawesomefx_commons_version = '8.15'
ext.fontawesomefx_fontawesome_version = '4.7.0-5'
@ -362,7 +364,7 @@ allprojects {
attributes('Corda-Release-Version': corda_release_version)
attributes('Corda-Platform-Version': corda_platform_version)
attributes('Corda-Revision': corda_revision)
attributes('Corda-Vendor': 'Corda Open Source')
attributes('Corda-Vendor': corda_build_edition)
attributes('Automatic-Module-Name': "net.corda.${task.project.name.replaceAll('-', '.')}")
attributes('Corda-Docs-Link': corda_docs_link)
}
@ -411,6 +413,12 @@ allprojects {
repositories {
mavenLocal()
// Prevents cache giving use the wrong artemis
mavenCentral {
content {
includeGroup 'org.apache.activemq'
}
}
// Use system environment to activate caching with Artifactory,
// because it is actually easier to pass that during parallel build.
// NOTE: it has to be a name of a virtual repository with all
@ -453,6 +461,12 @@ allprojects {
includeGroup 'com.github.detro'
}
}
maven {
url "${publicArtifactURL}/corda-releases"
content {
includeModule('net.corda', 'corda-shell')
}
}
mavenCentral()
jcenter()
}
@ -469,7 +483,9 @@ allprojects {
// Force dependencies to use the same version of Guava as Corda.
force "com.google.guava:guava:$guava_version"
// Demand that everything uses our given version of Netty.
// Demand that everything uses our given versions of:
// * Netty
// * Apache commons-configuration2
eachDependency { details ->
if (details.requested.group == 'io.netty' && details.requested.name.startsWith('netty-')) {
if (details.requested.name.startsWith('netty-tcnative')){
@ -479,6 +495,13 @@ allprojects {
}
}
if (details.requested.group == 'org.apache.commons') {
if (details.requested.name == "commons-configuration2") {
details.useVersion commons_configuration2_version
} else if (details.requested.name == "commons-text") {
details.useVersion commons_text_version
}
}
if (details.requested.group == 'org.yaml' && details.requested.name == 'snakeyaml') {
details.useVersion snake_yaml_version
}

View File

@ -2,8 +2,21 @@ package net.corda.client.jackson
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.core.*
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken
import com.fasterxml.jackson.databind.BeanDescription
import com.fasterxml.jackson.databind.DeserializationConfig
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.Module
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.cfg.ConstructorDetector
@ -22,9 +35,21 @@ import net.corda.core.DoNotImplement
import net.corda.core.contracts.Amount
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.*
import net.corda.core.identity.*
import net.corda.core.internal.*
import net.corda.core.crypto.Base58
import net.corda.core.crypto.MerkleTree
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.CertRole
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.isStatic
import net.corda.core.internal.kotlinObjectInstance
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.IdentityService

View File

@ -2,16 +2,30 @@
package net.corda.client.jackson.internal
import com.fasterxml.jackson.annotation.*
import com.fasterxml.jackson.annotation.JsonAutoDetect.Value
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonCreator.Mode.DISABLED
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonInclude.Include
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.annotation.JsonValue
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.BeanDescription
import com.fasterxml.jackson.databind.BeanProperty
import com.fasterxml.jackson.databind.DeserializationConfig
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JavaType
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.Module
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationConfig
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.cfg.MapperConfig
@ -32,12 +46,30 @@ import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer
import com.fasterxml.jackson.databind.ser.std.UUIDSerializer
import com.google.common.primitives.Booleans
import net.corda.client.jackson.JacksonSupport
import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.contracts.Amount
import net.corda.core.contracts.AttachmentConstraint
import net.corda.core.contracts.Command
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.PrivacySalt
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.contracts.TransactionState
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.PartialMerkleTree.PartialTree
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SecureHash.Companion.SHA2_256
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.SignatureScheme
import net.corda.core.crypto.TransactionSignature
import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.*
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.internal.createComponentGroups
import net.corda.core.node.NodeInfo
@ -45,7 +77,12 @@ import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.transactions.*
import net.corda.core.transactions.ContractUpgradeFilteredTransaction
import net.corda.core.transactions.ContractUpgradeWireTransaction
import net.corda.core.transactions.FilteredTransaction
import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.parseAsHex

View File

@ -55,7 +55,9 @@ dependencies {
// TODO: remove the forced update of commons-collections and beanutils when artemis updates them
compile "org.apache.commons:commons-collections4:${commons_collections_version}"
compile "commons-beanutils:commons-beanutils:${beanutils_version}"
compile "org.apache.activemq:artemis-core-client:${artemis_version}"
compile("org.apache.activemq:artemis-core-client:${artemis_version}") {
exclude group: 'org.jgroups', module: 'jgroups'
}
// Unit testing helpers.
testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"

View File

@ -30,6 +30,7 @@ import net.corda.testing.node.internal.rpcTestUser
import net.corda.testing.node.internal.startRandomRpcClient
import net.corda.testing.node.internal.startRpcClient
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration
import org.apache.activemq.artemis.api.core.QueueConfiguration
import org.apache.activemq.artemis.api.core.SimpleString
import org.junit.After
import org.junit.Assert.assertEquals
@ -551,7 +552,11 @@ class RPCStabilityTests {
// Construct an RPC session manually so that we can hang in the message handler
val myQueue = "${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.test.${random63BitValue()}"
val session = startArtemisSession(server.broker.hostAndPort!!)
session.createTemporaryQueue(myQueue, ActiveMQDefaultConfiguration.getDefaultRoutingType(), myQueue)
session.createQueue(QueueConfiguration(myQueue)
.setRoutingType(ActiveMQDefaultConfiguration.getDefaultRoutingType())
.setAddress(myQueue)
.setTemporary(true)
.setDurable(false))
val consumer = session.createConsumer(myQueue, null, -1, -1, false)
consumer.setMessageHandler {
Thread.sleep(5000) // Needs to be slower than one per second to get kicked.
@ -588,7 +593,11 @@ class RPCStabilityTests {
// Construct an RPC client session manually
val myQueue = "${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.test.${random63BitValue()}"
val session = startArtemisSession(server.broker.hostAndPort!!)
session.createTemporaryQueue(myQueue, ActiveMQDefaultConfiguration.getDefaultRoutingType(), myQueue)
session.createQueue(QueueConfiguration(myQueue)
.setRoutingType(ActiveMQDefaultConfiguration.getDefaultRoutingType())
.setAddress(myQueue)
.setTemporary(true)
.setDurable(false))
val consumer = session.createConsumer(myQueue, null, -1, -1, false)
val replies = ArrayList<Any>()
consumer.setMessageHandler {

View File

@ -50,6 +50,7 @@ import kotlin.concurrent.thread
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue
class CordaRPCClientReconnectionTest {
@ -595,6 +596,29 @@ class CordaRPCClientReconnectionTest {
}
}
@Test(timeout=300_000)
fun `reconnecting 'reattachFlowWithClientId' rpc works if called with non-existent client id`() {
driver(DriverParameters(inMemoryDB = false, cordappsForAllNodes = listOf(this.enclosedCordapp()))) {
val address = NetworkHostAndPort("localhost", portAllocator.nextPort())
fun startNode(additionalCustomOverrides: Map<String, Any?> = emptyMap()): NodeHandle {
return startNode(
providedName = CHARLIE_NAME,
rpcUsers = listOf(CordaRPCClientTest.rpcUser),
customOverrides = mapOf("rpcSettings.address" to address.toString()) + additionalCustomOverrides
).getOrThrow()
}
val node = startNode()
val client = CordaRPCClient(node.rpcAddress, config)
(client.start(rpcUser.username, rpcUser.password, gracefulReconnect = gracefulReconnect)).use {
val rpcOps = it.proxy as ReconnectingCordaRPCOps
val nonExistentClientId = UUID.randomUUID().toString()
val flowHandle = rpcOps.reattachFlowWithClientId<Any>(nonExistentClientId)
assertNull(flowHandle)
}
}
}
@StartableByRPC
class SimpleFlow : FlowLogic<Int>() {

View File

@ -98,6 +98,8 @@ class RPCClient<I : RPCOps>(
// By default RoundRobinConnectionLoadBalancingPolicy is used that picks first endpoint from the pool
// at random. This may be undesired and non-deterministic. For more information, see [RoundRobinConnectionPolicy]
connectionLoadBalancingPolicyClassName = RoundRobinConnectionPolicy::class.java.canonicalName
// Without this any type of "send" time failures will not be delivered back to the client
isBlockOnNonDurableSend = true
}
val sessionId = Trace.SessionId.newInstance()
val distributionMux = DistributionMux(listeners, username)

View File

@ -39,6 +39,7 @@ import net.corda.nodeapi.internal.rpc.client.RpcClientObservableDeSerializer
import net.corda.nodeapi.internal.rpc.client.RpcObservableMap
import org.apache.activemq.artemis.api.core.ActiveMQException
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
import org.apache.activemq.artemis.api.core.QueueConfiguration
import org.apache.activemq.artemis.api.core.RoutingType
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE
@ -60,6 +61,7 @@ import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.Future
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
@ -380,11 +382,18 @@ internal class RPCClientProxyHandler(
targetLegalIdentity?.let {
artemisMessage.putStringProperty(RPCApi.RPC_TARGET_LEGAL_IDENTITY, it.toString())
}
sendExecutor!!.submit {
val future: Future<*> = sendExecutor!!.submit {
artemisMessage.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, deduplicationSequenceNumber.getAndIncrement())
log.debug { "-> RPC -> $message" }
rpcProducer!!.send(artemisMessage)
rpcProducer!!.let {
if (!it.isClosed) {
it.send(artemisMessage)
} else {
log.info("Producer is already closed. Not sending: $message")
}
}
}
future.getOrThrow()
}
// The handler for Artemis messages.
@ -570,7 +579,12 @@ internal class RPCClientProxyHandler(
}
if (observableIds != null) {
log.debug { "Reaping ${observableIds.size} observables" }
sendMessage(RPCApi.ClientToServer.ObservablesClosed(observableIds))
@Suppress("TooGenericExceptionCaught")
try {
sendMessage(RPCApi.ClientToServer.ObservablesClosed(observableIds))
} catch(ex: Exception) {
log.warn("Unable to close observables", ex)
}
}
}
@ -632,7 +646,8 @@ internal class RPCClientProxyHandler(
consumerSession = sessionFactory!!.createSession(rpcUsername, rpcPassword, false, true, true, false, 16384)
clientAddress = SimpleString("${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$rpcUsername.${random63BitValue()}")
log.debug { "Client address: $clientAddress" }
consumerSession!!.createTemporaryQueue(clientAddress, RoutingType.ANYCAST, clientAddress)
consumerSession!!.createQueue(QueueConfiguration(clientAddress).setAddress(clientAddress).setRoutingType(RoutingType.ANYCAST)
.setTemporary(true).setDurable(false))
rpcConsumer = consumerSession!!.createConsumer(clientAddress)
rpcConsumer!!.setMessageHandler(this::artemisMessageHandler)
}

View File

@ -393,10 +393,11 @@ class ReconnectingCordaRPCOps private constructor(
initialFeed.copy(updates = observable)
}
FlowHandleWithClientId::class.java -> {
val initialHandle: FlowHandleWithClientId<Any?> = uncheckedCast(doInvoke(method, args,
// initialHandle can be null. See @CordaRPCOps.reattachFlowWithClientId.
val initialHandle: FlowHandleWithClientId<Any?>? = uncheckedCast(doInvoke(method, args,
reconnectingRPCConnection.gracefulReconnect.maxAttempts))
val initialFuture = initialHandle.returnValue
val initialFuture = initialHandle?.returnValue ?: return null
// This is the future that is returned to the client. It will get carried until we reconnect to the node.
val returnFuture = openFuture<Any?>()

View File

@ -40,7 +40,7 @@ import net.corda.nodeapi.internal.config.User
import net.corda.sleeping.SleepingFlow
import net.corda.smoketesting.NodeConfig
import net.corda.smoketesting.NodeProcess
import org.apache.commons.io.output.NullOutputStream
import org.apache.commons.io.output.NullOutputStream.NULL_OUTPUT_STREAM
import org.hamcrest.text.MatchesPattern
import org.junit.After
import org.junit.Before
@ -117,7 +117,7 @@ class StandaloneCordaRPClientTest {
assertEquals(attachment.sha256, id, "Attachment has incorrect SHA256 hash")
val hash = HashingInputStream(Hashing.sha256(), rpcProxy.openAttachment(id)).use { it ->
it.copyTo(NullOutputStream())
it.copyTo(NULL_OUTPUT_STREAM)
SecureHash.SHA256(it.hash().asBytes())
}
assertEquals(attachment.sha256, hash)
@ -132,7 +132,7 @@ class StandaloneCordaRPClientTest {
assertEquals(attachment.sha256, id, "Attachment has incorrect SHA256 hash")
val hash = HashingInputStream(Hashing.sha256(), rpcProxy.openAttachment(id)).use { it ->
it.copyTo(NullOutputStream())
it.copyTo(NULL_OUTPUT_STREAM)
SecureHash.SHA256(it.hash().asBytes())
}
assertEquals(attachment.sha256, hash)

View File

@ -9,4 +9,4 @@ package net.corda.common.logging
* (originally added to source control for ease of use)
*/
internal const val CURRENT_MAJOR_RELEASE = "4.8-SNAPSHOT"
internal const val CURRENT_MAJOR_RELEASE = "4.9-SNAPSHOT"

View File

@ -135,7 +135,7 @@
<DefaultRolloverStrategy min="1" max="100">
<Delete basePath="${archive}" maxDepth="1">
<IfFileName glob="${log-name}*.log.gz"/>
<IfFileName glob="${diagnostic-log-name}*.log.gz"/>
<IfLastModified age="60d">
<IfAny>
<IfAccumulatedFileSize exceeds="10 GB"/>
@ -159,7 +159,7 @@
<DefaultRolloverStrategy min="1" max="100">
<Delete basePath="${archive}" maxDepth="1">
<IfFileName glob="${log-name}*.log.gz"/>
<IfFileName glob="checkpoints_agent*.log.gz"/>
<IfLastModified age="60d">
<IfAny>
<IfAccumulatedFileSize exceeds="10 GB"/>
@ -202,7 +202,15 @@
<AppenderRef ref="Console-ErrorCode-Selector"/>
<AppenderRef ref="RollingFile-ErrorCode-Appender"/>
</Logger>
<Logger name="org.apache.activemq.artemis.core.server" level="error" additivity="false">
<Logger name="org.apache.activemq.artemis.core.server" level="warn" additivity="false">
<Filters>
<RegexFilter regex=".*AMQ222165.*" onMatch="DENY" onMismatch="NEUTRAL"/>
<RegexFilter regex=".*AMQ222166.*" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
<AppenderRef ref="Console-ErrorCode-Selector"/>
<AppenderRef ref="RollingFile-ErrorCode-Appender"/>
</Logger>
<Logger name="org.apache.activemq.audit" level="error" additivity="false">
<AppenderRef ref="Console-ErrorCode-Selector"/>
<AppenderRef ref="RollingFile-ErrorCode-Appender"/>
</Logger>

View File

@ -2,7 +2,7 @@
# because some versions here need to be matched by app authors in
# their own projects. So don't get fancy with syntax!
cordaVersion=4.8
cordaVersion=4.9
versionSuffix=SNAPSHOT
gradlePluginsVersion=5.0.12
kotlinVersion=1.2.71
@ -11,7 +11,7 @@ java8MinUpdateVersion=171
# When incrementing platformVersion make sure to update #
# net.corda.core.internal.CordaUtilsKt.PLATFORM_VERSION as well. #
# ***************************************************************#
platformVersion=10
platformVersion=11
guavaVersion=28.0-jre
# Quasar version to use with Java 8:
quasarVersion=0.7.15_r3
@ -27,7 +27,7 @@ typesafeConfigVersion=1.3.4
jsr305Version=3.0.2
artifactoryPluginVersion=4.16.1
snakeYamlVersion=1.33
caffeineVersion=2.7.0
caffeineVersion=2.9.3
metricsVersion=4.1.0
metricsNewRelicVersion=1.1.1
djvmVersion=1.1.1
@ -36,3 +36,4 @@ openSourceBranch=https://github.com/corda/corda/blob/release/os/4.4
openSourceSamplesBranch=https://github.com/corda/samples/blob/release-V4
jolokiaAgentVersion=1.6.1
detektVersion=1.0.1
tcnativeVersion=2.0.48.Final

View File

@ -28,7 +28,9 @@ import java.util.jar.JarInputStream
// *Internal* Corda-specific utilities.
const val PLATFORM_VERSION = 10
// When incrementing platformVersion make sure to update PLATFORM_VERSION in constants.properties as well.
const val PLATFORM_VERSION = 11
fun ServicesForResolution.ensureMinimumPlatformVersion(requiredMinPlatformVersion: Int, feature: String) {
checkMinimumPlatformVersion(networkParameters.minimumPlatformVersion, requiredMinPlatformVersion, feature)

View File

@ -5,13 +5,20 @@ import net.corda.core.messaging.RPCOps
/**
* RPC operations to perform operations related to flows including management of associated persistent states like checkpoints.
*/
@Deprecated(
"A public version of this interface has been exposed that should be interacted with using the MultiRPCClient",
ReplaceWith("net.corda.core.messaging.flows.FlowManagerRPCOps")
)
interface FlowManagerRPCOps : RPCOps {
/**
* Dump all the current flow checkpoints as JSON into a zip file in the node's log directory.
*/
fun dumpCheckpoints()
/** Dump all the current flow checkpoints, alongside with the node's main jar, all CorDapps and driver jars
* into a zip file in the node's log directory. */
/**
* Dump all the current flow checkpoints, alongside with the node's main jar, all CorDapps and driver jars into a zip file in the node's
* log directory.
*/
fun debugCheckpoints()
}

View File

@ -0,0 +1,20 @@
package net.corda.core.messaging.flows
import net.corda.core.messaging.RPCOps
/**
* RPC operations to perform operations related to flows including management of associated persistent states like checkpoints.
*/
interface FlowManagerRPCOps : RPCOps {
/**
* Dump all the current flow checkpoints as JSON into a zip file in the node's log directory.
*/
fun dumpCheckpoints()
/**
* Dump all the current flow checkpoints, alongside with the node's main jar, all CorDapps and driver jars into a zip file in the node's
* log directory.
*/
fun debugCheckpoints()
}

View File

@ -741,9 +741,11 @@ open class TransactionBuilder(
addReferenceState(resolvedStateAndRef.referenced())
}
} else {
log.warn("WARNING: You must pass in a ServiceHub reference to TransactionBuilder to resolve " +
"state pointers outside of flows. If you are writing a unit test then pass in a " +
"MockServices instance.")
if (nextStatePointer.isResolved) {
log.warn("WARNING: You must pass in a ServiceHub reference to TransactionBuilder to resolve " +
"state pointers outside of flows. If you are writing a unit test then pass in a " +
"MockServices instance.")
}
return
}
}

View File

@ -1364,7 +1364,6 @@
<ID>ThrowsCount:AMQPTypeIdentifierParser.kt$AMQPTypeIdentifierParser$// Make sure our inputs aren't designed to blow things up. private fun validate(typeString: String)</ID>
<ID>ThrowsCount:AbstractNode.kt$AbstractNode$private fun installCordaServices()</ID>
<ID>ThrowsCount:ArtemisMessagingServer.kt$ArtemisMessagingServer$// TODO: Maybe wrap [IOException] on a key store load error so that it's clearly splitting key store loading from // Artemis IO errors @Throws(IOException::class, AddressBindingException::class, KeyStoreException::class) private fun configureAndStartServer()</ID>
<ID>ThrowsCount:BrokerJaasLoginModule.kt$BaseBrokerJaasLoginModule$@Suppress("DEPRECATION") // should use java.security.cert.X509Certificate protected fun getUsernamePasswordAndCerts(): Triple&lt;String, String, Array&lt;javax.security.cert.X509Certificate&gt;?&gt;</ID>
<ID>ThrowsCount:CheckpointVerifier.kt$CheckpointVerifier$ fun verifyCheckpointsCompatible( checkpointStorage: CheckpointStorage, currentCordapps: List&lt;Cordapp&gt;, platformVersion: Int, serviceHub: ServiceHub, tokenizableServices: List&lt;Any&gt; )</ID>
<ID>ThrowsCount:CheckpointVerifier.kt$CheckpointVerifier$// Throws exception when the flow is incompatible private fun checkFlowCompatible(subFlow: SubFlow, currentCordappsByHash: Map&lt;SecureHash.SHA256, Cordapp&gt;, platformVersion: Int)</ID>
<ID>ThrowsCount:ClassCarpenter.kt$ClassCarpenterImpl$ private fun validateSchema(schema: Schema)</ID>

View File

@ -33,47 +33,34 @@ shadowJar {
}
enum ImageVariant {
UBUNTU_ZULU("zulu", "Dockerfile", "1.8"),
UBUNTU_ZULU_11("zulu", "Dockerfile11", "11"),
AL_CORRETTO("corretto", "DockerfileAL", "1.8"),
UBUNTU_ZULU("Dockerfile", "1.8", "zulu-openjdk8"),
UBUNTU_ZULU_11("Dockerfile11", "11", "zulu-openjdk11"),
AL_CORRETTO("DockerfileAL", "1.8", "amazonlinux2"),
OFFICIAL(UBUNTU_ZULU)
String knownAs
String dockerFile
String javaVersion
String versionString(String baseTag, String version) {
return "${baseTag}-${knownAs}" +
(knownAs.isEmpty() ? "" : "-") +
"java${javaVersion}-" + version
}
String baseImgaeFullName
ImageVariant(ImageVariant other) {
this.knownAs = other.knownAs
this.dockerFile = other.dockerFile
this.javaVersion = other.javaVersion
this.baseImgaeFullName = other.baseImgaeFullName
}
ImageVariant(String knownAs, String dockerFile, String javaVersion) {
this.knownAs = knownAs
ImageVariant(String dockerFile, String javaVersion, String baseImgaeFullName) {
this.dockerFile = dockerFile
this.javaVersion = javaVersion
this.baseImgaeFullName = baseImgaeFullName
}
static final String getRepository(Project project) {
return project.properties.getOrDefault("docker.image.repository", "corda/corda")
}
static private final String runTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
def getName(Project project) {
return versionString(getRepository(project), project.version.toString().toLowerCase())
}
Set<Identifier> buildTags(Project project) {
final String suffix = project.version.toString().toLowerCase().contains("snapshot") ? runTime : "RELEASE"
return [suffix, "latest"].stream().map {
toAppend -> "${getName(project)}:${toAppend}".toString()
return ["${project.version.toString().toLowerCase()}-${baseImgaeFullName}"].stream().map {
toAppend -> "${getRepository(project)}:${toAppend}".toString()
}.map(Identifier.&fromCompoundString).collect(Collectors.toSet())
}

View File

@ -1,8 +1,8 @@
#!/usr/bin/env bash
NODE_LIST=("dockerNode1" "dockerNode2" "dockerNode3")
NETWORK_NAME=mininet
CORDAPP_VERSION="4.8-SNAPSHOT"
DOCKER_IMAGE_VERSION="corda-zulu-4.8-snapshot"
CORDAPP_VERSION="4.9-SNAPSHOT"
DOCKER_IMAGE_VERSION="corda-zulu-4.9-snapshot"
mkdir cordapps
rm -f cordapps/*

View File

@ -1,4 +1,4 @@
FROM azul/zulu-openjdk:8u312
FROM azul/zulu-openjdk:8u382
## Remove Azul Zulu repo, as it is gone by now
RUN rm -rf /etc/apt/sources.list.d/zulu.list

View File

@ -1,4 +1,4 @@
FROM azul/zulu-openjdk:8u312
FROM azul/zulu-openjdk:8u382
## Add packages, clean cache, create dirs, create corda user and change ownership
RUN apt-get update && \

View File

@ -1,11 +1,10 @@
FROM amazonlinux:2
FROM amazoncorretto:8u382-al2
## Add packages, clean cache, create dirs, create corda user and change ownership
RUN amazon-linux-extras enable corretto8 && \
yum -y install java-1.8.0-amazon-corretto-devel && \
yum -y install bash && \
RUN yum -y install bash && \
yum -y install curl && \
yum -y install unzip && \
yum -y install shadow-utils.x86_64 && \
yum clean all && \
rm -rf /var/cache/yum && \
mkdir -p /opt/corda/cordapps && \

View File

@ -17,7 +17,9 @@ dependencies {
// TODO: remove the forced update of commons-collections and beanutils when artemis updates them
compile "org.apache.commons:commons-collections4:${commons_collections_version}"
compile "commons-beanutils:commons-beanutils:${beanutils_version}"
compile "org.apache.activemq:artemis-core-client:${artemis_version}"
compile("org.apache.activemq:artemis-core-client:${artemis_version}") {
exclude group: 'org.jgroups', module: 'jgroups'
}
compile "org.apache.activemq:artemis-commons:${artemis_version}"
compile "io.netty:netty-handler-proxy:$netty_version"
@ -65,6 +67,7 @@ dependencies {
compile ("org.apache.activemq:artemis-amqp-protocol:${artemis_version}") {
// Gains our proton-j version from core module.
exclude group: 'org.apache.qpid', module: 'proton-j'
exclude group: 'org.jgroups', module: 'jgroups'
}
}

View File

@ -67,7 +67,6 @@ class ArtemisMessagingClient(private val config: MutualSslConfiguration,
retryInterval = messagingServerConnectionConfig.retryInterval().toMillis()
retryIntervalMultiplier = messagingServerConnectionConfig.retryIntervalMultiplier()
maxRetryInterval = messagingServerConnectionConfig.maxRetryInterval(isHA).toMillis()
isFailoverOnInitialConnection = messagingServerConnectionConfig.failoverOnInitialAttempt(isHA)
initialConnectAttempts = messagingServerConnectionConfig.initialConnectAttempts(isHA)
}
addIncomingInterceptor(ArtemisMessageSizeChecksInterceptor(maxMessageSize))

View File

@ -53,7 +53,7 @@ class ArtemisTcpTransport {
keyStore?.let {
with (it) {
path.requireOnDefaultFileSystem()
options[TransportConstants.KEYSTORE_PROVIDER_PROP_NAME] = "JKS"
options[TransportConstants.KEYSTORE_TYPE_PROP_NAME] = "JKS"
options[TransportConstants.KEYSTORE_PATH_PROP_NAME] = path
options[TransportConstants.KEYSTORE_PASSWORD_PROP_NAME] = get().password
}
@ -61,7 +61,7 @@ class ArtemisTcpTransport {
trustStore?.let {
with (it) {
path.requireOnDefaultFileSystem()
options[TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME] = "JKS"
options[TransportConstants.TRUSTSTORE_TYPE_PROP_NAME] = "JKS"
options[TransportConstants.TRUSTSTORE_PATH_PROP_NAME] = path
options[TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME] = get().password
}
@ -72,13 +72,13 @@ class ArtemisTcpTransport {
private fun ClientRpcSslOptions.toTransportOptions() = mapOf(
TransportConstants.SSL_ENABLED_PROP_NAME to true,
TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME to trustStoreProvider,
TransportConstants.TRUSTSTORE_TYPE_PROP_NAME to trustStoreProvider,
TransportConstants.TRUSTSTORE_PATH_PROP_NAME to trustStorePath,
TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME to trustStorePassword)
private fun BrokerRpcSslOptions.toTransportOptions() = mapOf(
TransportConstants.SSL_ENABLED_PROP_NAME to true,
TransportConstants.KEYSTORE_PROVIDER_PROP_NAME to "JKS",
TransportConstants.KEYSTORE_TYPE_PROP_NAME to "JKS",
TransportConstants.KEYSTORE_PATH_PROP_NAME to keyStorePath,
TransportConstants.KEYSTORE_PASSWORD_PROP_NAME to keyStorePassword,
TransportConstants.NEED_CLIENT_AUTH_PROP_NAME to false)
@ -186,10 +186,7 @@ class ArtemisTcpTransport {
options[TransportConstants.HANDSHAKE_TIMEOUT] = 0
if (trustManagerFactory != null) {
// NettyAcceptor only creates default TrustManagerFactorys with the provided trust store details. However, we need to use
// more customised instances which use our revocation checkers, which we pass directly into NodeNettyAcceptorFactory.
//
// This, however, requires copying a lot of code from NettyAcceptor into NodeNettyAcceptor. The version of Artemis in
// Corda 4.9 solves this problem by introducing a "trustManagerFactoryPlugin" config option.
// more customised instances which use our revocation checkers, so we pass them in, to be picked up by Node(Open)SSLContextFactory.
options[TRUST_MANAGER_FACTORY_NAME] = trustManagerFactory
}
return createTransport(
@ -211,6 +208,10 @@ class ArtemisTcpTransport {
threadPoolName: String,
trace: Boolean,
remotingThreads: Int?): TransportConfiguration {
if (enableSSL) {
// This is required to stop Client checking URL address vs. Server provided certificate
options[TransportConstants.VERIFY_HOST_PROP_NAME] = false
}
return createTransport(
CordaNettyConnectorFactory::class.java.name,
hostAndPort,

View File

@ -10,11 +10,11 @@ import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection
class ArtemisMessageSizeChecksInterceptor(maxMessageSize: Int) : MessageSizeChecksInterceptor<Packet>(maxMessageSize), Interceptor {
override fun getMessageSize(packet: Packet?): Int? {
override fun getMessageSize(packet: Packet?): Long? {
return when (packet) {
// This is an estimate of how much memory a Message body takes up.
// Note, it is only an estimate
is MessagePacket -> (packet.message.persistentSize - packet.message.headersAndPropertiesEncodeSize - 4).toInt()
is MessagePacket -> (packet.message.persistentSize - packet.message.headersAndPropertiesEncodeSize - 4)
// Skip all artemis control messages.
else -> null
}
@ -22,7 +22,7 @@ class ArtemisMessageSizeChecksInterceptor(maxMessageSize: Int) : MessageSizeChec
}
class AmqpMessageSizeChecksInterceptor(maxMessageSize: Int) : MessageSizeChecksInterceptor<AMQPMessage>(maxMessageSize), AmqpInterceptor {
override fun getMessageSize(packet: AMQPMessage?): Int? = packet?.encodeSize
override fun getMessageSize(packet: AMQPMessage?): Long? = packet?.wholeMessageSize
}
/**
@ -45,6 +45,6 @@ sealed class MessageSizeChecksInterceptor<T : Any>(private val maxMessageSize: I
}
// get size of the message in byte, returns null if the message is null or size don't need to be checked.
abstract fun getMessageSize(packet: T?): Int?
abstract fun getMessageSize(packet: T?): Long?
}

View File

@ -17,6 +17,7 @@ import net.corda.nodeapi.internal.protonwrapper.netty.ProxyConfig
import net.corda.nodeapi.internal.protonwrapper.netty.RevocationConfig
import org.apache.activemq.artemis.api.core.ActiveMQNonExistentQueueException
import org.apache.activemq.artemis.api.core.ActiveMQQueueExistsException
import org.apache.activemq.artemis.api.core.QueueConfiguration
import org.apache.activemq.artemis.api.core.RoutingType
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.client.ClientConsumer
@ -105,7 +106,9 @@ class BridgeControlListener(private val keyStore: CertificateStore,
private fun registerBridgeControlListener(artemisSession: ClientSession) {
try {
artemisSession.createTemporaryQueue(BRIDGE_CONTROL, RoutingType.MULTICAST, bridgeControlQueue)
artemisSession.createQueue(
QueueConfiguration(bridgeControlQueue).setAddress(BRIDGE_CONTROL).setRoutingType(RoutingType.MULTICAST)
.setTemporary(true).setDurable(false))
} catch (ex: ActiveMQQueueExistsException) {
// Ignore if there is a queue still not cleaned up
}
@ -125,7 +128,9 @@ class BridgeControlListener(private val keyStore: CertificateStore,
private fun registerBridgeDuplicateChecker(artemisSession: ClientSession) {
try {
artemisSession.createTemporaryQueue(BRIDGE_NOTIFY, RoutingType.MULTICAST, bridgeNotifyQueue)
artemisSession.createQueue(
QueueConfiguration(bridgeNotifyQueue).setAddress(BRIDGE_NOTIFY).setRoutingType(RoutingType.MULTICAST)
.setTemporary(true).setDurable(false))
} catch (ex: ActiveMQQueueExistsException) {
// Ignore if there is a queue still not cleaned up
}

View File

@ -15,7 +15,6 @@ import java.time.Duration
*
* totalFailoverDuration = 5 + 5 * 1.5 + 5 * (1.5)^2 + 5 * (1.5)^3 + 5 * (1.5)^4 = ~66 seconds
*
* @param failoverOnInitialAttempt Determines whether failover is triggered if initial connection fails.
* @param initialConnectAttempts The number of reconnect attempts if failover is enabled for initial connection. A value
* of -1 represents infinite attempts.
* @param reconnectAttempts The number of reconnect attempts for failover after initial connection is done. A value
@ -27,7 +26,6 @@ import java.time.Duration
enum class MessagingServerConnectionConfiguration {
DEFAULT {
override fun failoverOnInitialAttempt(isHa: Boolean) = true
override fun initialConnectAttempts(isHa: Boolean) = 5
override fun reconnectAttempts(isHa: Boolean) = 5
override fun retryInterval() = 5.seconds
@ -36,7 +34,6 @@ enum class MessagingServerConnectionConfiguration {
},
FAIL_FAST {
override fun failoverOnInitialAttempt(isHa: Boolean) = isHa
override fun initialConnectAttempts(isHa: Boolean) = 0
// Client die too fast during failover/failback, need a few reconnect attempts to allow new master to become active
override fun reconnectAttempts(isHa: Boolean) = if (isHa) 3 else 0
@ -46,7 +43,6 @@ enum class MessagingServerConnectionConfiguration {
},
CONTINUOUS_RETRY {
override fun failoverOnInitialAttempt(isHa: Boolean) = true
override fun initialConnectAttempts(isHa: Boolean) = if (isHa) 0 else -1
override fun reconnectAttempts(isHa: Boolean) = -1
override fun retryInterval() = 5.seconds
@ -54,7 +50,6 @@ enum class MessagingServerConnectionConfiguration {
override fun maxRetryInterval(isHa: Boolean) = if (isHa) 3.minutes else 5.minutes
};
abstract fun failoverOnInitialAttempt(isHa: Boolean): Boolean
abstract fun initialConnectAttempts(isHa: Boolean): Int
abstract fun reconnectAttempts(isHa: Boolean): Int
abstract fun retryInterval(): Duration

View File

@ -112,12 +112,11 @@ class DatabaseTransaction(
} finally {
clearException()
contextTransactionOrNull = outerTransaction
}
if (outerTransaction == null) {
synchronized(this) {
closed = true
boundary.onNext(CordaPersistence.Boundary(id, committed))
if (outerTransaction == null) {
synchronized(this) {
closed = true
boundary.onNext(CordaPersistence.Boundary(id, committed))
}
}
}
}

View File

@ -47,6 +47,7 @@ internal class ConnectionStateMachine(private val serverMode: Boolean,
companion object {
private const val CORDA_AMQP_FRAME_SIZE_PROP_NAME = "net.corda.nodeapi.connectionstatemachine.AmqpMaxFrameSize"
private const val CORDA_AMQP_IDLE_TIMEOUT_PROP_NAME = "net.corda.nodeapi.connectionstatemachine.AmqpIdleTimeout"
private const val CREATE_ADDRESS_PERMISSION_ERROR = "AMQ229032"
private val MAX_FRAME_SIZE = Integer.getInteger(CORDA_AMQP_FRAME_SIZE_PROP_NAME, 128 * 1024)
private val IDLE_TIMEOUT = Integer.getInteger(CORDA_AMQP_IDLE_TIMEOUT_PROP_NAME, 10 * 1000)
@ -350,14 +351,35 @@ internal class ConnectionStateMachine(private val serverMode: Boolean,
override fun onLinkRemoteClose(e: Event) {
val link = e.link
if(link.remoteCondition != null) {
logWarnWithMDC("Connection closed due to error on remote side: `${link.remoteCondition.description}`")
if (link.remoteCondition != null) {
val remoteConditionDescription = link.remoteCondition.description
logWarnWithMDC("Connection closed due to error on remote side: `$remoteConditionDescription`")
// Description normally looks as follows:
// "AMQ229032: User: SystemUsers/Peer does not have permission='CREATE_ADDRESS' on address p2p.inbound.Test"
if (remoteConditionDescription.contains(CREATE_ADDRESS_PERMISSION_ERROR)) {
handleRemoteCreatePermissionError(e)
}
transport.condition = link.condition
transport.close_tail()
transport.pop(Math.max(0, transport.pending())) // Force generation of TRANSPORT_HEAD_CLOSE (not in C code)
}
}
/**
* If an the artemis channel does not exist on the counterparty, then a create permission error is returned in the [event].
* Do not retry messages to this channel as it will result in an infinite loop of retries.
* Log the error, mark the messages as acknowledged and clear them from the message queue.
*/
private fun handleRemoteCreatePermissionError(event: Event) {
val remoteP2PAddress = event.sender.source.address
logWarnWithMDC("Address does not exist on peer: $remoteP2PAddress. Marking messages sent to this address as Acknowledged.")
messageQueues[remoteP2PAddress]?.apply {
forEach { it.doComplete(MessageStatus.Acknowledged) }
clear()
}
}
override fun onLinkFinal(event: Event) {
val link = event.link
if (link is Sender) {

View File

@ -300,6 +300,7 @@ internal class AMQPChannelHandler(private val serverMode: Boolean,
cause is SSLException && (cause.message?.contains("close_notify") == true) -> logWarnWithMDC("Received close_notify during handshake")
// io.netty.handler.ssl.SslHandler.setHandshakeFailureTransportFailure()
cause is SSLException && (cause.message?.contains("writing TLS control frames") == true) -> logWarnWithMDC(cause.message!!)
cause is SSLException && (cause.message?.contains("internal_error") == true) -> logWarnWithMDC("Received internal_error during handshake")
else -> badCert = true
}
if (log.isTraceEnabled) {

View File

@ -94,7 +94,7 @@ processTestResources {
dependencies {
compile project(':node-api')
compile project(':client:rpc')
compile project(':tools:shell')
compile project(':client:jackson')
compile project(':tools:cliutils')
compile project(':common-validation')
compile project(':common-configuration-parsing')
@ -127,11 +127,17 @@ dependencies {
// TODO: remove the forced update of commons-collections and beanutils when artemis updates them
compile "org.apache.commons:commons-collections4:${commons_collections_version}"
compile "commons-beanutils:commons-beanutils:${beanutils_version}"
compile "org.apache.activemq:artemis-server:${artemis_version}"
compile "org.apache.activemq:artemis-core-client:${artemis_version}"
compile("org.apache.activemq:artemis-server:${artemis_version}") {
exclude group: 'org.apache.commons', module: 'commons-dbcp2'
exclude group: 'org.jgroups', module: 'jgroups'
}
compile("org.apache.activemq:artemis-core-client:${artemis_version}") {
exclude group: 'org.jgroups', module: 'jgroups'
}
runtime("org.apache.activemq:artemis-amqp-protocol:${artemis_version}") {
// Gains our proton-j version from core module.
exclude group: 'org.apache.qpid', module: 'proton-j'
exclude group: 'org.jgroups', module: 'jgroups'
}
// Manifests: for reading stuff from the manifest file
@ -200,7 +206,6 @@ dependencies {
// BFT-Smart dependencies
compile 'com.github.bft-smart:library:master-v1.1-beta-g6215ec8-87'
compile 'commons-codec:commons-codec:1.13'
// Java Atomix: RAFT library
compile 'io.atomix.copycat:copycat-client:1.2.3'

View File

@ -3,6 +3,7 @@ package net.corda.node
import co.paralleluniverse.fibers.Suspendable
import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.PermissionException
import net.corda.client.rpc.RPCException
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC
@ -151,7 +152,7 @@ class AuthDBTests : NodeBasedTest(cordappPackages = CORDAPPS) {
proxy.stateMachinesFeed()
assertFailsWith(
PermissionException::class,
"This user should not be authorized to call 'nodeInfo'") {
"This user should not be authorized to call 'stateMachinesFeed'") {
proxy.nodeInfo()
}
}
@ -185,7 +186,7 @@ class AuthDBTests : NodeBasedTest(cordappPackages = CORDAPPS) {
val proxy = it.proxy
assertFailsWith(
PermissionException::class,
"This user should not be authorized to call 'nodeInfo'") {
"This user should not be authorized to call 'stateMachinesFeed'") {
proxy.stateMachinesFeed()
}
db.addRoleToUser("user3", "default")
@ -207,8 +208,8 @@ class AuthDBTests : NodeBasedTest(cordappPackages = CORDAPPS) {
db.deleteUser("user4")
Thread.sleep(1500)
assertFailsWith(
PermissionException::class,
"This user should not be authorized to call 'nodeInfo'") {
RPCException::class,
"This user should not be authorized to call 'stateMachinesFeed'") {
proxy.stateMachinesFeed()
}
}

View File

@ -13,6 +13,7 @@ import kotlin.test.assertTrue
class NodeRPCTests {
private val CORDA_VERSION_REGEX = "\\d+(\\.\\d+)?(\\.\\d+)?(-\\w+)?".toRegex()
private val CORDA_VENDOR = "Corda Open Source"
private val CORDA_VENDOR_CE = "Corda Community Edition"
private val CORDAPPS = listOf(FINANCE_CONTRACTS_CORDAPP, FINANCE_WORKFLOWS_CORDAPP)
private val CORDAPP_TYPES = setOf("Contract CorDapp", "Workflow CorDapp")
private val CLASSIFIER = if (SystemUtils.IS_JAVA_11) "-jdk11" else ""
@ -29,7 +30,7 @@ class NodeRPCTests {
val nodeDiagnosticInfo = startNode().get().rpc.nodeDiagnosticInfo()
assertTrue(nodeDiagnosticInfo.version.matches(CORDA_VERSION_REGEX))
assertEquals(PLATFORM_VERSION, nodeDiagnosticInfo.platformVersion)
assertEquals(CORDA_VENDOR, nodeDiagnosticInfo.vendor)
assertTrue(nodeDiagnosticInfo.vendor == CORDA_VENDOR || nodeDiagnosticInfo.vendor == CORDA_VENDOR_CE)
nodeDiagnosticInfo.cordapps.forEach { println("${it.shortName} ${it.type}") }
assertEquals(CORDAPPS.size, nodeDiagnosticInfo.cordapps.size)
assertEquals(CORDAPP_TYPES, nodeDiagnosticInfo.cordapps.map { it.type }.toSet())

View File

@ -24,6 +24,7 @@ import net.corda.coretesting.internal.rigorousMock
import net.corda.coretesting.internal.stubs.CertificateStoreStubs
import net.corda.nodeapi.internal.protonwrapper.netty.toRevocationConfig
import org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID
import org.apache.activemq.artemis.api.core.QueueConfiguration
import org.apache.activemq.artemis.api.core.RoutingType
import org.apache.activemq.artemis.api.core.SimpleString
import org.junit.Assert.assertArrayEquals
@ -222,7 +223,8 @@ class AMQPBridgeTest {
val artemis = artemisClient.started!!
if (sourceQueueName != null) {
// Local queue for outgoing messages
artemis.session.createQueue(sourceQueueName, RoutingType.ANYCAST, sourceQueueName, true)
artemis.session.createQueue(
QueueConfiguration(sourceQueueName).setRoutingType(RoutingType.ANYCAST).setAddress(sourceQueueName).setDurable(true))
bridgeManager.deployBridge(ALICE_NAME.toString(), sourceQueueName, listOf(amqpAddress), setOf(BOB.name))
}
return Triple(artemisServer, artemisClient, bridgeManager)

View File

@ -37,6 +37,7 @@ import net.corda.testing.node.internal.network.CrlServer
import net.corda.testing.node.internal.network.CrlServer.Companion.EMPTY_CRL
import net.corda.testing.node.internal.network.CrlServer.Companion.NODE_CRL
import net.corda.testing.node.internal.network.CrlServer.Companion.withCrlDistPoint
import org.apache.activemq.artemis.api.core.QueueConfiguration
import org.apache.activemq.artemis.api.core.RoutingType
import org.assertj.core.api.Assertions.assertThat
import org.bouncycastle.jce.provider.BouncyCastleProvider
@ -496,7 +497,9 @@ class ArtemisServerRevocationTest : AbstractServerRevocationTest() {
}
val queueName = "${P2P_PREFIX}Test"
artemisNode.client.started!!.session.createQueue(queueName, RoutingType.ANYCAST, queueName, true)
artemisNode.client.started!!.session.createQueue(
QueueConfiguration(queueName).setRoutingType(RoutingType.ANYCAST).setAddress(queueName).setDurable(true)
)
val clientConnectionChangeStatus = client.waitForInitialConnectionAndCaptureChanges(expectedConnectedStatus)

View File

@ -36,6 +36,7 @@ import net.corda.testing.core.CHARLIE_NAME
import net.corda.testing.core.MAX_MESSAGE_SIZE
import net.corda.testing.driver.internal.incrementalPortAllocation
import net.corda.testing.internal.createDevIntermediateCaCertPath
import org.apache.activemq.artemis.api.core.QueueConfiguration
import org.apache.activemq.artemis.api.core.RoutingType
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Assert.assertArrayEquals
@ -272,7 +273,8 @@ class ProtonWrapperTests {
assertEquals(CHARLIE_NAME, CordaX500Name.build(clientConnected.get().remoteCert!!.subjectX500Principal))
val artemis = artemisClient.started!!
val sendAddress = P2P_PREFIX + "Test"
artemis.session.createQueue(sendAddress, RoutingType.ANYCAST, "queue", true)
artemis.session.createQueue(QueueConfiguration("queue")
.setRoutingType(RoutingType.ANYCAST).setAddress(sendAddress).setDurable(true))
val consumer = artemis.session.createConsumer("queue")
val testData = "Test".toByteArray()
val testProperty = mutableMapOf<String, Any?>()
@ -290,23 +292,26 @@ class ProtonWrapperTests {
@Test(timeout=300_000)
fun `Send a message larger then maxMessageSize from AMQP to Artemis inbox`() {
val maxMessageSize = 100_000
val (server, artemisClient) = createArtemisServerAndClient(maxMessageSize)
val amqpClient = createClient(maxMessageSize)
val maxUserPayloadSize = 100_000
val maxMessageSizeWithHeaders = maxUserPayloadSize + 512 // Adding a small "shim" to account for headers
// and other non-payload bits of data
val (server, artemisClient) = createArtemisServerAndClient(maxMessageSizeWithHeaders)
val amqpClient = createClient(maxMessageSizeWithHeaders)
val clientConnected = amqpClient.onConnection.toFuture()
amqpClient.start()
assertEquals(true, clientConnected.get().connected)
assertEquals(CHARLIE_NAME, CordaX500Name.build(clientConnected.get().remoteCert!!.subjectX500Principal))
val artemis = artemisClient.started!!
val sendAddress = P2P_PREFIX + "Test"
artemis.session.createQueue(sendAddress, RoutingType.ANYCAST, "queue", true)
artemis.session.createQueue(QueueConfiguration("queue")
.setRoutingType(RoutingType.ANYCAST).setAddress(sendAddress).setDurable(true))
val consumer = artemis.session.createConsumer("queue")
val testProperty = mutableMapOf<String, Any?>()
testProperty["TestProp"] = "1"
// Send normal message.
val testData = ByteArray(maxMessageSize)
val testData = ByteArray(maxUserPayloadSize)
val message = amqpClient.createMessage(testData, sendAddress, CHARLIE_NAME.toString(), testProperty)
amqpClient.write(message)
assertEquals(MessageStatus.Acknowledged, message.onComplete.get())
@ -314,8 +319,8 @@ class ProtonWrapperTests {
assertEquals("1", received.getStringProperty("TestProp"))
assertArrayEquals(testData, ByteArray(received.bodySize).apply { received.bodyBuffer.readBytes(this) })
// Send message larger then max message size.
val largeData = ByteArray(maxMessageSize + 1)
// Send message larger than max message size.
val largeData = ByteArray(maxMessageSizeWithHeaders + 1)
// Create message will fail.
assertThatThrownBy {
amqpClient.createMessage(largeData, sendAddress, CHARLIE_NAME.toString(), testProperty)
@ -393,7 +398,7 @@ class ProtonWrapperTests {
}
@Test(timeout=300_000)
fun `Message sent from AMQP to non-existent Artemis inbox is rejected and client disconnects`() {
fun `Message sent from AMQP to non-existent Artemis inbox is marked as acknowledged to avoid infinite retries`() {
val (server, artemisClient) = createArtemisServerAndClient()
val amqpClient = createClient()
// AmqpClient is set to auto-reconnect, there might be multiple connect/disconnect rounds
@ -413,8 +418,9 @@ class ProtonWrapperTests {
testProperty["TestProp"] = "1"
val message = amqpClient.createMessage(testData, sendAddress, CHARLIE_NAME.toString(), testProperty)
amqpClient.write(message)
assertEquals(MessageStatus.Rejected, message.onComplete.get())
assertTrue(connectedStack.contains(false))
assertEquals(MessageStatus.Acknowledged, message.onComplete.get())
assertTrue(connectedStack.contains(true))
assertEquals(1, connectedStack.size)
amqpClient.stop()
artemisClient.stop()
server.stop()

View File

@ -31,6 +31,7 @@ import java.security.PublicKey
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNull
import kotlin.test.assertNotNull
class CertificateRotationTest {
private val ref = OpaqueBytes.of(0x01)
@ -180,7 +181,7 @@ class CertificateRotationTest {
advertiseNodesToNetwork(mockNet.defaultNotaryNode, bob2, charlie)
assertNull(bob2.services.identityService.wellKnownPartyFromX500Name(ALICE_NAME))
assertNotNull(bob2.services.identityService.wellKnownPartyFromX500Name(ALICE_NAME))
assertNull(charlie.services.identityService.wellKnownPartyFromX500Name(ALICE_NAME))
bob2.services.startFlow(CashPaymentFlow(1000.DOLLARS, charlie.party, false))

View File

@ -183,6 +183,7 @@ class ArtemisMessagingTest {
messagingClient.send(tooLagerMessage, messagingClient.myAddress)
}.isInstanceOf(ActiveMQConnectionTimedOutException::class.java)
assertNull(receivedMessages.poll(200, MILLISECONDS))
this.messagingClient = null
}
@Test(timeout=300_000)
@ -232,7 +233,9 @@ class ArtemisMessagingTest {
MetricRegistry(),
TestingNamedCacheFactory(),
isDrainingModeOn = { false },
drainingModeWasChangedEvents = PublishSubject.create<Pair<Boolean, Boolean>>()).apply {
drainingModeWasChangedEvents = PublishSubject.create<Pair<Boolean, Boolean>>(),
terminateOnConnectionError = false,
timeoutConfig = P2PMessagingClient.TimeoutConfig(10.seconds, 10.seconds, 10.seconds)).apply {
config.configureWithDevSSLCertificate()
messagingClient = this
}

View File

@ -0,0 +1,67 @@
@file:Suppress("DEPRECATION")
package net.corda.node.services.messaging
import net.corda.client.rpc.ext.MultiRPCClient
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.internal.isRegularFile
import net.corda.core.internal.list
import net.corda.core.messaging.flows.FlowManagerRPCOps
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.node.internal.NodeStartup
import net.corda.node.services.Permissions
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.node.User
import org.junit.Test
import kotlin.test.assertNotNull
import net.corda.core.internal.messaging.FlowManagerRPCOps as InternalFlowManagerRPCOps
class FlowManagerRPCOpsTest {
@Test(timeout = 300_000)
fun `net_corda_core_internal_messaging_FlowManagerRPCOps can be accessed using the MultiRPCClient`() {
val user = User("user", "password", setOf(Permissions.all()))
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
val client = MultiRPCClient(nodeAHandle.rpcAddress, InternalFlowManagerRPCOps::class.java, user.username, user.password)
val logDirPath = nodeAHandle.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME
logDirPath.createDirectories()
client.use {
val rpcOps = it.start().getOrThrow(20.seconds).proxy
rpcOps.dumpCheckpoints()
it.stop()
}
assertNotNull(logDirPath.list().singleOrNull { it.isRegularFile() })
}
}
@Test(timeout = 300_000)
fun `net_corda_core_messaging_flows_FlowManagerRPCOps can be accessed using the MultiRPCClient`() {
val user = User("user", "password", setOf(Permissions.all()))
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
val client = MultiRPCClient(nodeAHandle.rpcAddress, FlowManagerRPCOps::class.java, user.username, user.password)
val logDirPath = nodeAHandle.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME
logDirPath.createDirectories()
client.use {
val rpcOps = it.start().getOrThrow(20.seconds).proxy
rpcOps.dumpCheckpoints()
it.stop()
}
assertNotNull(logDirPath.list().singleOrNull { it.isRegularFile() })
}
}
}

View File

@ -4,6 +4,8 @@ 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.crypto.SecureHash
import net.corda.core.flows.CollectSignaturesFlow
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
@ -12,20 +14,29 @@ import net.corda.core.flows.HospitalizeFlowException
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.NotaryException
import net.corda.core.flows.NotaryFlow
import net.corda.core.flows.ReceiveFinalityFlow
import net.corda.core.flows.SignTransactionFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.flows.UnexpectedFlowEndException
import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.transpose
import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.messaging.startFlow
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.core.utilities.unwrap
import net.corda.node.services.Permissions
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyContract.SingleOwnerState
import net.corda.testing.contracts.DummyState
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.node.User
@ -33,6 +44,7 @@ import net.corda.testing.node.internal.enclosedCordapp
import net.corda.testing.node.internal.findCordapp
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before
import org.junit.Test
import java.sql.SQLException
import java.util.*
@ -47,6 +59,12 @@ class FlowHospitalTest {
private val rpcUser = User("user1", "test", permissions = setOf(Permissions.all()))
@Before
fun before() {
SpendStateAndCatchDoubleSpendResponderFlow.exceptionSeenInUserFlow = false
CreateTransactionButDontFinalizeResponderFlow.exceptionSeenInUserFlow = false
}
@Test(timeout = 300_000)
fun `when double spend occurs, the flow is successfully deleted on the counterparty`() {
driver(DriverParameters(cordappsForAllNodes = listOf(enclosedCordapp(), findCordapp("net.corda.testing.contracts")))) {
@ -172,7 +190,7 @@ class FlowHospitalTest {
@Test(timeout = 300_000)
fun `HospitalizeFlowException cloaking an important exception thrown`() {
var dischargedCounter = 0
var observationCounter: Int = 0
var observationCounter = 0
StaffedFlowHospital.onFlowDischarged.add { _, _ ->
++dischargedCounter
}
@ -197,6 +215,84 @@ class FlowHospitalTest {
}
}
@Test(timeout = 300_000)
fun `catching a notary error will cause a peer to fail with unexpected session end during ReceiveFinalityFlow that passes through user code`() {
var dischargedCounter = 0
StaffedFlowHospital.onFlowErrorPropagated.add { _, _ ->
++dischargedCounter
}
val user = User("mark", "dadada", setOf(Permissions.all()))
driver(DriverParameters(isDebug = false, startNodesInProcess = true)) {
val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
val nodeBHandle = startNode(providedName = BOB_NAME, rpcUsers = listOf(user)).getOrThrow()
nodeAHandle.rpc.let {
val ref = it.startFlow(::CreateTransactionFlow, nodeBHandle.nodeInfo.singleIdentity()).returnValue.getOrThrow(20.seconds)
it.startFlow(::SpendStateAndCatchDoubleSpendFlow, nodeBHandle.nodeInfo.singleIdentity(), ref).returnValue.getOrThrow(20.seconds)
it.startFlow(::SpendStateAndCatchDoubleSpendFlow, nodeBHandle.nodeInfo.singleIdentity(), ref).returnValue.getOrThrow(20.seconds)
}
}
// 1 is the notary failing to notarise and propagating the error
// 2 is the receiving flow failing due to the unexpected session end error
assertEquals(2, dischargedCounter)
assertTrue(SpendStateAndCatchDoubleSpendResponderFlow.exceptionSeenInUserFlow)
}
@Test(timeout = 300_000)
fun `unexpected session end errors outside of ReceiveFinalityFlow are not handled`() {
var dischargedCounter = 0
var observationCounter = 0
StaffedFlowHospital.onFlowErrorPropagated.add { _, _ ->
++dischargedCounter
}
StaffedFlowHospital.onFlowKeptForOvernightObservation.add { _, _ ->
++observationCounter
}
val user = User("mark", "dadada", setOf(Permissions.all()))
driver(DriverParameters(isDebug = false, startNodesInProcess = true)) {
val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
val nodeBHandle = startNode(providedName = BOB_NAME, rpcUsers = listOf(user)).getOrThrow()
val nodeCHandle = startNode(providedName = CHARLIE_NAME, rpcUsers = listOf(user)).getOrThrow()
nodeAHandle.rpc.let {
val ref = it.startFlow(::CreateTransactionFlow, nodeBHandle.nodeInfo.singleIdentity()).returnValue.getOrThrow(20.seconds)
val ref2 = it.startFlow(::SpendStateAndCatchDoubleSpendFlow, nodeBHandle.nodeInfo.singleIdentity(), ref).returnValue.getOrThrow(20.seconds)
val ref3 = it.startFlow(::SpendStateAndCatchDoubleSpendFlow, nodeCHandle.nodeInfo.singleIdentity(), ref2).returnValue.getOrThrow(20.seconds)
it.startFlow(::CreateTransactionButDontFinalizeFlow, nodeBHandle.nodeInfo.singleIdentity(), ref3).returnValue.getOrThrow(20.seconds)
}
}
assertEquals(0, dischargedCounter)
assertEquals(1, observationCounter)
assertTrue(CreateTransactionButDontFinalizeResponderFlow.exceptionSeenInUserFlow)
}
@Test(timeout = 300_000)
fun `unexpected session end errors within ReceiveFinalityFlow can be caught and the flow can end gracefully`() {
var dischargedCounter = 0
var observationCounter = 0
StaffedFlowHospital.onFlowErrorPropagated.add { _, _ ->
++dischargedCounter
}
StaffedFlowHospital.onFlowKeptForOvernightObservation.add { _, _ ->
++observationCounter
}
val user = User("mark", "dadada", setOf(Permissions.all()))
driver(DriverParameters(isDebug = false, startNodesInProcess = true)) {
val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
val nodeBHandle = startNode(providedName = BOB_NAME, rpcUsers = listOf(user)).getOrThrow()
nodeAHandle.rpc.let {
val ref = it.startFlow(::CreateTransactionFlow, nodeBHandle.nodeInfo.singleIdentity()).returnValue.getOrThrow(20.seconds)
it.startFlow(::SpendStateAndCatchDoubleSpendFlow, nodeBHandle.nodeInfo.singleIdentity(), ref).returnValue.getOrThrow(20.seconds)
it.startFlow(::SpendStateAndCatchDoubleSpendFlow, nodeBHandle.nodeInfo.singleIdentity(), ref, true).returnValue.getOrThrow(20.seconds)
}
}
// 1 is the notary failing to notarise and propagating the error
assertEquals(1, dischargedCounter)
assertEquals(0, observationCounter)
assertTrue(SpendStateAndCatchDoubleSpendResponderFlow.exceptionSeenInUserFlow)
}
@StartableByRPC
class IssueFlow(val notary: Party) : FlowLogic<StateAndRef<SingleOwnerState>>() {
@ -296,4 +392,136 @@ class FlowHospitalTest {
setCause(SQLException("deadlock"))
}
}
@InitiatingFlow
@StartableByRPC
class CreateTransactionFlow(private val peer: Party) : FlowLogic<StateAndRef<DummyState>>() {
@Suspendable
override fun call(): StateAndRef<DummyState> {
val tx = TransactionBuilder(serviceHub.networkMapCache.notaryIdentities.first()).apply {
addOutputState(DummyState(participants = listOf(ourIdentity)))
addCommand(DummyContract.Commands.Create(), listOf(ourIdentity.owningKey, peer.owningKey))
}
val stx = serviceHub.signInitialTransaction(tx)
val session = initiateFlow(peer)
val ftx = subFlow(CollectSignaturesFlow(stx, listOf(session)))
subFlow(FinalityFlow(ftx, session))
return ftx.coreTransaction.outRef(0)
}
}
@InitiatedBy(CreateTransactionFlow::class)
class CreateTransactionResponderFlow(private val session: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
logger.info("CREATE TX - WAITING TO SIGN TX")
val stx = subFlow(object : SignTransactionFlow(session) {
override fun checkTransaction(stx: SignedTransaction) {
}
})
logger.info("CREATE TX - SIGNED TO SIGN TX")
subFlow(ReceiveFinalityFlow(session, stx.id))
logger.info("CREATE TX - RECEIVED TX")
}
}
@InitiatingFlow
@StartableByRPC
class SpendStateAndCatchDoubleSpendFlow(
private val peer: Party,
private val ref: StateAndRef<DummyState>,
private val consumePeerError: Boolean
) : FlowLogic<StateAndRef<DummyState>>() {
constructor(peer: Party, ref: StateAndRef<DummyState>): this(peer, ref, false)
@Suspendable
override fun call(): StateAndRef<DummyState> {
val tx = TransactionBuilder(serviceHub.networkMapCache.notaryIdentities.first()).apply {
addInputState(ref)
addOutputState(DummyState(participants = listOf(ourIdentity)))
addCommand(DummyContract.Commands.Move(), listOf(ourIdentity.owningKey, peer.owningKey))
}
val stx = serviceHub.signInitialTransaction(tx)
val session = initiateFlow(peer)
session.send(consumePeerError)
val ftx = subFlow(CollectSignaturesFlow(stx, listOf(session)))
try {
subFlow(FinalityFlow(ftx, session))
} catch(e: NotaryException) {
logger.info("Caught notary exception")
}
return ftx.coreTransaction.outRef(0)
}
}
@InitiatedBy(SpendStateAndCatchDoubleSpendFlow::class)
class SpendStateAndCatchDoubleSpendResponderFlow(private val session: FlowSession) : FlowLogic<Unit>() {
companion object {
var exceptionSeenInUserFlow = false
}
@Suspendable
override fun call() {
val consumeError = session.receive<Boolean>().unwrap { it }
val stx = subFlow(object : SignTransactionFlow(session) {
override fun checkTransaction(stx: SignedTransaction) {
}
})
try {
subFlow(ReceiveFinalityFlow(session, stx.id))
} catch (e: UnexpectedFlowEndException) {
exceptionSeenInUserFlow = true
if (!consumeError) {
throw e
}
}
}
}
@InitiatingFlow
@StartableByRPC
class CreateTransactionButDontFinalizeFlow(private val peer: Party, private val ref: StateAndRef<DummyState>) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val tx = TransactionBuilder(serviceHub.networkMapCache.notaryIdentities.first()).apply {
addInputState(ref)
addOutputState(DummyState(participants = listOf(ourIdentity)))
addCommand(DummyContract.Commands.Move(), listOf(ourIdentity.owningKey))
}
val stx = serviceHub.signInitialTransaction(tx)
val session = initiateFlow(peer)
// Send the transaction id to the peer instead of the transaction.
// This allows transaction dependency resolution to occur within the peer's [ReceiveTransactionFlow].
session.send(stx.id)
// Mimic notarisation from [FinalityFlow] so that failing inside [ResolveTransactionsFlow] can be achieved.
val notarySignatures = subFlow(NotaryFlow.Client(stx, skipVerification = true))
val notarisedTx = stx + notarySignatures
session.send(notarisedTx)
}
}
@InitiatedBy(CreateTransactionButDontFinalizeFlow::class)
class CreateTransactionButDontFinalizeResponderFlow(private val session: FlowSession) : FlowLogic<Unit>() {
companion object {
var exceptionSeenInUserFlow = false
}
@Suspendable
override fun call() {
val id = session.receive<SecureHash>().unwrap { it }
try {
subFlow(ReceiveFinalityFlow(session, id))
} catch (e: UnexpectedFlowEndException) {
exceptionSeenInUserFlow = true
throw e
}
}
}
}

View File

@ -28,6 +28,7 @@ import net.corda.testing.node.internal.NodeBasedTest
import net.corda.testing.node.internal.startFlow
import org.apache.activemq.artemis.api.core.ActiveMQNonExistentQueueException
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
import org.apache.activemq.artemis.api.core.QueueConfiguration
import org.apache.activemq.artemis.api.core.RoutingType
import org.apache.activemq.artemis.api.core.SimpleString
import org.assertj.core.api.Assertions.assertThatExceptionOfType
@ -130,7 +131,11 @@ abstract class MQSecurityTest : NodeBasedTest() {
fun assertTempQueueCreationAttackFails(queue: String) {
assertAttackFails(queue, "CREATE_NON_DURABLE_QUEUE") {
attacker.session.createTemporaryQueue(queue, RoutingType.MULTICAST, queue)
attacker.session.createQueue(QueueConfiguration(queue)
.setRoutingType(RoutingType.MULTICAST)
.setAddress(queue)
.setTemporary(true)
.setDurable(false))
}
// Double-check
assertThatExceptionOfType(ActiveMQNonExistentQueueException::class.java).isThrownBy {
@ -147,7 +152,8 @@ abstract class MQSecurityTest : NodeBasedTest() {
fun assertNonTempQueueCreationAttackFails(queue: String, durable: Boolean) {
val permission = if (durable) "CREATE_DURABLE_QUEUE" else "CREATE_NON_DURABLE_QUEUE"
assertAttackFails(queue, permission) {
attacker.session.createQueue(queue, RoutingType.MULTICAST, queue, durable)
attacker.session.createQueue(
QueueConfiguration(queue).setAddress(queue).setRoutingType(RoutingType.MULTICAST).setDurable(durable))
}
// Double-check
assertThatExceptionOfType(ActiveMQNonExistentQueueException::class.java).isThrownBy {

View File

@ -39,13 +39,13 @@ import net.corda.core.internal.concurrent.map
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.div
import net.corda.core.internal.messaging.AttachmentTrustInfoRPCOps
import net.corda.core.internal.messaging.FlowManagerRPCOps
import net.corda.core.internal.notary.NotaryService
import net.corda.core.internal.rootMessage
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.ClientRpcSslOptions
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.RPCOps
import net.corda.core.messaging.flows.FlowManagerRPCOps
import net.corda.core.node.AppServiceHub
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
@ -84,6 +84,7 @@ import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.node.internal.cordapp.VirtualCordapp
import net.corda.node.internal.rpc.proxies.AuthenticatedRpcOpsProxy
import net.corda.node.internal.rpc.proxies.ThreadContextAdjustingRpcOpsProxy
import net.corda.node.internal.shell.InteractiveShell
import net.corda.node.services.ContractUpgradeHandler
import net.corda.node.services.FinalityHandler
import net.corda.node.services.NotaryChangeHandler
@ -100,8 +101,6 @@ import net.corda.node.services.api.WritableTransactionStorage
import net.corda.node.services.attachments.NodeAttachmentTrustCalculator
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.rpc.NodeRpcOptions
import net.corda.node.services.config.shell.determineUnsafeUsers
import net.corda.node.services.config.shell.toShellConfig
import net.corda.node.services.config.shouldInitCrashShell
import net.corda.node.services.diagnostics.NodeDiagnosticsService
import net.corda.node.services.events.NodeSchedulerService
@ -168,7 +167,6 @@ import net.corda.nodeapi.internal.persistence.SchemaMigration
import net.corda.nodeapi.internal.persistence.contextDatabase
import net.corda.nodeapi.internal.persistence.withoutDatabaseAccess
import net.corda.nodeapi.internal.namedThreadPoolExecutor
import net.corda.tools.shell.InteractiveShell
import org.apache.activemq.artemis.utils.ReusableLatch
import org.jolokia.jvmagent.JolokiaServer
import org.jolokia.jvmagent.JolokiaServerConfig
@ -180,6 +178,7 @@ import java.sql.Savepoint
import java.time.Clock
import java.time.Duration
import java.time.format.DateTimeParseException
import java.util.ArrayList
import java.util.Properties
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
@ -401,23 +400,21 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
/** The implementation of the [RPCOps] interfaces used by this node. */
@Suppress("DEPRECATION")
open fun makeRPCOps(cordappLoader: CordappLoader): List<RPCOps> {
val cordaRPCOpsImpl = Pair(CordaRPCOps::class.java, CordaRPCOpsImpl(
services,
smm,
flowStarter
) {
shutdownExecutor.submit(::stop)
}.also { it.closeOnStop() })
val cordaRPCOps = CordaRPCOpsImpl(services, smm, flowStarter) { shutdownExecutor.submit(::stop) }
cordaRPCOps.closeOnStop()
val flowManagerRPCOps = FlowManagerRPCOpsImpl(checkpointDumper)
val attachmentTrustInfoRPCOps = AttachmentTrustInfoRPCOpsImpl(services.attachmentTrustCalculator)
val checkpointRPCOpsImpl = Pair(FlowManagerRPCOps::class.java, FlowManagerRPCOpsImpl(checkpointDumper))
val attachmentTrustInfoRPCOps = Pair(AttachmentTrustInfoRPCOps::class.java, AttachmentTrustInfoRPCOpsImpl(services.attachmentTrustCalculator))
return listOf(cordaRPCOpsImpl, checkpointRPCOpsImpl, attachmentTrustInfoRPCOps).map { rpcOpsImplPair ->
return listOf(
CordaRPCOps::class.java to cordaRPCOps,
FlowManagerRPCOps::class.java to flowManagerRPCOps,
net.corda.core.internal.messaging.FlowManagerRPCOps::class.java to flowManagerRPCOps,
AttachmentTrustInfoRPCOps::class.java to attachmentTrustInfoRPCOps
).map { (targetInterface, implementation) ->
// Mind that order of proxies is important
val targetInterface = rpcOpsImplPair.first
val stage1Proxy = AuthenticatedRpcOpsProxy.proxy(rpcOpsImplPair.second, targetInterface)
val stage1Proxy = AuthenticatedRpcOpsProxy.proxy(implementation, targetInterface)
val stage2Proxy = ThreadContextAdjustingRpcOpsProxy.proxy(stage1Proxy, targetInterface, cordappLoader.appClassLoader)
stage2Proxy
@ -448,7 +445,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
}
fun clearNetworkMapCache() {
open fun clearNetworkMapCache() {
Node.printBasicNodeInfo("Clearing network map cache entries")
log.info("Starting clearing of network map cache entries...")
startDatabase()
@ -680,16 +677,18 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
open fun startShell() {
if (configuration.shouldInitCrashShell()) {
val shellConfiguration = configuration.toShellConfig()
shellConfiguration.sshdPort?.let {
log.info("Binding Shell SSHD server on port $it.")
val isShellStarted = InteractiveShell.startShellIfInstalled(configuration, cordappLoader)
configuration.sshd?.port?.let {
if (isShellStarted) {
Node.printBasicNodeInfo("SSH server listening on port", configuration.sshd!!.port.toString())
log.info("SSH server listening on port: $it.")
} else {
Node.printBasicNodeInfo(
"SSH server not started. SSH port is defined but the corda-shell is not installed in node's drivers directory"
)
log.info("SSH server not started. SSH port is defined but the corda-shell is not installed in node's drivers directory")
}
}
val unsafeUsers = determineUnsafeUsers(configuration)
org.crsh.ssh.term.CRaSHCommand.setUserInfo(unsafeUsers, true, false)
log.info("Setting unsafe users as: ${unsafeUsers}")
InteractiveShell.startShell(shellConfiguration, cordappLoader.appClassLoader)
}
}

View File

@ -566,6 +566,11 @@ open class Node(configuration: NodeConfiguration,
return super.generateAndSaveNodeInfo()
}
override fun clearNetworkMapCache() {
initialiseSerialization()
super.clearNetworkMapCache()
}
override fun runDatabaseMigrationScripts(
updateCoreSchemas: Boolean,
updateAppSchemas: Boolean,

View File

@ -8,38 +8,56 @@ import net.corda.cliutils.printError
import net.corda.common.logging.CordaVersion
import net.corda.common.logging.errorReporting.CordaErrorContextProvider
import net.corda.common.logging.errorReporting.ErrorCode
import net.corda.common.logging.errorReporting.ErrorReporting
import net.corda.common.logging.errorReporting.report
import net.corda.core.contracts.HashAttachmentConstraint
import net.corda.core.crypto.Crypto
import net.corda.core.internal.*
import net.corda.core.internal.Emoji
import net.corda.core.internal.HashAgility
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.concurrent.thenMatch
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.internal.errors.AddressBindingException
import net.corda.core.internal.exists
import net.corda.core.internal.isDirectory
import net.corda.core.internal.location
import net.corda.core.internal.randomOrNull
import net.corda.core.internal.safeSymbolicRead
import net.corda.core.utilities.Try
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.loggerFor
import net.corda.node.*
import net.corda.common.logging.errorReporting.ErrorReporting
import net.corda.common.logging.errorReporting.report
import net.corda.node.NodeCmdLineOptions
import net.corda.node.SerialFilter
import net.corda.node.SharedNodeCmdLineOptions
import net.corda.node.VersionInfo
import net.corda.node.defaultSerialFilter
import net.corda.node.internal.Node.Companion.isInvalidJavaVersion
import net.corda.node.internal.cordapp.MultipleCordappsForFlowException
import net.corda.node.internal.subcommands.*
import net.corda.node.internal.shell.InteractiveShell
import net.corda.node.internal.subcommands.ClearNetworkCacheCli
import net.corda.node.internal.subcommands.GenerateNodeInfoCli
import net.corda.node.internal.subcommands.GenerateRpcSslCertsCli
import net.corda.node.internal.subcommands.InitialRegistration
import net.corda.node.internal.subcommands.InitialRegistrationCli
import net.corda.node.internal.subcommands.RunMigrationScriptsCli
import net.corda.node.internal.subcommands.SynchroniseSchemasCli
import net.corda.node.internal.subcommands.ValidateConfigurationCli
import net.corda.node.internal.subcommands.ValidateConfigurationCli.Companion.logConfigurationErrors
import net.corda.node.internal.subcommands.ValidateConfigurationCli.Companion.logRawConfig
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.shouldStartLocalShell
import net.corda.node.services.config.shouldStartSSHDaemon
import net.corda.node.utilities.registration.NodeRegistrationException
import net.corda.nodeapi.internal.JVMAgentUtilities
import net.corda.nodeapi.internal.addShutdownHook
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
import net.corda.nodeapi.internal.persistence.DatabaseIncompatibleException
import net.corda.tools.shell.InteractiveShell
import org.fusesource.jansi.Ansi
import org.slf4j.bridge.SLF4JBridgeHandler
import picocli.CommandLine.Mixin
import java.io.IOException
import java.io.RandomAccessFile
import java.lang.NullPointerException
import java.lang.management.ManagementFactory
import java.net.InetAddress
import java.nio.channels.UnresolvedAddressException
@ -236,29 +254,21 @@ open class NodeStartup : NodeStartupLogging {
val loadedCodapps = node.services.cordappProvider.cordapps.filter { it.isLoaded }
logLoadedCorDapps(loadedCodapps)
node.nodeReadyFuture.thenMatch({
// Elapsed time in seconds. We used 10 / 100.0 and not directly / 1000.0 to only keep two decimal digits.
val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0
val name = nodeInfo.legalIdentitiesAndCerts.first().name.organisation
Node.printBasicNodeInfo("Node for \"$name\" started up and registered in $elapsed sec")
node.nodeReadyFuture.thenMatch(
{
// Elapsed time in seconds. We used 10 / 100.0 and not directly / 1000.0 to only keep two decimal digits.
val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0
val name = nodeInfo.legalIdentitiesAndCerts.first().name.organisation
Node.printBasicNodeInfo("Node for \"$name\" started up and registered in $elapsed sec")
// Don't start the shell if there's no console attached.
if (node.configuration.shouldStartLocalShell()) {
node.startupComplete.then {
try {
InteractiveShell.runLocalShell(node::stop)
} catch (e: Exception) {
logger.error("Shell failed to start", e)
}
// Don't start the shell if there's no console attached.
if (node.configuration.shouldStartLocalShell()) {
InteractiveShell.runLocalShellIfInstalled(node::stop)
}
}
if (node.configuration.shouldStartSSHDaemon()) {
Node.printBasicNodeInfo("SSH server listening on port", node.configuration.sshd!!.port.toString())
}
},
{ th ->
logger.error("Unexpected exception during registration", th)
})
},
{ th ->
logger.error("Unexpected exception during registration", th)
})
node.run()
}

View File

@ -1,6 +1,5 @@
package net.corda.node.internal.artemis
import io.netty.channel.unix.Errors
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.internal.LifecycleSupport
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
@ -18,4 +17,11 @@ data class BrokerAddresses(val primary: NetworkHostAndPort, private val adminArg
val admin = adminArg ?: primary
}
fun java.io.IOException.isBindingError() = this is BindException || this is Errors.NativeIoException && message?.contains("Address already in use") == true
fun Throwable.isBindingError(): Boolean {
val addressAlreadyUsedMsg = "Address already in use"
// This is not an exact science here.
// Depending on the underlying OS it can be either [Errors.NativeIoException] on Linux or [BindException] on Windows
// and of course this is dependent on the version of Artemis library used.
return this is BindException ||
this is IllegalStateException && cause.let { it is BindException || it?.message?.contains(addressAlreadyUsedMsg) == true }
}

View File

@ -14,6 +14,7 @@ import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal
import java.io.IOException
import java.security.KeyStore
import java.security.Principal
import java.security.cert.X509Certificate
import java.util.*
import javax.security.auth.Subject
import javax.security.auth.callback.CallbackHandler
@ -119,19 +120,18 @@ class BrokerJaasLoginModule : BaseBrokerJaasLoginModule() {
// The Main authentication logic, responsible for running all the configured checks for each user type
// and return the actual User and principals
@Suppress("DEPRECATION") // should use java.security.cert.X509Certificate
private fun authenticateAndAuthorise(username: String, certificates: Array<javax.security.cert.X509Certificate>?, password: String): Pair<String, List<RolePrincipal>> {
fun requireTls(certificates: Array<javax.security.cert.X509Certificate>?) = requireNotNull(certificates) { "No client certificates presented." }
private fun authenticateAndAuthorise(username: String, certificates: Array<X509Certificate>, password: String): Pair<String, List<RolePrincipal>> {
fun requireTls(certificates: Array<X509Certificate>?) = requireNotNull(certificates) { "No client certificates presented." }
return when (username) {
ArtemisMessagingComponent.NODE_P2P_USER -> {
requireTls(certificates)
CertificateChainCheckPolicy.LeafMustMatch.createCheck(nodeJaasConfig.keyStore, nodeJaasConfig.trustStore).checkCertificateChain(certificates!!)
CertificateChainCheckPolicy.LeafMustMatch.createCheck(nodeJaasConfig.keyStore, nodeJaasConfig.trustStore).checkCertificateChain(certificates)
Pair(certificates.first().subjectDN.name, listOf(RolePrincipal(NODE_P2P_ROLE)))
}
ArtemisMessagingComponent.NODE_RPC_USER -> {
requireTls(certificates)
CertificateChainCheckPolicy.LeafMustMatch.createCheck(nodeJaasConfig.keyStore, nodeJaasConfig.trustStore).checkCertificateChain(certificates!!)
CertificateChainCheckPolicy.LeafMustMatch.createCheck(nodeJaasConfig.keyStore, nodeJaasConfig.trustStore).checkCertificateChain(certificates)
Pair(ArtemisMessagingComponent.NODE_RPC_USER, listOf(RolePrincipal(NODE_RPC_ROLE)))
}
ArtemisMessagingComponent.PEER_USER -> {
@ -140,7 +140,7 @@ class BrokerJaasLoginModule : BaseBrokerJaasLoginModule() {
// This check is redundant as it was performed already during the SSL handshake
CertificateChainCheckPolicy.RootMustMatch
.createCheck(p2pJaasConfig.keyStore, p2pJaasConfig.trustStore)
.checkCertificateChain(certificates!!)
.checkCertificateChain(certificates)
Pair(certificates.first().subjectDN.name, listOf(RolePrincipal(PEER_ROLE)))
}
else -> {
@ -176,8 +176,8 @@ abstract class BaseBrokerJaasLoginModule : LoginModule {
protected lateinit var callbackHandler: CallbackHandler
protected val principals = ArrayList<Principal>()
@Suppress("DEPRECATION") // should use java.security.cert.X509Certificate
protected fun getUsernamePasswordAndCerts(): Triple<String, String, Array<javax.security.cert.X509Certificate>?> {
@Suppress("ThrowsCount")
protected fun getUsernamePasswordAndCerts(): Triple<String, String, Array<X509Certificate>> {
val nameCallback = NameCallback("Username: ")
val passwordCallback = PasswordCallback("Password: ", false)
val certificateCallback = CertificateCallback()

View File

@ -13,7 +13,7 @@ sealed class CertificateChainCheckPolicy {
@FunctionalInterface
interface Check {
fun checkCertificateChain(theirChain: Array<javax.security.cert.X509Certificate>)
fun checkCertificateChain(theirChain: Array<java.security.cert.X509Certificate>)
}
abstract fun createCheck(keyStore: KeyStore, trustStore: KeyStore): Check
@ -21,7 +21,7 @@ sealed class CertificateChainCheckPolicy {
object Any : CertificateChainCheckPolicy() {
override fun createCheck(keyStore: KeyStore, trustStore: KeyStore): Check {
return object : Check {
override fun checkCertificateChain(theirChain: Array<javax.security.cert.X509Certificate>) {
override fun checkCertificateChain(theirChain: Array<java.security.cert.X509Certificate>) {
// nothing to do here
}
}
@ -33,7 +33,7 @@ sealed class CertificateChainCheckPolicy {
val rootAliases = trustStore.aliases().asSequence().filter { it.startsWith(X509Utilities.CORDA_ROOT_CA) }
val rootPublicKeys = rootAliases.map { trustStore.getCertificate(it).publicKey }.toSet()
return object : Check {
override fun checkCertificateChain(theirChain: Array<javax.security.cert.X509Certificate>) {
override fun checkCertificateChain(theirChain: Array<java.security.cert.X509Certificate>) {
val theirRoot = theirChain.last().publicKey
if (theirRoot !in rootPublicKeys) {
throw CertificateException("Root certificate mismatch, their root = $theirRoot")
@ -47,7 +47,7 @@ sealed class CertificateChainCheckPolicy {
override fun createCheck(keyStore: KeyStore, trustStore: KeyStore): Check {
val ourPublicKey = keyStore.getCertificate(X509Utilities.CORDA_CLIENT_TLS).publicKey
return object : Check {
override fun checkCertificateChain(theirChain: Array<javax.security.cert.X509Certificate>) {
override fun checkCertificateChain(theirChain: Array<java.security.cert.X509Certificate>) {
val theirLeaf = theirChain.first().publicKey
if (ourPublicKey != theirLeaf) {
throw CertificateException("Leaf certificate mismatch, their leaf = $theirLeaf")
@ -61,7 +61,7 @@ sealed class CertificateChainCheckPolicy {
override fun createCheck(keyStore: KeyStore, trustStore: KeyStore): Check {
val trustedPublicKeys = trustedAliases.map { trustStore.getCertificate(it).publicKey }.toSet()
return object : Check {
override fun checkCertificateChain(theirChain: Array<javax.security.cert.X509Certificate>) {
override fun checkCertificateChain(theirChain: Array<java.security.cert.X509Certificate>) {
if (!theirChain.any { it.publicKey in trustedPublicKeys }) {
throw CertificateException("Their certificate chain contained none of the trusted ones")
}
@ -78,7 +78,7 @@ sealed class CertificateChainCheckPolicy {
class UsernameMustMatchCommonNameCheck : Check {
lateinit var username: String
override fun checkCertificateChain(theirChain: Array<javax.security.cert.X509Certificate>) {
override fun checkCertificateChain(theirChain: Array<java.security.cert.X509Certificate>) {
if (!theirChain.any { certificate -> CordaX500Name.parse(certificate.subjectDN.name).commonName == username }) {
throw CertificateException("Client certificate does not match login username.")
}

View File

@ -1,13 +1,15 @@
@file:Suppress("DEPRECATION")
package net.corda.node.internal.checkpoints
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.messaging.FlowManagerRPCOps
import net.corda.core.messaging.flows.FlowManagerRPCOps
import net.corda.node.services.rpc.CheckpointDumperImpl
import net.corda.core.internal.messaging.FlowManagerRPCOps as InternalFlowManagerRPCOps
/**
* Implementation of [FlowManagerRPCOps]
*/
internal class FlowManagerRPCOpsImpl(private val checkpointDumper: CheckpointDumperImpl) : FlowManagerRPCOps {
internal class FlowManagerRPCOpsImpl(private val checkpointDumper: CheckpointDumperImpl) : FlowManagerRPCOps, InternalFlowManagerRPCOps {
override val protocolVersion: Int = PLATFORM_VERSION

View File

@ -0,0 +1,87 @@
package net.corda.node.internal.shell
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.shell.determineUnsafeUsers
import net.corda.node.services.config.shell.toShellConfigMap
import net.corda.nodeapi.internal.cordapp.CordappLoader
import org.slf4j.LoggerFactory
object InteractiveShell {
private val log = LoggerFactory.getLogger(InteractiveShell::class.java)
private const val INTERACTIVE_SHELL_CLASS = "net.corda.tools.shell.InteractiveShell"
private const val CRASH_COMMAND_CLASS = "org.crsh.ssh.term.CRaSHCommand"
private const val START_SHELL_METHOD = "startShell"
private const val RUN_LOCAL_SHELL_METHOD = "runLocalShell"
private const val SET_USER_INFO_METHOD = "setUserInfo"
fun startShellIfInstalled(configuration: NodeConfiguration, cordappLoader: CordappLoader): Boolean {
return if (isShellInstalled()) {
try {
val shellConfiguration = configuration.toShellConfigMap()
setUnsafeUsers(configuration)
startShell(shellConfiguration, cordappLoader)
true
} catch (e: Exception) {
log.error("Shell failed to start", e)
false
}
} else {
false
}
}
/**
* Only call this after [startShellIfInstalled] has been called or the required classes will not be loaded into the current classloader.
*/
fun runLocalShellIfInstalled(onExit: () -> Unit = {}): Boolean {
return if (isShellInstalled()) {
try {
runLocalShell(onExit)
true
} catch (e: Exception) {
log.error("Shell failed to start", e)
false
}
} else {
false
}
}
private fun isShellInstalled(): Boolean {
return try {
javaClass.classLoader.loadClass(INTERACTIVE_SHELL_CLASS)
true
} catch (e: ClassNotFoundException) {
false
}
}
private fun setUnsafeUsers(configuration: NodeConfiguration) {
val unsafeUsers = determineUnsafeUsers(configuration)
val clazz = javaClass.classLoader.loadClass(CRASH_COMMAND_CLASS)
clazz.getDeclaredMethod(SET_USER_INFO_METHOD, Set::class.java, Boolean::class.java, Boolean::class.java)
.invoke(null, unsafeUsers, true, false)
log.info("Setting unsafe users as: $unsafeUsers")
}
private fun startShell(shellConfiguration: Map<String, Any?>, cordappLoader: CordappLoader) {
val clazz = javaClass.classLoader.loadClass(INTERACTIVE_SHELL_CLASS)
val instance = clazz.getDeclaredConstructor()
.apply { this.isAccessible = true }
.newInstance()
clazz.getDeclaredMethod(START_SHELL_METHOD, Map::class.java, ClassLoader::class.java, Boolean::class.java)
.invoke(instance, shellConfiguration, cordappLoader.appClassLoader, false)
}
private fun runLocalShell(onExit: () -> Unit = {}) {
val clazz = javaClass.classLoader.loadClass(INTERACTIVE_SHELL_CLASS)
// Gets the existing instance created by [startShell] as [InteractiveShell] is a static instance
val instance = clazz.getDeclaredConstructor()
.apply { this.isAccessible = true }
.newInstance()
clazz.getDeclaredMethod(RUN_LOCAL_SHELL_METHOD, Function0::class.java).invoke(instance, onExit)
}
}

View File

@ -13,6 +13,7 @@ import net.corda.node.internal.DBNetworkParametersStorage
import net.corda.node.internal.schemas.NodeInfoSchemaV1
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.node.services.vault.NodeVaultService
@ -132,7 +133,8 @@ object VaultMigrationSchemaV1 : MappedSchema(schemaFamily = VaultMigrationSchema
PersistentIdentityService.PersistentPublicKeyHashToParty::class.java,
BasicHSMKeyManagementService.PersistentKey::class.java,
NodeAttachmentService.DBAttachment::class.java,
DBNetworkParametersStorage.PersistentNetworkParameters::class.java
DBNetworkParametersStorage.PersistentNetworkParameters::class.java,
PersistentNetworkMapCache.PersistentPartyToPublicKeyHash::class.java
)
)

View File

@ -15,4 +15,6 @@ interface IdentityServiceInternal : IdentityService {
fun verifyAndRegisterNewRandomIdentity(identity: PartyAndCertificate)
fun invalidateCaches(name: CordaX500Name) {}
fun archiveNamedIdentity(name:String, publicKeyHash: String?) {}
}

View File

@ -11,17 +11,18 @@ import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.config.rpc.NodeRpcOptions
import net.corda.node.services.config.schema.v1.V1NodeConfigurationSpec
import net.corda.node.services.config.shell.SSHDConfiguration
import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.notary.experimental.bftsmart.BFTSmartConfig
import net.corda.notary.experimental.raft.RaftConfig
import net.corda.tools.shell.SSHDConfiguration
import java.net.URL
import java.nio.file.Path
import java.time.Duration
import java.util.*
import java.util.Properties
import java.util.UUID
import javax.security.auth.x500.X500Principal
val Int.MB: Long get() = this * 1024L * 1024L

View File

@ -8,6 +8,7 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.seconds
import net.corda.node.services.config.rpc.NodeRpcOptions
import net.corda.node.services.config.shell.SSHDConfiguration
import net.corda.nodeapi.BrokerRpcSslOptions
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier
@ -15,11 +16,11 @@ import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.config.SslConfiguration
import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.tools.shell.SSHDConfiguration
import java.net.URL
import java.nio.file.Path
import java.time.Duration
import java.util.*
import java.util.Properties
import java.util.UUID
import javax.security.auth.x500.X500Principal
data class NodeConfigurationImpl(

View File

@ -40,13 +40,14 @@ import net.corda.node.services.config.schema.parsers.toProperties
import net.corda.node.services.config.schema.parsers.toURL
import net.corda.node.services.config.schema.parsers.toUUID
import net.corda.node.services.config.schema.parsers.validValue
import net.corda.node.services.config.shell.SSHDConfiguration
import net.corda.nodeapi.BrokerRpcSslOptions
import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel
import net.corda.notary.experimental.bftsmart.BFTSmartConfig
import net.corda.notary.experimental.raft.RaftConfig
import net.corda.tools.shell.SSHDConfiguration
import java.util.Properties
internal object UserSpec : Configuration.Specification<User>("User") {
private val username by string().optional()
@ -67,9 +68,32 @@ internal object UserSpec : Configuration.Specification<User>("User") {
internal object SecurityConfigurationSpec : Configuration.Specification<SecurityConfiguration>("SecurityConfiguration") {
private object AuthServiceSpec : Configuration.Specification<SecurityConfiguration.AuthService>("AuthService") {
private object DataSourceSpec : Configuration.Specification<SecurityConfiguration.AuthService.DataSource>("DataSource") {
fun Properties.enablePasswordMasking(): Properties {
class PwMasking : Properties() {
fun maskPassword(): Properties {
if (!containsKey("password")) return this
val propsNoPassword = Properties()
// if the properties are passed in to the constructor as defaults
// they don't get printed so adding all keys explicitly
propsNoPassword.putAll(this)
propsNoPassword.setProperty("password", "***")
return propsNoPassword
}
override fun toString(): String {
val props = maskPassword()
return props.toString()
}
}
val masker = PwMasking()
masker.putAll(this)
return masker
}
private val type by enum(AuthDataSourceType::class)
private val passwordEncryption by enum(PasswordEncryption::class).optional().withDefaultValue(SecurityConfiguration.AuthService.DataSource.Defaults.passwordEncryption)
private val connection by nestedObject(sensitive = true).map(::toProperties).optional()
private val connection by nestedObject(sensitive = true).map{ toProperties(it).enablePasswordMasking() }.optional()
private val users by nested(UserSpec).list().optional()
override fun parseValid(configuration: Config, options: Configuration.Options): Valid<SecurityConfiguration.AuthService.DataSource> {

View File

@ -1,4 +1,4 @@
package net.corda.tools.shell
package net.corda.node.services.config.shell
data class SSHDConfiguration(val port: Int) {
companion object {
@ -11,7 +11,7 @@ data class SSHDConfiguration(val port: Int) {
*/
@JvmStatic
fun parse(str: String): SSHDConfiguration {
require(!str.isBlank()) { SSHDConfiguration.MISSING_PORT_FORMAT.format(str) }
require(str.isNotBlank()) { MISSING_PORT_FORMAT.format(str) }
val port = try {
str.toInt()
} catch (ex: NumberFormatException) {

View File

@ -3,22 +3,23 @@ package net.corda.node.services.config.shell
import net.corda.core.internal.div
import net.corda.node.internal.clientSslOptionsCompatibleWith
import net.corda.node.services.config.NodeConfiguration
import net.corda.tools.shell.ShellConfiguration
import net.corda.tools.shell.ShellConfiguration.Companion.COMMANDS_DIR
import net.corda.tools.shell.ShellConfiguration.Companion.CORDAPPS_DIR
import net.corda.tools.shell.ShellConfiguration.Companion.SSHD_HOSTKEY_DIR
private const val COMMANDS_DIR = "shell-commands"
private const val CORDAPPS_DIR = "cordapps"
private const val SSHD_HOSTKEY_DIR = "ssh"
//re-packs data to Shell specific classes
fun NodeConfiguration.toShellConfig() = ShellConfiguration(
commandsDirectory = this.baseDirectory / COMMANDS_DIR,
cordappsDirectory = this.baseDirectory.toString() / CORDAPPS_DIR,
user = INTERNAL_SHELL_USER,
password = internalShellPassword,
permissions = internalShellPermissions(!this.localShellUnsafe),
localShellAllowExitInSafeMode = this.localShellAllowExitInSafeMode,
localShellUnsafe = this.localShellUnsafe,
hostAndPort = this.rpcOptions.address,
ssl = clientSslOptionsCompatibleWith(this.rpcOptions),
sshdPort = this.sshd?.port,
sshHostKeyDirectory = this.baseDirectory / SSHD_HOSTKEY_DIR,
noLocalShell = this.noLocalShell)
fun NodeConfiguration.toShellConfigMap() = mapOf(
"commandsDirectory" to this.baseDirectory / COMMANDS_DIR,
"cordappsDirectory" to this.baseDirectory.toString() / CORDAPPS_DIR,
"user" to INTERNAL_SHELL_USER,
"password" to internalShellPassword,
"permissions" to internalShellPermissions(!this.localShellUnsafe),
"localShellAllowExitInSafeMode" to this.localShellAllowExitInSafeMode,
"localShellUnsafe" to this.localShellUnsafe,
"hostAndPort" to this.rpcOptions.address,
"ssl" to clientSslOptionsCompatibleWith(this.rpcOptions),
"sshdPort" to this.sshd?.port,
"sshHostKeyDirectory" to this.baseDirectory / SSHD_HOSTKEY_DIR,
"noLocalShell" to this.noLocalShell
)

View File

@ -1,5 +1,6 @@
package net.corda.node.services.identity
import com.google.common.util.concurrent.ThreadFactoryBuilder
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.AbstractParty
@ -22,6 +23,7 @@ import net.corda.node.internal.schemas.NodeInfoSchemaV1
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.node.services.network.NotaryUpdateListener
import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.persistence.PublicKeyHashToExternalId
import net.corda.node.services.persistence.WritablePublicKeyToOwningIdentityCache
import net.corda.node.utilities.AppendOnlyPersistentMap
@ -46,6 +48,8 @@ import java.security.cert.CollectionCertStoreParameters
import java.security.cert.TrustAnchor
import java.security.cert.X509Certificate
import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.stream.Stream
import javax.annotation.concurrent.ThreadSafe
import javax.persistence.Column
@ -140,6 +144,8 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
private fun mapToKey(party: PartyAndCertificate) = party.owningKey.toStringShort()
}
val archiveIdentityExecutor: ExecutorService = Executors.newCachedThreadPool(ThreadFactoryBuilder().setNameFormat("archive-named-identity-thread-%d").build())
@Entity
@javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}identities")
class PersistentPublicKeyHashToCertificate(
@ -312,7 +318,76 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
}
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = database.transaction {
nameToParty[name]?.orElse(null)
if (nameToParty[name]?.isPresent == true) {
nameToParty[name]?.get()
}
else {
retrievePartyFromArchive(name)
}
}
private fun retrievePartyFromArchive(name: CordaX500Name): Party? {
val hashKey = database.transaction {
val query = session.criteriaBuilder.createQuery(PersistentNetworkMapCache.PersistentPartyToPublicKeyHash::class.java)
val queryRoot = query.from(PersistentNetworkMapCache.PersistentPartyToPublicKeyHash::class.java)
query.where(session.criteriaBuilder.equal(queryRoot.get<String>("name"), name.toString()))
val resultList = session.createQuery(query).resultList
if (resultList.isNotEmpty()) {
resultList?.first()?.publicKeyHash
}
else {
retrieveHashKeyAndCacheParty(name)
}
}
return hashKey?.let { keyToPartyAndCert[it]?.party }
}
private fun retrieveHashKeyAndCacheParty(name: CordaX500Name): String? {
return database.transaction {
val cb = session.criteriaBuilder
val query = cb.createQuery(PersistentPublicKeyHashToParty::class.java)
val root = query.from(PersistentPublicKeyHashToParty::class.java)
val isNotConfidentialIdentity = cb.equal(root.get<String>("publicKeyHash"), root.get<String>("owningKeyHash"))
val matchName = cb.equal(root.get<String>("name"), name.toString())
query.select(root).where(cb.and(matchName, isNotConfidentialIdentity))
val resultList = session.createQuery(query).resultList
var hashKey: String? = if (resultList.isNotEmpty()) {
if (resultList.size == 1) {
resultList?.single()?.owningKeyHash
}
else {
selectIdentityHash(session, resultList.mapNotNull { it.publicKeyHash }, name)
}
} else {
null
}
archiveNamedIdentity(name.toString(), hashKey)
hashKey
}
}
private fun selectIdentityHash(session: Session, hashList: List<String>, name: CordaX500Name): String? {
val cb = session.criteriaBuilder
val query = cb.createQuery(PersistentPublicKeyHashToCertificate::class.java)
val root = query.from(PersistentPublicKeyHashToCertificate::class.java)
query.select(root).where(root.get<String>("publicKeyHash").`in`(hashList))
val resultList = session.createQuery(query).resultList
resultList.sortWith(compareBy { PartyAndCertificate(X509CertificateFactory().delegate.generateCertPath(it.identity.inputStream())).certificate.notBefore })
log.warn("Retrieving identity hash for removed identity '${name}', more that one hash found, returning last one according to notBefore validity of certificate." +
" Hash returned is ${resultList.last().publicKeyHash}")
return resultList.last().publicKeyHash
}
override fun archiveNamedIdentity(name:String, publicKeyHash: String?) {
archiveIdentityExecutor.submit {
database.transaction {
val deleteQuery = session.criteriaBuilder.createCriteriaDelete(PersistentNetworkMapCache.PersistentPartyToPublicKeyHash::class.java)
val queryRoot = deleteQuery.from(PersistentNetworkMapCache.PersistentPartyToPublicKeyHash::class.java)
deleteQuery.where(session.criteriaBuilder.equal(queryRoot.get<String>("name"), name))
session.createQuery(deleteQuery).executeUpdate()
session.save(PersistentNetworkMapCache.PersistentPartyToPublicKeyHash(name, publicKeyHash))
}
}.get()
}
override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? {

View File

@ -18,6 +18,7 @@ import net.corda.node.internal.artemis.SecureArtemisConfiguration
import net.corda.node.internal.artemis.UserValidationPlugin
import net.corda.node.internal.artemis.isBindingError
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.utilities.artemis.startSynchronously
import net.corda.nodeapi.internal.AmqpMessageSizeChecksInterceptor
import net.corda.nodeapi.internal.ArtemisMessageSizeChecksInterceptor
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX
@ -41,7 +42,6 @@ import org.apache.activemq.artemis.core.security.Role
import org.apache.activemq.artemis.core.server.ActiveMQServer
import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager
import java.io.IOException
import java.lang.Long.max
import javax.annotation.concurrent.ThreadSafe
import javax.security.auth.login.AppConfigurationEntry
@ -107,21 +107,21 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
val artemisConfig = createArtemisConfig()
val securityManager = createArtemisSecurityManager()
activeMQServer = ActiveMQServerImpl(artemisConfig, securityManager).apply {
// Throw any exceptions which are detected during startup
registerActivationFailureListener { exception -> throw exception }
// Some types of queue might need special preparation on our side, like dialling back or preparing
// a lazily initialised subsystem.
registerPostQueueCreationCallback { log.debug { "Queue Created: $it" } }
registerPostQueueDeletionCallback { address, qName -> log.debug { "Queue deleted: $qName for $address" } }
}
@Suppress("TooGenericExceptionCaught")
try {
activeMQServer.start()
} catch (e: IOException) {
activeMQServer.startSynchronously()
} catch (e: Throwable) {
log.error("Unable to start message broker", e)
if (e.isBindingError()) {
throw AddressBindingException(config.p2pAddress)
} else {
log.error("Unexpected error starting message broker", e)
throw e
}
}

View File

@ -5,50 +5,37 @@ import io.netty.channel.ChannelHandlerContext
import io.netty.channel.group.ChannelGroup
import io.netty.handler.logging.LogLevel
import io.netty.handler.logging.LoggingHandler
import io.netty.handler.ssl.SslContext
import io.netty.handler.ssl.SslContextBuilder
import io.netty.handler.ssl.SslHandler
import io.netty.handler.ssl.SslHandshakeTimeoutException
import io.netty.handler.ssl.SslProvider
import net.corda.core.internal.declaredField
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.trace
import net.corda.nodeapi.internal.ArtemisTcpTransport
import net.corda.nodeapi.internal.config.CertificateStore
import net.corda.nodeapi.internal.protonwrapper.netty.createAndInitSslContext
import net.corda.nodeapi.internal.protonwrapper.netty.keyManagerFactory
import net.corda.nodeapi.internal.protonwrapper.netty.sslDelegatedTaskExecutor
import net.corda.nodeapi.internal.setThreadPoolName
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration
import org.apache.activemq.artemis.api.core.BaseInterceptor
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptor
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants
import org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger
import org.apache.activemq.artemis.core.server.balancing.RedirectHandler
import org.apache.activemq.artemis.core.server.cluster.ClusterConnection
import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager
import org.apache.activemq.artemis.spi.core.remoting.Acceptor
import org.apache.activemq.artemis.spi.core.remoting.AcceptorFactory
import org.apache.activemq.artemis.spi.core.remoting.BufferHandler
import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener
import org.apache.activemq.artemis.spi.core.remoting.ssl.OpenSSLContextFactoryProvider
import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactoryProvider
import org.apache.activemq.artemis.utils.ConfigurationHelper
import org.apache.activemq.artemis.utils.actors.OrderedExecutor
import java.net.SocketAddress
import java.nio.channels.ClosedChannelException
import java.nio.file.Paths
import java.security.PrivilegedExceptionAction
import java.time.Duration
import java.util.concurrent.Executor
import java.util.concurrent.ScheduledExecutorService
import java.util.regex.Pattern
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLEngine
import javax.net.ssl.SSLPeerUnverifiedException
import javax.net.ssl.TrustManagerFactory
import javax.security.auth.Subject
@Suppress("unused", "TooGenericExceptionCaught", "ComplexMethod", "MagicNumber", "TooManyFunctions")
@Suppress("unused") // Used via reflection in ArtemisTcpTransport
class NodeNettyAcceptorFactory : AcceptorFactory {
override fun createAcceptor(name: String?,
clusterConnection: ClusterConnection?,
@ -57,7 +44,7 @@ class NodeNettyAcceptorFactory : AcceptorFactory {
listener: ServerConnectionLifeCycleListener?,
threadPool: Executor,
scheduledThreadPool: ScheduledExecutorService,
protocolMap: Map<String, ProtocolManager<BaseInterceptor<*>>>?): Acceptor {
protocolMap: MutableMap<String, ProtocolManager<BaseInterceptor<*>, RedirectHandler<*>>>?): Acceptor {
val threadPoolName = ConfigurationHelper.getStringProperty(ArtemisTcpTransport.THREAD_POOL_NAME_NAME, "Acceptor", configuration)
threadPool.setThreadPoolName("$threadPoolName-artemis")
scheduledThreadPool.setThreadPoolName("$threadPoolName-artemis-scheduler")
@ -83,12 +70,18 @@ class NodeNettyAcceptorFactory : AcceptorFactory {
listener: ServerConnectionLifeCycleListener?,
scheduledThreadPool: ScheduledExecutorService?,
failureExecutor: Executor,
protocolMap: Map<String, ProtocolManager<BaseInterceptor<*>>>?,
protocolMap: MutableMap<String, ProtocolManager<BaseInterceptor<*>, RedirectHandler<*>>>?,
private val threadPoolName: String) :
NettyAcceptor(name, clusterConnection, configuration, handler, listener, scheduledThreadPool, failureExecutor, protocolMap)
{
companion object {
private val defaultThreadPoolNamePattern = Pattern.compile("""Thread-(\d+) \(activemq-netty-threads\)""")
init {
// Make sure Artemis isn't using another (Open)SSLContextFactory
check(SSLContextFactoryProvider.getSSLContextFactory() is NodeSSLContextFactory)
check(OpenSSLContextFactoryProvider.getOpenSSLContextFactory() is NodeOpenSSLContextFactory)
}
}
private val sslDelegatedTaskExecutor = sslDelegatedTaskExecutor(threadPoolName)
@ -112,9 +105,9 @@ class NodeNettyAcceptorFactory : AcceptorFactory {
}
@Synchronized
override fun getSslHandler(alloc: ByteBufAllocator?): SslHandler {
override fun getSslHandler(alloc: ByteBufAllocator?, peerHost: String?, peerPort: Int): SslHandler {
applyThreadPoolName()
val engine = getSSLEngine(alloc)
val engine = super.getSslHandler(alloc, peerHost, peerPort).engine()
val sslHandler = NodeAcceptorSslHandler(engine, sslDelegatedTaskExecutor, trace)
val handshakeTimeout = configuration[ArtemisTcpTransport.SSL_HANDSHAKE_TIMEOUT_NAME] as Duration?
if (handshakeTimeout != null) {
@ -132,111 +125,6 @@ class NodeNettyAcceptorFactory : AcceptorFactory {
Thread.currentThread().name = "$threadPoolName-${matcher.group(1)}" // Preserve the pool thread number
}
}
/**
* This is a copy of [NettyAcceptor.getSslHandler] so that we can provide different implementations for [loadOpenSslEngine] and
* [loadJdkSslEngine]. [NodeNettyAcceptor], instead of creating a default [TrustManagerFactory], will simply use the provided one in
* the [ArtemisTcpTransport.TRUST_MANAGER_FACTORY_NAME] configuration.
*/
private fun getSSLEngine(alloc: ByteBufAllocator?): SSLEngine {
val engine = if (sslProvider == TransportConstants.OPENSSL_PROVIDER) {
loadOpenSslEngine(alloc)
} else {
loadJdkSslEngine()
}
engine.useClientMode = false
if (needClientAuth) {
engine.needClientAuth = true
}
// setting the enabled cipher suites resets the enabled protocols so we need
// to save the enabled protocols so that after the customer cipher suite is enabled
// we can reset the enabled protocols if a customer protocol isn't specified
val originalProtocols = engine.enabledProtocols
if (enabledCipherSuites != null) {
try {
engine.enabledCipherSuites = SSLSupport.parseCommaSeparatedListIntoArray(enabledCipherSuites)
} catch (e: IllegalArgumentException) {
ActiveMQServerLogger.LOGGER.invalidCipherSuite(SSLSupport.parseArrayIntoCommandSeparatedList(engine.supportedCipherSuites))
throw e
}
}
if (enabledProtocols != null) {
try {
engine.enabledProtocols = SSLSupport.parseCommaSeparatedListIntoArray(enabledProtocols)
} catch (e: IllegalArgumentException) {
ActiveMQServerLogger.LOGGER.invalidProtocol(SSLSupport.parseArrayIntoCommandSeparatedList(engine.supportedProtocols))
throw e
}
} else {
engine.enabledProtocols = originalProtocols
}
return engine
}
/**
* Copy of [NettyAcceptor.loadOpenSslEngine] which invokes our custom [createOpenSslContext].
*/
private fun loadOpenSslEngine(alloc: ByteBufAllocator?): SSLEngine {
val context = try {
// We copied all this code just so we could replace the SSLSupport.createNettyContext method call with our own one.
createOpenSslContext()
} catch (e: Exception) {
throw IllegalStateException("Unable to create NodeNettyAcceptor", e)
}
return Subject.doAs<SSLEngine>(null, PrivilegedExceptionAction {
context.newEngine(alloc)
})
}
/**
* Copy of [NettyAcceptor.loadJdkSslEngine] which invokes our custom [createJdkSSLContext].
*/
private fun loadJdkSslEngine(): SSLEngine {
val context = try {
// We copied all this code just so we could replace the SSLHelper.createContext method call with our own one.
createJdkSSLContext()
} catch (e: Exception) {
throw IllegalStateException("Unable to create NodeNettyAcceptor", e)
}
return Subject.doAs<SSLEngine>(null, PrivilegedExceptionAction {
context.createSSLEngine()
})
}
/**
* Create an [SSLContext] using the [TrustManagerFactory] provided on the [ArtemisTcpTransport.TRUST_MANAGER_FACTORY_NAME] config.
*/
private fun createJdkSSLContext(): SSLContext {
return createAndInitSslContext(
createKeyManagerFactory(),
configuration[ArtemisTcpTransport.TRUST_MANAGER_FACTORY_NAME] as TrustManagerFactory?
)
}
/**
* Create an [SslContext] using the the [TrustManagerFactory] provided on the [ArtemisTcpTransport.TRUST_MANAGER_FACTORY_NAME] config.
*/
private fun createOpenSslContext(): SslContext {
return SslContextBuilder
.forServer(createKeyManagerFactory())
.sslProvider(SslProvider.OPENSSL)
.trustManager(configuration[ArtemisTcpTransport.TRUST_MANAGER_FACTORY_NAME] as TrustManagerFactory?)
.build()
}
private fun createKeyManagerFactory(): KeyManagerFactory {
return keyManagerFactory(CertificateStore.fromFile(Paths.get(keyStorePath), keyStorePassword, keyStorePassword, false))
}
// Replicate the fields which are private in NettyAcceptor
private val sslProvider = ConfigurationHelper.getStringProperty(TransportConstants.SSL_PROVIDER, TransportConstants.DEFAULT_SSL_PROVIDER, configuration)
private val needClientAuth = ConfigurationHelper.getBooleanProperty(TransportConstants.NEED_CLIENT_AUTH_PROP_NAME, TransportConstants.DEFAULT_NEED_CLIENT_AUTH, configuration)
private val enabledCipherSuites = ConfigurationHelper.getStringProperty(TransportConstants.ENABLED_CIPHER_SUITES_PROP_NAME, TransportConstants.DEFAULT_ENABLED_CIPHER_SUITES, configuration)
private val enabledProtocols = ConfigurationHelper.getStringProperty(TransportConstants.ENABLED_PROTOCOLS_PROP_NAME, TransportConstants.DEFAULT_ENABLED_PROTOCOLS, configuration)
private val keyStorePath = ConfigurationHelper.getStringProperty(TransportConstants.KEYSTORE_PATH_PROP_NAME, TransportConstants.DEFAULT_KEYSTORE_PATH, configuration)
private val keyStoreProvider = ConfigurationHelper.getStringProperty(TransportConstants.KEYSTORE_PROVIDER_PROP_NAME, TransportConstants.DEFAULT_KEYSTORE_PROVIDER, configuration)
private val keyStorePassword = ConfigurationHelper.getPasswordProperty(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, TransportConstants.DEFAULT_KEYSTORE_PASSWORD, configuration, ActiveMQDefaultConfiguration.getPropMaskPassword(), ActiveMQDefaultConfiguration.getPropPasswordCodec())
}

View File

@ -0,0 +1,59 @@
package net.corda.node.services.messaging
import io.netty.handler.ssl.SslContext
import io.netty.handler.ssl.SslContextBuilder
import io.netty.handler.ssl.SslProvider
import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.TRUST_MANAGER_FACTORY_NAME
import net.corda.nodeapi.internal.config.CertificateStore
import net.corda.nodeapi.internal.protonwrapper.netty.createAndInitSslContext
import net.corda.nodeapi.internal.protonwrapper.netty.keyManagerFactory
import org.apache.activemq.artemis.core.remoting.impl.ssl.DefaultOpenSSLContextFactory
import org.apache.activemq.artemis.core.remoting.impl.ssl.DefaultSSLContextFactory
import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextConfig
import java.nio.file.Paths
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
class NodeSSLContextFactory : DefaultSSLContextFactory() {
override fun getSSLContext(config: SSLContextConfig, additionalOpts: Map<String, Any>): SSLContext {
val trustManagerFactory = additionalOpts[TRUST_MANAGER_FACTORY_NAME] as TrustManagerFactory?
return if (trustManagerFactory != null) {
createAndInitSslContext(loadKeyManagerFactory(config), trustManagerFactory)
} else {
super.getSSLContext(config, additionalOpts)
}
}
override fun getPriority(): Int {
// We make sure this factory is the one that's chosen, so any sufficiently large value will do.
return 15
}
}
class NodeOpenSSLContextFactory : DefaultOpenSSLContextFactory() {
override fun getServerSslContext(config: SSLContextConfig, additionalOpts: Map<String, Any>): SslContext {
val trustManagerFactory = additionalOpts[TRUST_MANAGER_FACTORY_NAME] as TrustManagerFactory?
return if (trustManagerFactory != null) {
SslContextBuilder
.forServer(loadKeyManagerFactory(config))
.sslProvider(SslProvider.OPENSSL)
.trustManager(trustManagerFactory)
.build()
} else {
super.getServerSslContext(config, additionalOpts)
}
}
override fun getPriority(): Int {
// We make sure this factory is the one that's chosen, so any sufficiently large value will do.
return 15
}
}
private fun loadKeyManagerFactory(config: SSLContextConfig): KeyManagerFactory {
val keyStore = CertificateStore.fromFile(Paths.get(config.keystorePath), config.keystorePassword, config.keystorePassword, false)
return keyManagerFactory(keyStore)
}

View File

@ -24,6 +24,10 @@ class P2PMessageDeduplicator(cacheFactory: NamedCacheFactory, private val databa
private val beingProcessedMessages = ConcurrentHashMap<DeduplicationId, MessageMeta>()
private val processedMessages = createProcessedMessages(cacheFactory)
enum class Outcome {
NEW, DUPLICATE, IN_FLIGHT
}
private fun createProcessedMessages(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<DeduplicationId, MessageMeta, ProcessedMessage, String> {
return AppendOnlyPersistentMap(
cacheFactory = cacheFactory,
@ -48,15 +52,17 @@ class P2PMessageDeduplicator(cacheFactory: NamedCacheFactory, private val databa
private fun senderHash(senderKey: SenderKey) = SecureHash.sha256(senderKey.peer.toString() + senderKey.isSessionInit.toString() + senderKey.senderUUID).toString()
/**
* @return true if we have seen this message before.
* @return IN_FLIGHT if this message is currently being processed by the state machine, otherwise indicate if DUPLICATE or NEW.
*/
fun isDuplicate(msg: ReceivedMessage): Boolean {
fun checkDuplicate(msg: ReceivedMessage): Outcome {
if (beingProcessedMessages.containsKey(msg.uniqueMessageId)) {
return true
return Outcome.IN_FLIGHT
}
return isDuplicateInDatabase(msg)
return booleanToEnum(isDuplicateInDatabase(msg))
}
private fun booleanToEnum(isDuplicate: Boolean): Outcome = if (isDuplicate) Outcome.DUPLICATE else Outcome.NEW
/**
* Called the first time we encounter [deduplicationId].
*/

View File

@ -18,7 +18,15 @@ import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.core.serialization.serialize
import net.corda.core.utilities.*
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.minutes
import net.corda.core.utilities.seconds
import net.corda.core.utilities.trace
import net.corda.node.VersionInfo
import net.corda.node.internal.LifecycleSupport
import net.corda.node.internal.artemis.ReactiveArtemisConsumer
@ -31,12 +39,15 @@ import net.corda.node.services.statemachine.SenderDeduplicationId
import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.errorAndTerminate
import net.corda.nodeapi.internal.ArtemisMessagingComponent
import net.corda.nodeapi.internal.ArtemisMessagingComponent.*
import net.corda.nodeapi.internal.ArtemisMessagingComponent.ArtemisAddress
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_CONTROL
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_NOTIFY
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.JOURNAL_HEADER_SIZE
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2PMessagingHeaders
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX
import net.corda.nodeapi.internal.ArtemisMessagingComponent.NodeAddress
import net.corda.nodeapi.internal.ArtemisMessagingComponent.RemoteInboxAddress
import net.corda.nodeapi.internal.ArtemisMessagingComponent.ServiceAddress
import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.p2pConnectorTcpTransport
import net.corda.nodeapi.internal.bridging.BridgeControl
import net.corda.nodeapi.internal.bridging.BridgeEntry
@ -48,15 +59,26 @@ import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration
import org.apache.activemq.artemis.api.core.ActiveMQObjectClosedException
import org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID
import org.apache.activemq.artemis.api.core.Message.HDR_VALIDATED_USER
import org.apache.activemq.artemis.api.core.QueueConfiguration
import org.apache.activemq.artemis.api.core.RoutingType
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.client.*
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
import org.apache.activemq.artemis.api.core.client.ClientConsumer
import org.apache.activemq.artemis.api.core.client.ClientMessage
import org.apache.activemq.artemis.api.core.client.ClientProducer
import org.apache.activemq.artemis.api.core.client.ClientSession
import org.apache.activemq.artemis.api.core.client.ClientSessionFactory
import org.apache.activemq.artemis.api.core.client.FailoverEventType
import org.apache.activemq.artemis.api.core.client.ServerLocator
import rx.Observable
import rx.Subscription
import rx.subjects.PublishSubject
import java.security.PublicKey
import java.time.Duration
import java.time.Instant
import java.util.*
import java.util.Collections
import java.util.Timer
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CountDownLatch
import javax.annotation.concurrent.ThreadSafe
@ -72,15 +94,17 @@ import kotlin.concurrent.timer
* executor through into Artemis and from there, back through to senders.
*
* An implementation of [CordaRPCOps] can be provided. If given, clients using the CordaMQClient RPC library can
* invoke methods on the provided implementation. There is more documentation on this in the docsite and the
* invoke methods on the provided implementation. There is more documentation on this in the doc-site and the
* CordaRPCClient class.
*
* @param config The configuration of the node, which is used for controlling the message redelivery options.
* @param versionInfo All messages from the node carry the version info and received messages are checked against this for compatibility.
* @param serverAddress The host and port of the Artemis broker.
* @param nodeExecutor The received messages are marshalled onto the server executor to prevent Netty buffers leaking during fiber suspends.
* @param database The nodes database, which is used to deduplicate messages.
* @param database The node's database, which is used to deduplicate messages.
* @param terminateOnConnectionError whether the process should be terminated forcibly if connection with the broker fails.
*/
@Suppress("LongParameterList")
@ThreadSafe
class P2PMessagingClient(val config: NodeConfiguration,
private val versionInfo: VersionInfo,
@ -94,7 +118,9 @@ class P2PMessagingClient(val config: NodeConfiguration,
private val isDrainingModeOn: () -> Boolean,
private val drainingModeWasChangedEvents: Observable<Pair<Boolean, Boolean>>,
private val threadPoolName: String = "P2PClient",
private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)
private val stateHelper: ServiceStateHelper = ServiceStateHelper(log),
private val terminateOnConnectionError: Boolean = true,
private val timeoutConfig: TimeoutConfig = TimeoutConfig.default()
) : SingletonSerializeAsToken(), MessagingService, AddressToArtemisQueueResolver, ServiceStateSupport by stateHelper {
companion object {
private val log = contextLogger()
@ -127,6 +153,21 @@ class P2PMessagingClient(val config: NodeConfiguration,
fun sendMessage(address: String, message: ClientMessage) = producer!!.send(address, message)
}
/**
* @property callTimeout the time a blocking call (e.g. message send) from a client waits for a response until it times out.
* @property serverConnectionTtl the time the server waits for a packet/heartbeat from a client before it announces the connection dead and cleans it up.
* @property clientConnectionTtl the time the client waits for a packet/heartbeat from a client before it announces the connection dead and cleans it up.
*/
data class TimeoutConfig(val callTimeout: Duration, val serverConnectionTtl: Duration, val clientConnectionTtl: Duration) {
companion object {
/**
* Some sensible defaults, aligned with defaults of Artemis
*/
@Suppress("MagicNumber")
fun default() = TimeoutConfig(30.seconds, 60.seconds, 30.seconds)
}
}
/** A registration to handle messages of different types */
data class HandlerRegistration(val topic: String, val callback: Any) : MessageHandlerRegistration
@ -167,15 +208,21 @@ class P2PMessagingClient(val config: NodeConfiguration,
// TODO Add broker CN to config for host verification in case the embedded broker isn't used
val tcpTransport = p2pConnectorTcpTransport(serverAddress, config.p2pSslOptions, threadPoolName = threadPoolName)
locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply {
connectionTTL = 60000
clientFailureCheckPeriod = 30000
callTimeout = timeoutConfig.callTimeout.toMillis()
connectionTTL = timeoutConfig.serverConnectionTtl.toMillis()
clientFailureCheckPeriod = timeoutConfig.clientConnectionTtl.toMillis()
minLargeMessageSize = maxMessageSize + JOURNAL_HEADER_SIZE
isUseGlobalPools = nodeSerializationEnv != null
}
sessionFactory = locator!!.createSessionFactory().addFailoverListener(::failoverCallback)
sessionFactory = if (terminateOnConnectionError) {
locator!!.createSessionFactory().addFailoverListener(::failoverCallback)
} else {
locator!!.createSessionFactory()
}
// Login using the node username. The broker will authenticate us as its node (as opposed to another peer)
// using our TLS certificate.
// Note that the acknowledgement of messages is not flushed to the Artermis journal until the default buffer
// Note that the acknowledgement of messages is not flushed to the Artemis journal until the default buffer
// size of 1MB is acknowledged.
val createNewSession = { sessionFactory!!.createSession(ArtemisMessagingComponent.NODE_P2P_USER, ArtemisMessagingComponent.NODE_P2P_USER, false, true, true, false, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE) }
@ -233,7 +280,8 @@ class P2PMessagingClient(val config: NodeConfiguration,
private fun InnerState.registerBridgeControl(session: ClientSession, inboxes: List<String>) {
val bridgeNotifyQueue = "$BRIDGE_NOTIFY.${myIdentity.toStringShort()}"
if (!session.queueQuery(SimpleString(bridgeNotifyQueue)).isExists) {
session.createTemporaryQueue(BRIDGE_NOTIFY, RoutingType.MULTICAST, bridgeNotifyQueue)
session.createQueue(QueueConfiguration(bridgeNotifyQueue).setAddress(BRIDGE_NOTIFY).setRoutingType(RoutingType.MULTICAST)
.setTemporary(true).setDurable(false))
}
val bridgeConsumer = session.createConsumer(bridgeNotifyQueue)
bridgeNotifyConsumer = bridgeConsumer
@ -265,8 +313,8 @@ class P2PMessagingClient(val config: NodeConfiguration,
log.info("Updating bridges on network map change: ${change::class.simpleName} ${change.node}")
fun gatherAddresses(node: NodeInfo): Sequence<BridgeEntry> {
return state.locked {
node.legalIdentitiesAndCerts.map {
val messagingAddress = NodeAddress(it.party.owningKey)
node.legalIdentitiesAndCerts.map { partyAndCertificate ->
val messagingAddress = NodeAddress(partyAndCertificate.party.owningKey)
BridgeEntry(messagingAddress.queueName, node.addresses, node.legalIdentities.map { it.name }, serviceAddress = false)
}.filter { producerSession!!.queueQuery(SimpleString(it.queueName)).isExists }.asSequence()
}
@ -404,12 +452,15 @@ class P2PMessagingClient(val config: NodeConfiguration,
internal fun deliver(artemisMessage: ClientMessage) {
artemisToCordaMessage(artemisMessage)?.let { cordaMessage ->
if (!deduplicator.isDuplicate(cordaMessage)) {
val outcome = deduplicator.checkDuplicate(cordaMessage)
if (outcome == P2PMessageDeduplicator.Outcome.NEW) {
deduplicator.signalMessageProcessStart(cordaMessage)
deliver(cordaMessage, artemisMessage)
} else {
log.trace { "Discard duplicate message ${cordaMessage.uniqueMessageId} for ${cordaMessage.topic}" }
} else if (outcome == P2PMessageDeduplicator.Outcome.DUPLICATE) {
log.debug { "Acknowledge duplicate message id: ${cordaMessage.uniqueMessageId} senderUUID: ${cordaMessage.senderUUID} senderSeqNo: ${cordaMessage.senderSeqNo} isSessionInit: ${cordaMessage.isSessionInit}" }
messagingExecutor!!.acknowledge(artemisMessage)
} else {
log.debug { "Discard in-flight message id: ${cordaMessage.uniqueMessageId} senderUUID: ${cordaMessage.senderUUID} senderSeqNo: ${cordaMessage.senderSeqNo} isSessionInit: ${cordaMessage.isSessionInit}" }
}
}
}
@ -463,8 +514,8 @@ class P2PMessagingClient(val config: NodeConfiguration,
running = false
stateHelper.active = false
networkChangeSubscription?.unsubscribe()
require(p2pConsumer != null, { "stop can't be called twice" })
require(producer != null, { "stop can't be called twice" })
require(p2pConsumer != null) { "stop can't be called twice" }
require(producer != null) { "stop can't be called twice" }
close(p2pConsumer)
p2pConsumer = null
@ -524,7 +575,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
// If we are sending to ourselves then route the message directly to our P2P queue.
RemoteInboxAddress(myIdentity).queueName
} else {
// Otherwise we send the message to an internal queue for the target residing on our broker. It's then the
// Otherwise, we send the message to an internal queue for the target residing on our broker. It's then the
// broker's job to route the message to the target's P2P queue.
val internalTargetQueue = (address as? ArtemisAddress)?.queueName
?: throw IllegalArgumentException("Not an Artemis address")
@ -556,9 +607,13 @@ class P2PMessagingClient(val config: NodeConfiguration,
val queueQuery = session.queueQuery(SimpleString(queueName))
if (!queueQuery.isExists) {
log.info("Create fresh queue $queueName bound on same address")
session.createQueue(queueName, RoutingType.ANYCAST, queueName, null, true, false,
ActiveMQDefaultConfiguration.getDefaultMaxQueueConsumers(),
ActiveMQDefaultConfiguration.getDefaultPurgeOnNoConsumers(), exclusive, null)
session.createQueue(QueueConfiguration(queueName).setRoutingType(RoutingType.ANYCAST).setAddress(queueName)
.setDurable(true).setAutoCreated(false)
.setMaxConsumers(ActiveMQDefaultConfiguration.getDefaultMaxQueueConsumers())
.setPurgeOnNoConsumers(ActiveMQDefaultConfiguration.getDefaultPurgeOnNoConsumers())
.setExclusive(exclusive)
.setLastValue(null)
)
sendBridgeCreateMessage()
}
}
@ -567,7 +622,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
}
override fun addMessageHandler(topic: String, callback: MessageHandler): MessageHandlerRegistration {
require(!topic.isBlank()) { "Topic must not be blank, as the empty topic is a special case." }
require(topic.isNotBlank()) { "Topic must not be blank, as the empty topic is a special case." }
handlers.compute(topic) { _, handler ->
if (handler != null) {
throw IllegalStateException("Cannot add another acking handler for $topic, there is already an acking one")

View File

@ -17,6 +17,7 @@ import net.corda.core.node.services.NetworkMapCache.MapChange
import net.corda.core.node.services.PartyInfo
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.serialize
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
@ -34,6 +35,9 @@ import java.security.PublicKey
import java.security.cert.CertPathValidatorException
import java.util.*
import javax.annotation.concurrent.ThreadSafe
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Id
import javax.persistence.PersistenceException
/** Database-based network map cache. */
@ -61,6 +65,18 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
@Volatile
private lateinit var rotatedNotaries: Set<CordaX500Name>
@Entity
@javax.persistence.Table(name = "node_named_identities")
data class PersistentPartyToPublicKeyHash(
@Id
@Suppress("MagicNumber") // database column width
@Column(name = "name", length = 128, nullable = false)
var name: String = "",
@Column(name = "pk_hash", length = MAX_HASH_HEX_SIZE, nullable = true)
var publicKeyHash: String? = ""
)
// Notary whitelist may contain multiple identities with the same X.500 name after certificate rotation.
// Exclude duplicated entries, which are not present in the network map.
override val notaryIdentities: List<Party> get() = notaries.map { it.identity }
@ -294,6 +310,7 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
synchronized(_changed) {
database.transaction {
removeInfoDB(session, node)
archiveNamedIdentity(node)
changePublisher.onNext(MapChange.Removed(node))
}
}
@ -302,6 +319,12 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
logger.debug { "Done removing node with info: $node" }
}
private fun archiveNamedIdentity(nodeInfo: NodeInfo) {
nodeInfo.legalIdentities.forEach { party ->
identityService.archiveNamedIdentity(party.name.toString(), party.owningKey.toStringShort())
}
}
override val allNodes: List<NodeInfo>
get() {
return database.transaction {
@ -428,7 +451,10 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
database.transaction {
val result = getAllNodeInfos(session)
logger.debug { "Number of node infos to be cleared: ${result.size}" }
for (nodeInfo in result) session.remove(nodeInfo)
for (nodeInfo in result) {
session.remove(nodeInfo)
archiveNamedIdentity(nodeInfo.toNodeInfo())
}
}
}

View File

@ -101,7 +101,9 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
// Rough estimate for the average of a public key and the transaction metadata - hard to get exact figures here,
// as public keys can vary in size a lot, and if someone else is holding a reference to the key, it won't add
// to the memory pressure at all here.
private const val transactionSignatureOverheadEstimate = 1024
private const val TRANSACTION_SIGNATURE_OVERHEAD_BYTES = 1024
private const val TXCACHEVALUE_OVERHEAD_BYTES = 80
private const val SECUREHASH_OVERHEAD_BYTES = 24
private val logger = contextLogger()
@ -134,13 +136,13 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
)
},
persistentEntityClass = DBTransaction::class.java,
weighingFunc = { hash, tx -> hash.size + weighTx(tx) }
weighingFunc = { hash, tx -> SECUREHASH_OVERHEAD_BYTES + hash.size + weighTx(tx) }
)
}
private fun weighTx(tx: AppendOnlyPersistentMapBase.Transactional<TxCacheValue>): Int {
val actTx = tx.peekableValue ?: return 0
return actTx.sigs.sumBy { it.size + transactionSignatureOverheadEstimate } + actTx.txBits.size
private fun weighTx(actTx: TxCacheValue?): Int {
if (actTx == null) return 0
return TXCACHEVALUE_OVERHEAD_BYTES + actTx.sigs.sumBy { it.size + TRANSACTION_SIGNATURE_OVERHEAD_BYTES } + actTx.txBits.size
}
private val log = contextLogger()

View File

@ -88,6 +88,9 @@ class NodeAttachmentService @JvmOverloads constructor(
while (true) {
val cursor = jar.nextJarEntry ?: break
// Security check to stop directory traversal from filename entry
require(!(cursor.name.contains("../"))) { "Bad character in ${cursor.name}" }
require(!(cursor.name.contains("..\\"))) { "Bad character in ${cursor.name}" }
if (manifestHasEntries && !allManifestEntries!!.remove(cursor.name)) extraFilesNotFoundInEntries.add(cursor)
val entryPath = Paths.get(cursor.name)
// Security check to stop zips trying to escape their rightful place.

View File

@ -8,6 +8,7 @@ import net.corda.node.internal.artemis.*
import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.NODE_SECURITY_CONFIG
import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.RPC_SECURITY_CONFIG
import net.corda.node.internal.security.RPCSecurityManager
import net.corda.node.utilities.artemis.startSynchronously
import net.corda.nodeapi.BrokerRpcSslOptions
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
@ -51,20 +52,19 @@ class ArtemisRpcBroker internal constructor(
}
}
@Suppress("TooGenericExceptionCaught")
override fun start() {
logger.debug { "Artemis RPC broker is starting for: $addresses" }
try {
server.start()
} catch (e: IOException) {
server.startSynchronously()
} catch (e: Throwable) {
logger.error("Unable to start message broker", e)
if (e.isBindingError()) {
throw AddressBindingException(adminAddressOptional?.let { setOf(it, addresses.primary) } ?: setOf(addresses.primary))
} else {
logger.error("Unexpected error starting message broker", e)
throw e
}
} catch (th: Throwable) {
logger.error("Unexpected error starting message broker", th)
throw th
}
logger.debug("Artemis RPC broker is started.")
}
@ -90,7 +90,6 @@ class ArtemisRpcBroker internal constructor(
val serverSecurityManager = createArtemisSecurityManager(serverConfiguration.loginListener)
return ActiveMQServerImpl(serverConfiguration, serverSecurityManager).apply {
registerActivationFailureListener { exception -> throw exception }
registerPostQueueDeletionCallback { address, qName -> logger.debug("Queue deleted: $qName for $address") }
}
}

View File

@ -12,8 +12,8 @@ import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.rpcAcceptorTcpTr
import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.rpcInternalAcceptorTcpTransport
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration
import org.apache.activemq.artemis.api.core.QueueConfiguration
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.core.config.CoreQueueConfiguration
import org.apache.activemq.artemis.core.security.Role
import org.apache.activemq.artemis.core.settings.impl.AddressFullMessagePolicy
import org.apache.activemq.artemis.core.settings.impl.AddressSettings
@ -37,14 +37,14 @@ internal class RpcBrokerConfiguration(baseDirectory: Path, maxMessageSize: Int,
}
acceptorConfigurations = acceptorConfigurationsSet
queueConfigurations = queueConfigurations()
queueConfigs = queueConfigurations()
managementNotificationAddress = SimpleString(ArtemisMessagingComponent.NOTIFICATIONS_ADDRESS)
addressesSettings = mapOf(
"${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.#" to AddressSettings().apply {
maxSizeBytes = 5L * maxMessageSize
addressFullMessagePolicy = AddressFullMessagePolicy.PAGE
pageSizeBytes = 1L * maxMessageSize
pageSizeBytes = maxMessageSize
}
)
@ -76,7 +76,11 @@ internal class RpcBrokerConfiguration(baseDirectory: Path, maxMessageSize: Int,
securityRoles["${ArtemisMessagingComponent.INTERNAL_PREFIX}#"] = setOf(nodeInternalRole)
securityRoles[RPCApi.RPC_SERVER_QUEUE_NAME] = setOf(nodeInternalRole, restrictedRole(BrokerJaasLoginModule.RPC_ROLE, send = true))
securitySettingPlugins.add(rolesAdderOnLogin)
securityInvalidationInterval = ArtemisMessagingComponent.SECURITY_INVALIDATION_INTERVAL
// Effectively disable security cache as permissions might change dynamically when e.g. DB is updated
authenticationCacheSize = 0
authorizationCacheSize = 0
securityInvalidationInterval = 0
}
private fun enableJmx() {
@ -85,19 +89,19 @@ internal class RpcBrokerConfiguration(baseDirectory: Path, maxMessageSize: Int,
}
private fun initialiseSettings(maxMessageSize: Int, journalBufferTimeout: Int?) {
// Enable built in message deduplication. Note we still have to do our own as the delayed commits
// and our own definition of commit mean that the built in deduplication cannot remove all duplicates.
// Enable built-in message deduplication. Note we still have to do our own as the delayed commits
// and our own definition of commit means that the built-in deduplication cannot remove all the duplicates.
idCacheSize = 2000 // Artemis Default duplicate cache size i.e. a guess
isPersistIDCache = true
isPopulateValidatedUser = true
journalBufferSize_NIO = maxMessageSize // Artemis default is 490KiB - required to address IllegalArgumentException (when Artemis uses Java NIO): Record is too large to store.
journalBufferSize_NIO = maxMessageSize // Artemis default is 490 KB - required to address IllegalArgumentException (when Artemis uses Java NIO): Record is too large to store.
journalBufferTimeout_NIO = journalBufferTimeout ?: ActiveMQDefaultConfiguration.getDefaultJournalBufferTimeoutNio()
journalBufferSize_AIO = maxMessageSize // Required to address IllegalArgumentException (when Artemis uses Linux Async IO): Record is too large to store.
journalBufferTimeout_AIO = journalBufferTimeout ?: ActiveMQDefaultConfiguration.getDefaultJournalBufferTimeoutAio()
journalFileSize = maxMessageSize // The size of each journal file in bytes. Artemis default is 10MiB.
journalFileSize = maxMessageSize // The size of each journal file in bytes. Artemis default is 10 MB.
}
private fun queueConfigurations(): List<CoreQueueConfiguration> {
private fun queueConfigurations(): List<QueueConfiguration> {
return listOf(
queueConfiguration(RPCApi.RPC_SERVER_QUEUE_NAME, durable = false),
queueConfiguration(
@ -122,15 +126,8 @@ internal class RpcBrokerConfiguration(baseDirectory: Path, maxMessageSize: Int,
pagingDirectory = (baseDirectory / "paging").toString()
}
private fun queueConfiguration(name: String, address: String = name, filter: String? = null, durable: Boolean): CoreQueueConfiguration {
val configuration = CoreQueueConfiguration()
configuration.name = name
configuration.address = address
configuration.filterString = filter
configuration.isDurable = durable
return configuration
private fun queueConfiguration(name: String, address: String = name, filter: String? = null, durable: Boolean): QueueConfiguration {
return QueueConfiguration(name).setAddress(address).setFilterString(filter).setDurable(durable)
}
private fun restrictedRole(name: String, send: Boolean = false, consume: Boolean = false, createDurableQueue: Boolean = false,

View File

@ -14,6 +14,7 @@ import net.corda.node.services.events.NodeSchedulerService
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.node.services.messaging.P2PMessageDeduplicator
import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.persistence.DBCheckpointStorage
import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.persistence.NodeAttachmentService
@ -49,7 +50,8 @@ class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()
PersistentIdentityService.PersistentHashToPublicKey::class.java,
ContractUpgradeServiceImpl.DBContractUpgrade::class.java,
DBNetworkParametersStorage.PersistentNetworkParameters::class.java,
PublicKeyHashToExternalId::class.java
PublicKeyHashToExternalId::class.java,
PersistentNetworkMapCache.PersistentPartyToPublicKeyHash::class.java
)) {
override val migrationResource = "node-core.changelog-master"
}

View File

@ -11,6 +11,7 @@ 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.ResolveTransactionsFlow
import net.corda.core.internal.ThreadBox
import net.corda.core.internal.TimedFlow
import net.corda.core.internal.VisibleForTesting
@ -21,6 +22,7 @@ import net.corda.core.utilities.debug
import net.corda.core.utilities.minutes
import net.corda.core.utilities.seconds
import net.corda.node.services.FinalityHandler
import net.corda.node.services.statemachine.transitions.StartedFlowTransition
import org.hibernate.exception.ConstraintViolationException
import rx.subjects.PublishSubject
import java.io.Closeable
@ -29,10 +31,9 @@ import java.sql.SQLTransientConnectionException
import java.time.Clock
import java.time.Duration
import java.time.Instant
import java.util.*
import java.util.Timer
import java.util.concurrent.ConcurrentHashMap
import javax.persistence.PersistenceException
import kotlin.collections.HashMap
import kotlin.concurrent.timerTask
import kotlin.math.pow
@ -103,17 +104,6 @@ class StaffedFlowHospital(private val flowMessaging: FlowMessaging,
* Flows should be removed from [flowsInHospital] when they have completed a successful transition.
*/
private val flowsInHospital = ConcurrentHashMap<StateMachineRunId, FlowFiber>()
/**
* Returns true if the flow is currently being treated in the hospital.
* The differs to flows with a medical history (which can accessed via [StaffedFlowHospital.contains]).
*/
@VisibleForTesting
internal fun flowInHospital(runId: StateMachineRunId): Boolean {
// The .keys avoids https://youtrack.jetbrains.com/issue/KT-18053
return runId in flowsInHospital.keys
}
private val mutex = ThreadBox(object {
/**
* Contains medical history of every flow (a patient) that has entered the hospital. A flow can leave the hospital,
@ -347,7 +337,7 @@ class StaffedFlowHospital(private val flowMessaging: FlowMessaging,
}
}
operator fun contains(flowId: StateMachineRunId) = mutex.locked { flowId in flowPatients }
operator fun contains(flowId: StateMachineRunId) = flowId in flowsInHospital.keys
override fun close() {
hospitalJobTimer.cancel()
@ -485,13 +475,22 @@ class StaffedFlowHospital(private val flowMessaging: FlowMessaging,
"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
when {
isErrorPropagatedFromCounterparty(newError) && isErrorThrownDuringReceiveTransactionFlow(newError) -> {
// no need to keep around the flow, since notarisation has already failed at the counterparty.
Diagnosis.NOT_MY_SPECIALTY
}
isEndSessionErrorThrownDuringReceiveTransactionFlow(newError) -> {
// Typically occurs if the initiating flow catches a notary exception and ends their flow successfully.
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
@ -523,13 +522,26 @@ class StaffedFlowHospital(private val flowMessaging: FlowMessaging,
* 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 {
private fun isErrorThrownDuringReceiveTransactionFlow(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!!)
}
/**
* Checks if an end session error exception was thrown and that it did so within [ReceiveTransactionFlow].
*
* The check for [ReceiveTransactionFlow] is important to ensure that the session didn't end within [ResolveTransactionsFlow] which
* implies that it has been receiving the transaction's dependencies and therefore ending before receiving the whole transaction
* is incorrect behaviour.
*/
private fun isEndSessionErrorThrownDuringReceiveTransactionFlow(error: Throwable): Boolean {
return error is UnexpectedFlowEndException
&& error.message?.contains(StartedFlowTransition.UNEXPECTED_SESSION_END_MESSAGE) == true
&& isErrorThrownDuringReceiveTransactionFlow(error)
}
}
/**

View File

@ -28,6 +28,7 @@ class StartedFlowTransition(
companion object {
private val logger: Logger = contextLogger()
const val UNEXPECTED_SESSION_END_MESSAGE = "Received session end message instead of a data session message. Mismatched send and receive?"
}
override fun transition(): TransitionResult {
@ -253,7 +254,7 @@ class StartedFlowTransition(
newSessionMessages[sessionId] = sessionState.copy(receivedMessages = messages.subList(1, messages.size).toArrayList())
// at this point, we've already checked for errors and session ends, so it's guaranteed that the first message will be a data message.
resultMessages[sessionId] = if (messages[0] is EndSessionMessage) {
throw UnexpectedFlowEndException("Received session end message instead of a data session message. Mismatched send and receive?")
throw UnexpectedFlowEndException(UNEXPECTED_SESSION_END_MESSAGE)
} else {
(messages[0] as DataSessionMessage).payload
}

View File

@ -272,7 +272,7 @@ class HibernateAttachmentQueryCriteriaParser<T,R>(override val criteriaBuilder:
class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractState>,
val contractStateTypeMappings: Map<String, Set<String>>,
override val criteriaBuilder: CriteriaBuilder,
val criteriaQuery: CriteriaQuery<Tuple>,
val criteriaQuery: CriteriaQuery<*>,
val vaultStates: Root<VaultSchemaV1.VaultStates>) : AbstractQueryCriteriaParser<QueryCriteria, IQueryCriteriaParser, Sort>(), IQueryCriteriaParser {
private companion object {
private val log = contextLogger()

View File

@ -35,7 +35,6 @@ import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.Sort
import net.corda.core.node.services.vault.SortAttribute
import net.corda.core.node.services.vault.builder
import net.corda.core.observable.internal.OnResilientSubscribe
import net.corda.core.schemas.PersistentStateRef
import net.corda.core.serialization.SingletonSerializeAsToken
@ -69,17 +68,21 @@ import java.security.PublicKey
import java.sql.SQLException
import java.time.Clock
import java.time.Instant
import java.util.Arrays
import java.util.UUID
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArraySet
import java.util.stream.Stream
import javax.persistence.PersistenceException
import javax.persistence.Tuple
import javax.persistence.criteria.CriteriaBuilder
import javax.persistence.criteria.CriteriaQuery
import javax.persistence.criteria.CriteriaUpdate
import javax.persistence.criteria.Predicate
import javax.persistence.criteria.Root
import kotlin.collections.ArrayList
import kotlin.collections.LinkedHashSet
import kotlin.collections.component1
import kotlin.collections.component2
/**
* The vault service handles storage, retrieval and querying of states.
@ -706,7 +709,8 @@ class NodeVaultService(
paging: PageSpecification,
sorting: Sort,
contractStateType: Class<out T>): Vault.Page<T> {
val (query, stateTypes) = createQuery(criteria, contractStateType, sorting)
val (criteriaQuery, criteriaParser) = buildCriteriaQuery<Tuple>(criteria, contractStateType, sorting)
val query = getSession().createQuery(criteriaQuery)
query.setResultWindow(paging)
val statesMetadata: MutableList<Vault.StateMetadata> = mutableListOf()
@ -736,7 +740,7 @@ class NodeVaultService(
else -> queryTotalStateCount(criteria, contractStateType)
}
return Vault.Page(states, statesMetadata, totalStatesAvailable, stateTypes, otherResults)
return Vault.Page(states, statesMetadata, totalStatesAvailable, criteriaParser.stateTypes, otherResults)
}
private fun <R> Query<R>.resultStream(paging: PageSpecification): Stream<R> {
@ -765,19 +769,17 @@ class NodeVaultService(
}
}
private fun <T : ContractState> queryTotalStateCount(baseCriteria: QueryCriteria, contractStateType: Class<out T>): Long {
val count = builder { VaultSchemaV1.VaultStates::recordedTime.count() }
val countCriteria = QueryCriteria.VaultCustomQueryCriteria(count, Vault.StateStatus.ALL)
val criteria = baseCriteria.and(countCriteria)
val (query) = createQuery(criteria, contractStateType, null)
val results = query.resultList
return results.last().toArray().last() as Long
private fun <T : ContractState> queryTotalStateCount(criteria: QueryCriteria, contractStateType: Class<out T>): Long {
val (criteriaQuery, criteriaParser) = buildCriteriaQuery<Long>(criteria, contractStateType, null)
criteriaQuery.select(criteriaBuilder.count(criteriaParser.vaultStates))
val query = getSession().createQuery(criteriaQuery)
return query.singleResult
}
private fun <T : ContractState> createQuery(criteria: QueryCriteria,
contractStateType: Class<out T>,
sorting: Sort?): Pair<Query<Tuple>, Vault.StateStatus> {
val criteriaQuery = criteriaBuilder.createQuery(Tuple::class.java)
private inline fun <reified T> buildCriteriaQuery(criteria: QueryCriteria,
contractStateType: Class<out ContractState>,
sorting: Sort?): Pair<CriteriaQuery<T>, HibernateQueryCriteriaParser> {
val criteriaQuery = criteriaBuilder.createQuery(T::class.java)
val criteriaParser = HibernateQueryCriteriaParser(
contractStateType,
contractStateTypeMappings,
@ -786,8 +788,7 @@ class NodeVaultService(
criteriaQuery.from(VaultSchemaV1.VaultStates::class.java)
)
criteriaParser.parse(criteria, sorting)
val query = getSession().createQuery(criteriaQuery)
return Pair(query, criteriaParser.stateTypes)
return Pair(criteriaQuery, criteriaParser)
}
/**

View File

@ -32,8 +32,10 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
private val log = contextLogger()
}
protected class PendingKeyValue(val transactions: MutableSet<DatabaseTransaction>, val estimatedSize: Int)
protected abstract val cache: LoadingCache<K, Transactional<V>>
protected val pendingKeys = ConcurrentHashMap<K, MutableSet<DatabaseTransaction>>()
protected val pendingKeys = ConcurrentHashMap<K, PendingKeyValue>()
/**
* Returns the value associated with the key, first loading that value from the storage if necessary.
@ -85,7 +87,8 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
// for cases where the value passed to set differs from that in the cache, but an update function has decided that this
// differing value should not be written to the database.
if (wasWritten) {
Transactional.InFlight(this, key, _readerValueLoader = { loadValue(key) }).apply { alsoWrite(value) }
Transactional.InFlight(this, key, weight = weight(key, value), _readerValueLoader = { loadValue(key) })
.apply { alsoWrite(value) }
} else {
oldValueInCache
}
@ -120,7 +123,8 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
Transactional.Committed(oldValue)
} else {
// Some database transactions, including us, writing, with readers seeing whatever is in the database and writers seeing the (in memory) value.
Transactional.InFlight(this, key, _readerValueLoader = { loadValue(key) }).apply { alsoWrite(value) }
Transactional.InFlight(this, key, weight = weight(key, value), _readerValueLoader = { loadValue(key) })
.apply { alsoWrite(value) }
}
}
@ -214,11 +218,12 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
protected fun transactionalLoadValue(key: K): Transactional<V> {
// This gets called if a value is read and the cache has no Transactional for this key yet.
return if (anyoneWriting(key)) {
val estimatedSize = anyoneWriting(key)
return if (estimatedSize != -1) {
// If someone is writing (but not us)
// For those not writing, they need to re-load the value from the database (which their database transaction MIGHT see).
// For those writing, they need to re-load the value from the database (which their database transaction CAN see).
Transactional.InFlight(this, key, { loadValue(key) }, { loadValue(key)!! })
Transactional.InFlight(this, key, estimatedSize, { loadValue(key) }, { loadValue(key)!! })
} else {
// If no one is writing, then the value may or may not exist in the database.
Transactional.Unknown(this, key) { loadValue(key) }
@ -240,21 +245,24 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
}
// Helpers to know if transaction(s) are currently writing the given key.
private fun weAreWriting(key: K): Boolean = pendingKeys[key]?.contains(contextTransaction) ?: false
private fun weAreWriting(key: K): Boolean = pendingKeys[key]?.transactions?.contains(contextTransaction) ?: false
private fun anyoneWriting(key: K): Boolean = pendingKeys[key]?.isNotEmpty() ?: false
private fun anyoneWriting(key: K): Int = pendingKeys[key]?.estimatedSize ?: -1
protected open fun weight(key: K, value: V): Int = 1
// Indicate this database transaction is a writer of this key.
private fun addPendingKey(key: K, databaseTransaction: DatabaseTransaction): Boolean {
private fun addPendingKey(key: K, databaseTransaction: DatabaseTransaction, estimatedSize: Int): Boolean {
var added = true
pendingKeys.compute(key) { _, oldSet ->
pendingKeys.compute(key) { _, value: PendingKeyValue? ->
val oldSet = value?.transactions
if (oldSet == null) {
val newSet = HashSet<DatabaseTransaction>(0)
newSet += databaseTransaction
newSet
PendingKeyValue(newSet, estimatedSize)
} else {
added = oldSet.add(databaseTransaction)
oldSet
value
}
}
return added
@ -262,12 +270,13 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
// Remove this database transaction as a writer of this key, because the transaction committed or rolled back.
private fun removePendingKey(key: K, databaseTransaction: DatabaseTransaction) {
pendingKeys.compute(key) { _, oldSet ->
pendingKeys.compute(key) { _, value: PendingKeyValue? ->
val oldSet = value?.transactions
if (oldSet == null) {
oldSet
null
} else {
oldSet -= databaseTransaction
if (oldSet.size == 0) null else oldSet
if (oldSet.size == 0) null else value
}
}
}
@ -278,10 +287,12 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
* There are 3 states. Globally missing, globally visible, and being written in a transaction somewhere now or in
* the past (and it rolled back).
*/
@Suppress("MagicNumber")
sealed class Transactional<T> {
abstract val value: T
abstract val isPresent: Boolean
abstract val peekableValue: T?
abstract val shallowSize: Int
fun orElse(alt: T?) = if (isPresent) value else alt
@ -291,6 +302,8 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
get() = true
override val peekableValue: T?
get() = value
override val shallowSize: Int
get() = 48
}
// No one can see it.
@ -301,6 +314,8 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
get() = false
override val peekableValue: T?
get() = null
override val shallowSize: Int
get() = 16
}
// No one is writing, but we haven't looked in the database yet. This can only be when there are no writers.
@ -323,12 +338,15 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
}
val isResolved: Boolean get() = valueWithoutIsolationDelegate.isInitialized()
override val peekableValue: T? get() = if (isResolved && isPresent) value else null
override val shallowSize: Int
get() = 128
}
// Written in a transaction (uncommitted) somewhere, but there's a small window when this might be seen after commit,
// hence the committed flag.
class InFlight<K, T>(private val map: AppendOnlyPersistentMapBase<K, T, *, *>,
private val key: K,
val weight: Int,
private val _readerValueLoader: () -> T?,
private val _writerValueLoader: () -> T = { throw IllegalAccessException("No value loader provided") }) : Transactional<T>() {
@ -352,7 +370,7 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
val tx = contextTransaction
val strongKey = key
val strongMap = map
if (map.addPendingKey(key, tx)) {
if (map.addPendingKey(key, tx, weight)) {
// If the transaction commits, update cache to make globally visible if we're first for this key,
// and then stop saying the transaction is writing the key.
tx.onCommit {
@ -414,6 +432,9 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
// The value from the perspective of the eviction algorithm of the cache. i.e. we want to reveal memory footprint to it etc.
override val peekableValue: T?
get() = if (writerValueLoader.get() != _writerValueLoader) writerValueLoader.get()() else if (readerValueLoader.get() != _readerValueLoader) readerValueLoader.get()() else null
override val shallowSize: Int
get() = 256
}
}
}
@ -445,15 +466,24 @@ class WeightBasedAppendOnlyPersistentMap<K, V, E, out EK>(
fromPersistentEntity: (E) -> Pair<K, V>,
toPersistentEntity: (key: K, value: V) -> E,
persistentEntityClass: Class<E>,
weighingFunc: (K, Transactional<V>) -> Int
private val weighingFunc: (K, V?) -> Int
) : AppendOnlyPersistentMapBase<K, V, E, EK>(
toPersistentEntityKey,
fromPersistentEntity,
toPersistentEntity,
persistentEntityClass) {
override fun weight(key: K, value: V): Int = weighingFunc(key, value)
override val cache = NonInvalidatingWeightBasedCache(
cacheFactory = cacheFactory,
name = name,
weigher = Weigher { key, value -> weighingFunc(key, value) },
weigher = Weigher { key, value: Transactional<V> ->
value.shallowSize + if (value is Transactional.InFlight<*, *>) {
value.weight * 2
} else {
weighingFunc(key, value.peekableValue)
}
},
loadFunction = { key: K -> transactionalLoadValue(key) })
}

View File

@ -73,7 +73,7 @@ class InfrequentlyMutatedCache<K : Any, V : Any>(name: String, cacheFactory: Nam
backingCache.invalidateAll()
}
private fun invalidate(key: K, value: Wrapper.Invalidated<V>): Wrapper.Invalidated<V> {
private fun invalidate(key: K, value: Wrapper.Invalidated<V>): Wrapper.Invalidated<V>? {
val tx = contextTransactionOrNull
value.invalidators.incrementAndGet()
currentlyInvalid[key] = value
@ -81,7 +81,10 @@ class InfrequentlyMutatedCache<K : Any, V : Any>(name: String, cacheFactory: Nam
// When we close, we can't start using caching again until all simultaneously open transactions are closed.
tx.onClose { tx.database.onAllOpenTransactionsClosed { decrementInvalidators(key, value) } }
} else {
decrementInvalidators(key, value)
if (value.invalidators.decrementAndGet() == 0) {
currentlyInvalid.remove(key)
return null
}
}
return value
}

View File

@ -0,0 +1,22 @@
package net.corda.node.utilities.artemis
import net.corda.core.utilities.getOrThrow
import org.apache.activemq.artemis.core.server.ActivateCallback
import org.apache.activemq.artemis.core.server.ActiveMQServer
import java.util.concurrent.CompletableFuture
fun ActiveMQServer.startSynchronously() {
val startupFuture = CompletableFuture<Unit>()
registerActivateCallback(object: ActivateCallback {
override fun activationComplete() {
startupFuture.complete(Unit)
}
})
registerActivationFailureListener {
startupFuture.completeExceptionally(it)
}
start()
startupFuture.getOrThrow()
}

View File

@ -0,0 +1 @@
net.corda.node.services.messaging.NodeOpenSSLContextFactory

View File

@ -0,0 +1 @@
net.corda.node.services.messaging.NodeSSLContextFactory

View File

@ -27,6 +27,7 @@
<include file="migration/node-core.changelog-v14-data.xml"/>
<include file="migration/node-core.changelog-v16.xml"/>
<include file="migration/node-core.changelog-v20.xml"/>
<include file="migration/node-core.changelog-v22.xml"/>
<!-- This must run after node-core.changelog-init.xml, to prevent database columns being created twice. -->
<include file="migration/vault-schema.changelog-v9.xml"/>

View File

@ -0,0 +1,13 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet author="R3.Corda" id="add_node_named_identities_table_back_in">
<createTable tableName="node_named_identities">
<column name="name" type="NVARCHAR(128)">
<constraints nullable="false"/>
</column>
<column name="pk_hash" type="NVARCHAR(130)"/>
</createTable>
</changeSet>
</databaseChangeLog>

View File

@ -1,24 +1,34 @@
package net.corda.node.internal.artemis
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.doThrow
import com.nhaarman.mockito_kotlin.whenever
import net.corda.coretesting.internal.rigorousMock
import net.corda.nodeapi.internal.ArtemisMessagingComponent
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
import org.apache.activemq.artemis.api.core.Message
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.core.client.impl.ClientMessageImpl
import org.apache.activemq.artemis.core.server.ServerSession
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage
import org.apache.activemq.artemis.protocol.amqp.converter.AMQPConverter
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPStandardMessage
import org.assertj.core.api.Assertions
import org.junit.Test
class UserValidationPluginTest {
private val plugin = UserValidationPlugin()
private val coreMessage = ClientMessageImpl(0, false, 0, System.currentTimeMillis(), 4.toByte(), 1024)
private val amqpMessage get() = AMQPConverter.getInstance().fromCore(coreMessage)
private val coreMessage = ClientMessageImpl(0, false, 0, System.currentTimeMillis(),
4.toByte(), 1024)
private val amqpMessage: AMQPMessage
get() {
return rigorousMock<AMQPMessage>().also {
doReturn(coreMessage.validatedUserID).whenever(it).getStringProperty(Message.HDR_VALIDATED_USER)
}
}
private val session = rigorousMock<ServerSession>().also {
doReturn(ArtemisMessagingComponent.PEER_USER).whenever(it).username
doReturn(ALICE_NAME.toString()).whenever(it).validatedUser
@ -31,16 +41,17 @@ class UserValidationPluginTest {
@Test(timeout = 300_000)
fun `accept AMQP message with user`() {
coreMessage.putStringProperty("_AMQ_VALIDATED_USER", ALICE_NAME.toString())
coreMessage.validatedUserID = ALICE_NAME.toString()
plugin.beforeSend(session, rigorousMock(), amqpMessage, direct = false, noAutoCreateQueue = false)
}
@Test(timeout = 300_000)
fun `reject AMQP message with different user`() {
coreMessage.putStringProperty("_AMQ_VALIDATED_USER", BOB_NAME.toString())
coreMessage.validatedUserID = BOB_NAME.toString()
val localAmqpMessage = amqpMessage
Assertions.assertThatExceptionOfType(ActiveMQSecurityException::class.java).isThrownBy {
plugin.beforeSend(session, rigorousMock(), amqpMessage, direct = false, noAutoCreateQueue = false)
}.withMessageContaining("_AMQ_VALIDATED_USER")
plugin.beforeSend(session, rigorousMock(), localAmqpMessage, direct = false, noAutoCreateQueue = false)
}.withMessageContaining(Message.HDR_VALIDATED_USER.toString())
}
@Test(timeout = 300_000)
@ -49,7 +60,7 @@ class UserValidationPluginTest {
doReturn(ArtemisMessagingComponent.NODE_P2P_USER).whenever(it).username
doReturn(ALICE_NAME.toString()).whenever(it).validatedUser
}
coreMessage.putStringProperty("_AMQ_VALIDATED_USER", BOB_NAME.toString())
coreMessage.validatedUserID = BOB_NAME.toString()
plugin.beforeSend(internalSession, rigorousMock(), amqpMessage, direct = false, noAutoCreateQueue = false)
}
@ -62,11 +73,8 @@ class UserValidationPluginTest {
@Test(timeout = 300_000)
fun `reject message with exception`() {
coreMessage.putStringProperty("_AMQ_VALIDATED_USER", BOB_NAME.toString())
val messageWithException = object : AMQPMessage(0, amqpMessage.buffer.array(), null) {
override fun getStringProperty(key: SimpleString?): String {
throw IllegalStateException("My exception")
}
val messageWithException = rigorousMock<AMQPMessage>().also {
doThrow(IllegalStateException("My exception")).whenever(it).getStringProperty(any<SimpleString>())
}
// Artemis swallows all exceptions except ActiveMQException, so making sure that proper exception is thrown
Assertions.assertThatExceptionOfType(ActiveMQSecurityException::class.java).isThrownBy {
@ -76,9 +84,8 @@ class UserValidationPluginTest {
@Test(timeout = 300_000)
fun `reject message with security exception`() {
coreMessage.putStringProperty("_AMQ_VALIDATED_USER", BOB_NAME.toString())
val messageWithException = object : AMQPMessage(0, amqpMessage.buffer.array(), null) {
override fun getStringProperty(key: SimpleString?): String {
val messageWithException = object : AMQPStandardMessage(0, ByteArray(0), null) {
override fun getApplicationPropertiesMap(createIfAbsent: Boolean): MutableMap<String, Any> {
throw ActiveMQSecurityException("My security exception")
}
}

View File

@ -50,7 +50,7 @@ object IdentityTestSchemaV1 : MappedSchema(
@Column(name = "name", length = 128, nullable = false)
var name: String = "",
@Column(name = "pk_hash", length = MAX_HASH_HEX_SIZE, nullable = false)
@Column(name = "pk_hash", length = MAX_HASH_HEX_SIZE, nullable = true)
var publicKeyHash: String = ""
)

View File

@ -10,10 +10,10 @@ import net.corda.core.internal.div
import net.corda.core.internal.toPath
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.seconds
import net.corda.node.services.config.shell.SSHDConfiguration
import net.corda.nodeapi.internal.config.getBooleanCaseInsensitive
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import net.corda.tools.shell.SSHDConfiguration
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Assert.assertEquals

View File

@ -1,16 +1,22 @@
package net.corda.node.services.network
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.CordaX500Name
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.serialization.serialize
import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNodeParameters
import net.corda.testing.node.internal.TestStartedNode
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Assert
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import java.math.BigInteger
import kotlin.test.assertEquals
@ -18,6 +24,7 @@ import kotlin.test.assertNotNull
import kotlin.test.assertNull
class NetworkMapCacheTest {
private val TestStartedNode.party get() = info.legalIdentities.first()
private val mockNet = InternalMockNetwork()
@After
@ -25,6 +32,153 @@ class NetworkMapCacheTest {
mockNet.stopNodes()
}
@Test(timeout=300_000)
fun `unknown Party object gets recorded as null entry in node_named_identities table`() {
val bobNode = mockNet.createPartyNode(BOB_NAME)
assertEquals(null, bobNode.services.identityService.wellKnownPartyFromX500Name(CHARLIE_NAME))
bobNode.database.transaction {
val cb = session.criteriaBuilder
val query = cb.createQuery(PersistentNetworkMapCache.PersistentPartyToPublicKeyHash::class.java)
val root = query.from(PersistentNetworkMapCache.PersistentPartyToPublicKeyHash::class.java)
val matchPublicKey = cb.isNull(root.get<String>("publicKeyHash"))
val matchName = cb.equal(root.get<String>("name"), CHARLIE_NAME.toString())
query.select(root).where(cb.and(matchName, matchPublicKey))
val resultList = session.createQuery(query).resultList
assertEquals(1, resultList.size)
}
}
@Test(timeout=300_000)
fun `check Party object can still be retrieved when not in node_named_identities table`() {
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
val bobNode = mockNet.createPartyNode(BOB_NAME)
val bobCache: NetworkMapCache = bobNode.services.networkMapCache
val bobCacheInternal = bobCache as NetworkMapCacheInternal
assertNotNull(bobCacheInternal)
bobCache.removeNode(aliceNode.info)
val alicePubKeyHash = aliceNode.info.legalIdentities[0].owningKey.toStringShort()
// Remove node adds an entry to the PersistentPartyToPublicKeyHash, so for this test delete this entry.
removeNodeFromNodeNamedIdentitiesTable(bobNode, alicePubKeyHash)
assertEquals(aliceNode.party, bobNode.services.identityService.wellKnownPartyFromX500Name(ALICE_NAME))
assertEquals(1, queryNodeNamedIdentities(bobNode, ALICE_NAME, alicePubKeyHash).size)
}
private fun removeNodeFromNodeNamedIdentitiesTable(node: TestStartedNode, publicKeyHashToRemove: String) {
// Remove node adds an entry to the PersistentPartyToPublicKeyHash, so for this test delete this entry.
node.database.transaction {
val deleteQuery = session.criteriaBuilder.createCriteriaDelete(PersistentNetworkMapCache.PersistentPartyToPublicKeyHash::class.java)
val queryRoot = deleteQuery.from(PersistentNetworkMapCache.PersistentPartyToPublicKeyHash::class.java)
deleteQuery.where(session.criteriaBuilder.equal(queryRoot.get<String>("publicKeyHash"), publicKeyHashToRemove))
session.createQuery(deleteQuery).executeUpdate()
}
}
private fun queryNodeNamedIdentities(node: TestStartedNode, party: CordaX500Name, publicKeyHash: String): List<PersistentNetworkMapCache.PersistentPartyToPublicKeyHash> {
return node.database.transaction {
val cb = session.criteriaBuilder
val query = cb.createQuery(PersistentNetworkMapCache.PersistentPartyToPublicKeyHash::class.java)
val root = query.from(PersistentNetworkMapCache.PersistentPartyToPublicKeyHash::class.java)
val matchPublicKeyHash = cb.equal(root.get<String>("publicKeyHash"), publicKeyHash)
val matchName = cb.equal(root.get<String>("name"), party.toString())
query.select(root).where(cb.and(matchName, matchPublicKeyHash))
session.createQuery(query).resultList
}
}
@Test(timeout=300_000)
fun `check removed node is inserted into node_name_identities table and then its Party object can be retrieved`() {
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
val bobNode = mockNet.createPartyNode(BOB_NAME)
val bobCache: NetworkMapCache = bobNode.services.networkMapCache
val bobCacheInternal = bobCache as NetworkMapCacheInternal
assertNotNull(bobCacheInternal)
val aliceParty1 = bobNode.services.identityService.wellKnownPartyFromX500Name(ALICE_NAME)
println("alicePart1 = $aliceParty1")
bobCache.removeNode(aliceNode.info)
val alicePubKeyHash = aliceNode.info.legalIdentities[0].owningKey.toStringShort()
assertEquals(1, queryNodeNamedIdentities(bobNode, ALICE_NAME, alicePubKeyHash).size)
assertEquals(aliceNode.party, bobNode.services.identityService.wellKnownPartyFromX500Name(ALICE_NAME))
}
@Test(timeout=300_000)
fun `check two removed nodes are both archived and then both Party objects are retrievable`() {
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
val bobNode = mockNet.createPartyNode(BOB_NAME)
val charlieNode = mockNet.createPartyNode(CHARLIE_NAME)
val bobCache: NetworkMapCache = bobNode.services.networkMapCache
val bobCacheInternal = bobCache as NetworkMapCacheInternal
assertNotNull(bobCacheInternal)
bobCache.removeNode(aliceNode.info)
bobCache.removeNode(charlieNode.info)
val alicePubKeyHash = aliceNode.info.legalIdentities[0].owningKey.toStringShort()
val charliePubKeyHash = charlieNode.info.legalIdentities[0].owningKey.toStringShort()
assertEquals(1, queryNodeNamedIdentities(bobNode, ALICE_NAME, alicePubKeyHash).size)
assertEquals(1, queryNodeNamedIdentities(bobNode, CHARLIE_NAME, charliePubKeyHash).size)
assertEquals(aliceNode.party, bobNode.services.identityService.wellKnownPartyFromX500Name(ALICE_NAME))
assertEquals(charlieNode.party, bobNode.services.identityService.wellKnownPartyFromX500Name(CHARLIE_NAME))
}
@Test(timeout=300_000)
fun `check latest identity returned according to certificate after identity mock rotatated`() {
val aliceNode1 = mockNet.createPartyNode(ALICE_NAME)
val bobNode = mockNet.createPartyNode(BOB_NAME)
val bobCache: NetworkMapCache = bobNode.services.networkMapCache
val alicePubKeyHash1 = aliceNode1.info.legalIdentities[0].owningKey.toStringShort()
val bobCacheInternal = bobCache as NetworkMapCacheInternal
assertNotNull(bobCacheInternal)
bobCache.removeNode(aliceNode1.info)
// Remove node adds an entry to the PersistentPartyToPublicKeyHash, so for this test delete this entry.
removeNodeFromNodeNamedIdentitiesTable(bobNode, alicePubKeyHash1)
val aliceNode2 = mockNet.createPartyNode(ALICE_NAME)
val alicePubKeyHash2 = aliceNode2.info.legalIdentities[0].owningKey.toStringShort()
bobCache.removeNode(aliceNode2.info)
// Remove node adds an entry to the PersistentPartyToPublicKeyHash, so for this test delete this entry.
removeNodeFromNodeNamedIdentitiesTable(bobNode, alicePubKeyHash2)
val retrievedParty = bobNode.services.identityService.wellKnownPartyFromX500Name(ALICE_NAME)
// For both identity certificates the valid from date is the start of the day, so either could be returned.
assertTrue(aliceNode2.party == retrievedParty || aliceNode1.party == retrievedParty)
}
@Test(timeout=300_000)
fun `latest identity is archived after identity rotated`() {
var aliceNode = mockNet.createPartyNode(ALICE_NAME)
val bobNode = mockNet.createPartyNode(BOB_NAME)
val bobCache: NetworkMapCache = bobNode.services.networkMapCache
val bobCacheInternal = bobCache as NetworkMapCacheInternal
assertNotNull(bobCacheInternal)
bobCache.removeNode(aliceNode.info)
fun checkArchivedIdentity(bobNode: TestStartedNode, aliceNode: TestStartedNode) {
val alicePubKeyHash = aliceNode.info.legalIdentities[0].owningKey.toStringShort()
bobNode.database.transaction {
val hashToIdentityStatement = database.dataSource.connection.prepareStatement("SELECT name, pk_hash FROM node_named_identities WHERE pk_hash=?")
hashToIdentityStatement.setString(1, alicePubKeyHash)
val aliceResultSet = hashToIdentityStatement.executeQuery()
Assert.assertTrue(aliceResultSet.next())
Assert.assertEquals(ALICE_NAME.toString(), aliceResultSet.getString("name"))
Assert.assertEquals(alicePubKeyHash.toString(), aliceResultSet.getString("pk_hash"))
Assert.assertFalse(aliceResultSet.next())
}
}
checkArchivedIdentity(bobNode, aliceNode)
aliceNode.dispose()
aliceNode = mockNet.createPartyNode(ALICE_NAME)
bobCache.removeNode(aliceNode.info)
checkArchivedIdentity(bobNode, aliceNode)
}
@Test(timeout=300_000)
fun `key collision`() {
val entropy = BigInteger.valueOf(24012017L)

View File

@ -46,14 +46,19 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.net.URL
import java.nio.charset.StandardCharsets
import java.nio.file.FileAlreadyExistsException
import java.nio.file.FileSystem
import java.nio.file.Path
import java.util.*
import java.util.jar.JarEntry
import java.util.jar.JarInputStream
import java.util.jar.JarOutputStream
import java.util.jar.Manifest
import kotlin.streams.toList
import kotlin.test.*
@ -788,6 +793,32 @@ class NodeAttachmentServiceTest {
}
}
@Test(timeout=300_000)
fun `attachments containing jar entries whose names expose malicious directory traversal are prevented`() {
fun createJarWithJarEntryTraversalAttack(jarEntryName: String): InputStream {
val byteArrayOutputStream = ByteArrayOutputStream()
JarOutputStream(byteArrayOutputStream, Manifest()).apply {
putNextEntry(JarEntry(jarEntryName))
write("some-text".toByteArray())
closeEntry()
close()
}
return ByteArrayInputStream(byteArrayOutputStream.toByteArray())
}
val traversalAttackJarWin = createJarWithJarEntryTraversalAttack("..\\attack")
val traversalAttackJarUnix = createJarWithJarEntryTraversalAttack("../attack")
assertFailsWith(IllegalArgumentException::class) {
NodeAttachmentService.checkIsAValidJAR(traversalAttackJarWin)
}
assertFailsWith(IllegalArgumentException::class) {
NodeAttachmentService.checkIsAValidJAR(traversalAttackJarUnix)
}
}
@Test(timeout=300_000)
fun `attachments can be queried by providing a intersection of signers using an EQUAL statement - EQUAL containing a single public key`() {
SelfCleaningDir().use { file ->

View File

@ -22,7 +22,7 @@ class VaultQueryExceptionsTests : VaultQueryParties by rule {
@ClassRule
@JvmField
val rule = object : VaultQueryTestRule() {
val rule = object : VaultQueryTestRule(persistentServices = false) {
override val cordappPackages = listOf(
"net.corda.testing.contracts",
"net.corda.finance.contracts",

View File

@ -4,6 +4,7 @@ import com.nhaarman.mockito_kotlin.mock
import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.packageName
@ -37,6 +38,7 @@ import net.corda.testing.internal.configureDatabase
import net.corda.testing.internal.vault.*
import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndPersistentServices
import net.corda.testing.node.makeTestIdentityService
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatCode
@ -102,7 +104,7 @@ interface VaultQueryParties {
val cordappPackages: List<String>
}
open class VaultQueryTestRule : ExternalResource(), VaultQueryParties {
open class VaultQueryTestRule(private val persistentServices: Boolean) : ExternalResource(), VaultQueryParties {
override val alice = TestIdentity(ALICE_NAME, 70)
override val bankOfCorda = TestIdentity(BOC_NAME)
override val bigCorp = TestIdentity(CordaX500Name("BigCorporation", "New York", "US"))
@ -135,12 +137,22 @@ open class VaultQueryTestRule : ExternalResource(), VaultQueryParties {
override fun before() {
// register additional identities
val databaseAndServices = makeTestDatabaseAndMockServices(
cordappPackages,
makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity),
megaCorp,
moreKeys = *arrayOf(DUMMY_NOTARY_KEY))
val databaseAndServices = if (persistentServices) {
makeTestDatabaseAndPersistentServices(
cordappPackages,
megaCorp,
moreKeys = setOf(DUMMY_NOTARY_KEY),
moreIdentities = setOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity)
)
} else {
@Suppress("SpreadOperator")
makeTestDatabaseAndMockServices(
cordappPackages,
makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity),
megaCorp,
moreKeys = *arrayOf(DUMMY_NOTARY_KEY)
)
}
database = databaseAndServices.first
services = databaseAndServices.second
vaultFiller = VaultFiller(services, dummyNotary)
@ -2832,9 +2844,8 @@ abstract class VaultQueryTestsBase : VaultQueryParties {
}
class VaultQueryTests : VaultQueryTestsBase(), VaultQueryParties by delegate {
companion object {
val delegate = VaultQueryTestRule()
val delegate = VaultQueryTestRule(persistentServices = false)
}
@Rule
@ -3137,4 +3148,34 @@ class VaultQueryTests : VaultQueryTestsBase(), VaultQueryParties by delegate {
)
}
}
}
}
class PersistentServicesVaultQueryTests : VaultQueryParties by delegate {
companion object {
val delegate = VaultQueryTestRule(persistentServices = true)
@ClassRule
@JvmField
val testSerialization = SerializationEnvironmentRule()
}
@Rule
@JvmField
val vaultQueryTestRule = delegate
@Test(timeout = 300_000)
fun `query on externalId which maps to multiple keys`() {
val externalId = UUID.randomUUID()
val page = database.transaction {
val keys = Array(2) { services.keyManagementService.freshKey(externalId) }
vaultFiller.fillWithDummyState(participants = keys.map(::AnonymousParty))
services.vaultService.queryBy<ContractState>(
VaultQueryCriteria(externalIds = listOf(externalId)),
paging = PageSpecification(DEFAULT_PAGE_NUM, 10)
)
}
assertThat(page.states).hasSize(1)
assertThat(page.totalStatesAvailable).isEqualTo(1)
}
}

View File

@ -25,6 +25,11 @@ class InfrequentlyMutatedCacheTest {
database.close()
}
@Test(timeout = 300_000)
fun `invalidate outside transaction should not hang`() {
cache.invalidate("Fred")
}
@Test(timeout=300_000)
fun `get from empty cache returns result of loader`() {
database.transaction {

View File

@ -31,6 +31,9 @@ configurations {
}
dependencies {
if (System.getProperty('excludeShell') == null) {
cordaDriver "net.corda:corda-shell:$corda_release_version"
}
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version"
compile "javax.servlet:javax.servlet-api:${servlet_version}"
@ -77,6 +80,7 @@ def webTask = tasks.getByPath(':testing:testserver:testcapsule::assemble')
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask, webTask]) {
ext.rpcUsers = [['username': "demo", 'password': "demo", 'permissions': ["StartFlow.net.corda.attachmentdemo.AttachmentDemoFlow",
"InvokeRpc.partiesFromName",
"InvokeRpc.notaryPartyFromX500Name",
"InvokeRpc.attachmentExists",
"InvokeRpc.openAttachment",
"InvokeRpc.uploadAttachment",

View File

@ -5,10 +5,13 @@ import net.corda.core.utilities.getOrThrow
import net.corda.node.services.Permissions.Companion.all
import net.corda.testing.core.DUMMY_BANK_A_NAME
import net.corda.testing.core.DUMMY_BANK_B_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.driver.internal.incrementalPortAllocation
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.User
import net.corda.testing.node.internal.DummyClusterSpec
import net.corda.testing.node.internal.findCordapp
import org.junit.Test
import java.util.concurrent.CompletableFuture.supplyAsync
@ -21,7 +24,8 @@ class AttachmentDemoTest {
driver(DriverParameters(
portAllocation = incrementalPortAllocation(),
startNodesInProcess = true,
cordappsForAllNodes = listOf(findCordapp("net.corda.attachmentdemo.contracts"), findCordapp("net.corda.attachmentdemo.workflows")))
cordappsForAllNodes = listOf(findCordapp("net.corda.attachmentdemo.contracts"), findCordapp("net.corda.attachmentdemo.workflows")),
notarySpecs = listOf(NotarySpec(name = DUMMY_NOTARY_NAME, cluster = DummyClusterSpec(clusterSize = 1))))
) {
val demoUser = listOf(User("demo", "demo", setOf(all())))
val (nodeA, nodeB) = listOf(

View File

@ -5,6 +5,7 @@ import net.corda.attachmentdemo.contracts.AttachmentContract
import net.corda.attachmentdemo.workflows.AttachmentDemoFlow
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.Emoji
import net.corda.core.internal.InputStreamAndHash
import net.corda.core.messaging.CordaRPCOps
@ -65,7 +66,7 @@ fun sender(rpc: CordaRPCOps, numOfClearBytes: Int = 1024) { // default size 1K.
private fun sender(rpc: CordaRPCOps, inputStream: InputStream, hash: SecureHash.SHA256) {
// Get the identity key of the other side (the recipient).
val notaryParty = rpc.partiesFromName("Notary", false).firstOrNull() ?: throw IllegalArgumentException("Couldn't find notary party")
val notaryParty = rpc.notaryPartyFromX500Name(CordaX500Name.parse("O=Notary Service,L=Zurich,C=CH")) ?: throw IllegalArgumentException("Couldn't find notary party")
val bankBParty = rpc.partiesFromName("Bank B", false).firstOrNull() ?: throw IllegalArgumentException("Couldn't find Bank B party")
// Make sure we have the file in storage
if (!rpc.attachmentExists(hash)) {

Some files were not shown because too many files have changed in this diff Show More