Merge branch 'release/os/4.6' into dan/4.6-into-checkpoint-feature-branch-2020-05-05

# Conflicts:
#	node/src/main/kotlin/net/corda/node/services/statemachine/Event.kt
#	node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt
#	node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt
#	node/src/main/kotlin/net/corda/node/services/statemachine/transitions/TopLevelTransition.kt
This commit is contained in:
LankyDan 2020-05-05 17:05:23 +01:00
commit 4ccd0fd3df
742 changed files with 8114 additions and 37741 deletions

View File

@ -0,0 +1,79 @@
@Library('corda-shared-build-pipeline-steps')
import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
pipeline {
agent { label 'k8s' }
options {
timestamps()
buildDiscarder(logRotator(daysToKeepStr: '7', artifactDaysToKeepStr: '7'))
timeout(time: 3, unit: 'HOURS')
}
environment {
DOCKER_TAG_TO_USE = "${env.GIT_COMMIT.subSequence(0, 8)}"
EXECUTOR_NUMBER = "${env.EXECUTOR_NUMBER}"
BUILD_ID = "${env.BUILD_ID}-${env.JOB_NAME}"
ARTIFACTORY_CREDENTIALS = credentials('artifactory-credentials')
}
stages {
stage('Corda Pull Request - Generate Build Image') {
steps {
withCredentials([string(credentialsId: 'container_reg_passwd', variable: 'DOCKER_PUSH_PWD')]) {
sh "./gradlew " +
"-Dkubenetize=true " +
"-Ddocker.push.password=\"\${DOCKER_PUSH_PWD}\" " +
"-Ddocker.work.dir=\"/tmp/\${EXECUTOR_NUMBER}\" " +
"-Ddocker.build.tag=\"\${DOCKER_TAG_TO_USE}\" " +
"-Ddocker.buildbase.tag=11latest " +
"-Ddocker.dockerfile=DockerfileJDK11Azul" +
" clean pushBuildImage --stacktrace"
}
sh "kubectl auth can-i get pods"
}
}
stage('Testing phase') {
parallel {
stage('Regression Test') {
steps {
sh "./gradlew " +
"-DbuildId=\"\${BUILD_ID}\" " +
"-Dkubenetize=true " +
"-Ddocker.run.tag=\"\${DOCKER_TAG_TO_USE}\" " +
"-Dartifactory.username=\"\${ARTIFACTORY_CREDENTIALS_USR}\" " +
"-Dartifactory.password=\"\${ARTIFACTORY_CREDENTIALS_PSW}\" " +
"-Dgit.branch=\"\${GIT_BRANCH}\" " +
"-Dgit.target.branch=\"\${GIT_BRANCH}\" " +
" parallelRegressionTest --stacktrace"
}
}
stage('Slow Integration Test') {
steps {
sh "./gradlew " +
"-DbuildId=\"\${BUILD_ID}\" " +
"-Dkubenetize=true " +
"-Ddocker.run.tag=\"\${DOCKER_TAG_TO_USE}\" " +
"-Dartifactory.username=\"\${ARTIFACTORY_CREDENTIALS_USR}\" " +
"-Dartifactory.password=\"\${ARTIFACTORY_CREDENTIALS_PSW}\" " +
"-Dgit.branch=\"\${GIT_BRANCH}\" " +
"-Dgit.target.branch=\"\${GIT_BRANCH}\" " +
" allParallelSlowIntegrationTest --stacktrace"
}
}
}
}
}
post {
always {
archiveArtifacts artifacts: '**/pod-logs/**/*.log', fingerprint: false
junit '**/build/test-results-xml/**/*.xml'
}
cleanup {
deleteDir() /* clean up our workspace */
}
}
}

View File

@ -5,7 +5,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
pipeline {
agent { label 'local-k8s' }
agent { label 'k8s' }
options {
timestamps()
timeout(time: 3, unit: 'HOURS')

View File

@ -4,7 +4,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
pipeline {
agent { label 'local-k8s' }
agent { label 'k8s' }
options {
timestamps()
overrideIndexTriggers(false)

View File

@ -3,4 +3,4 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
onDemandTestPipeline('local-k8s', '.ci/dev/on-demand-tests/commentMappings.yml')
onDemandTestPipeline('k8s', '.ci/dev/on-demand-tests/commentMappings.yml')

View File

@ -4,7 +4,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
pipeline {
agent { label 'local-k8s' }
agent { label 'k8s' }
options {
timestamps()
buildDiscarder(logRotator(daysToKeepStr: '7', artifactDaysToKeepStr: '7'))
@ -61,6 +61,36 @@ pipeline {
" allParallelSlowIntegrationTest --stacktrace"
}
}
stage('Generate sonarqube report') {
steps {
script {
try {
// running this step here is the only way to not majorly affect the distributed test plugin,
// as now that neither returns build files nor runs jacoco reports
sh "./gradlew --no-daemon build jacocoRootReport --stacktrace"
withSonarQubeEnv('sq01') {
sh "./gradlew --no-daemon sonarqube -x test --stacktrace"
}
timeout(time: 3, unit: 'MINUTES') {
script {
try {
def qg = waitForQualityGate();
if (qg.status != 'OK') {
error "Pipeline aborted due to quality gate failure: ${qg.status}"
}
} catch (org.jenkinsci.plugins.workflow.steps.FlowInterruptedException e) {
println('No sonarqube webhook response within timeout. Please check the webhook configuration in sonarqube.')
// continue the pipeline
}
}
}
} catch (err) {
println('Error while trying to execute sonarqube analysis, will be skipped.')
}
}
}
}
}
}
}

View File

@ -4,7 +4,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
pipeline {
agent { label 'local-k8s' }
agent { label 'k8s' }
options {
timestamps()
overrideIndexTriggers(false)

View File

@ -5,7 +5,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
pipeline {
agent { label 'local-k8s' }
agent { label 'k8s' }
options {
timestamps()
timeout(time: 3, unit: 'HOURS')

19
.github/workflows/jira_assign_issue.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Sync assigned jira issues
on:
schedule:
- cron: '15 * * * *'
jobs:
sync_assigned:
runs-on: ubuntu-latest
steps:
- name: Assign
uses: corda/jira-sync-assigned-action@master
with:
jiraBaseUrl: ${{ secrets.JIRA_BASE_URL }}
jiraEmail: ${{ secrets.JIRA_USER_EMAIL }}
jiraToken: ${{ secrets.JIRA_API_TOKEN }}
token: ${{ secrets.GH_TOKEN }}
owner: corda
repository: corda

20
.github/workflows/jira_close_issue.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: Sync closed jira issues
on:
schedule:
- cron: '30 * * * *'
jobs:
sync_closed:
runs-on: ubuntu-latest
steps:
- name: Close
uses: corda/jira-sync-closed-action@master
with:
project: CORDA
jiraBaseUrl: https://r3-cev.atlassian.net
jiraEmail: ${{ secrets.JIRA_USER_EMAIL }}
jiraToken: ${{ secrets.JIRA_API_TOKEN }}
token: ${{ secrets.GH_TOKEN }}
owner: corda
repository: corda

36
.github/workflows/jira_create_issue.yml vendored Normal file
View File

@ -0,0 +1,36 @@
name: Create jira issue from github issue
on:
issues:
types: [opened]
jobs:
update_jira:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Jira Create issue
id: create
uses: corda/jira-create-issue-action@master
with:
jiraBaseUrl: https://r3-cev.atlassian.net
project: CORDA
issuetype: Bug
summary: ${{ github.event.issue.title }}
labels: community
jiraEmail: ${{ secrets.JIRA_USER_EMAIL }}
jiraToken: ${{ secrets.JIRA_API_TOKEN }}
description: |
${{ github.event.issue.body }}
Created by github action.
- name: Create comment
uses: peter-evans/create-or-update-comment@v1
with:
token: ${{ secrets.GH_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Automatically created Jira issue: ${{ steps.create.outputs.issue }}
reaction-type: '+1'

2
Jenkinsfile vendored
View File

@ -5,7 +5,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
pipeline {
agent { label 'local-k8s' }
agent { label 'k8s' }
options {
timestamps()
timeout(time: 3, unit: 'HOURS')

View File

@ -2,7 +2,7 @@
<img src="https://www.corda.net/wp-content/themes/corda/assets/images/crda-logo-big.svg" alt="Corda" width="500">
</p>
<a href="https://ci-master.corda.r3cev.com/viewType.html?buildTypeId=Corda_CordaBuild&tab=buildTypeStatusDiv&guest=1"><img src="https://ci.corda.r3cev.com/app/rest/builds/buildType:Corda_CordaBuild/statusIcon"/></a> [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
<a href="https://ci-master.corda.r3cev.com/viewType.html?buildTypeId=Corda_Build_ActiveReleaseBranches_BuildOsRelease45&tab=buildTypeStatusDiv&guest=1"><img src="https://ci.corda.r3cev.com/app/rest/builds/buildType:Corda_Build_ActiveReleaseBranches_BuildOsRelease45/statusIcon"/></a> [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
# Corda

View File

@ -79,8 +79,8 @@ buildscript {
ext.djvm_version = constants.getProperty("djvmVersion")
ext.deterministic_rt_version = constants.getProperty('deterministicRtVersion')
ext.okhttp_version = '3.14.2'
ext.netty_version = '4.1.29.Final'
ext.tcnative_version = '2.0.14.Final'
ext.netty_version = '4.1.46.Final'
ext.tcnative_version = '2.0.29.Final'
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
ext.fileupload_version = '1.4'
ext.kryo_version = '4.0.2'
@ -189,6 +189,7 @@ buildscript {
classpath group: "com.r3.testing", name: "gradle-distributed-testing-plugin", version: "1.2-LOCAL-K8S-SHARED-CACHE-SNAPSHOT", changing: true
classpath group: "com.r3.dependx", name: "gradle-dependx", version: "0.1.13", changing: true
classpath "com.bmuschko:gradle-docker-plugin:5.0.0"
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8"
}
}
@ -241,6 +242,7 @@ allprojects {
apply plugin: 'jacoco'
apply plugin: 'org.owasp.dependencycheck'
apply plugin: 'kotlin-allopen'
apply plugin: 'org.sonarqube'
allOpen {
annotations(
@ -400,6 +402,16 @@ allprojects {
}
}
}
sonarqube {
properties {
property "sonar.projectName", "Corda"
property "sonar.projectKey", "corda"
property 'sonar.tests', '**/src/test/**,**/src/smoke-test/**,**/src/integration-test/**,**/src/integration-test-slow/**'
property 'sonar.coverage.jacoco.xmlReportPaths', "${rootDir.path}/build/reports/jacoco/jacocoRootReport/jacocoRootReport.xml"
property 'detekt.sonar.kotlin.baseline.path', "${rootDir.path}/detekt-baseline.xml"
property 'detekt.sonar.kotlin.config.path', "${rootDir.path}/detekt-config.yml"
}
}
// Check that we are running on a Java 8 JDK. The source/targetCompatibility values above aren't sufficient to
// guarantee this because those are properties checked by the Java plugin, but we're using Kotlin.
@ -459,6 +471,28 @@ task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) {
it.exists()
})
}
afterEvaluate {
classDirectories = files(classDirectories.files.collect {
fileTree(dir: it,
// these exclusions are necessary because jacoco gets confused by same class names
// which occur due to deterministic versions of non deterministic classes
exclude: ['**/net/corda/core/crypto/SHA256DigestSupplier**',
'**/net/corda/core/crypto/DelegatingSecureRandomService',
'**/net/corda/core/internal/ThreadLocalToggleField**',
'**/net/corda/core/internal/InheritableThreadLocalToggleField**',
'**/net/corda/core/internal/ToggleField**',
'net/corda/core/internal/rules/StateContractValidationEnforcementRule**',
'net/corda/core/internal/SimpleToggleField**',
'net/corda/core/serialization/SerializationFactory**',
'net/corda/serialization/internal/amqp/AMQPStreams**',
'net/corda/serialization/internal/amqp/AMQPSerializerFactories**',
'net/corda/serialization/internal/amqp/AMQPSerializationThreadContext**',
'net/corda/serialization/internal/ByteBufferStreams**',
'net/corda/serialization/internal/model/DefaultCacheProvider**',
'net/corda/serialization/internal/DefaultWhitelist**'
])
})
}
}
tasks.register('detekt', JavaExec) {

View File

@ -1,7 +1,6 @@
package net.corda.client.rpc
import co.paralleluniverse.fibers.Suspendable
import com.esotericsoftware.kryo.KryoException
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.core.messaging.startFlow

View File

@ -14,13 +14,13 @@ import net.corda.core.utilities.Try
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.millis
import net.corda.core.utilities.seconds
import net.corda.coretesting.internal.testThreadFactory
import net.corda.node.services.rpc.RPCServerConfiguration
import net.corda.nodeapi.RPCApi
import net.corda.testing.common.internal.eventually
import net.corda.testing.common.internal.succeeds
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.driver.internal.incrementalPortAllocation
import net.corda.coretesting.internal.testThreadFactory
import net.corda.testing.node.internal.RPCDriverDSL
import net.corda.testing.node.internal.RpcBrokerHandle
import net.corda.testing.node.internal.RpcServerHandle
@ -75,7 +75,7 @@ class RPCStabilityTests {
values.poll()
}
val first = values.peek()
if (values.size == 5 && values.all { it.keys.size == first.keys.size }) {
if (values.size == 5 && values.all { it.keys == first.keys }) {
first
} else {
null

View File

@ -1,7 +1,6 @@
package net.corda.common.configuration.parsing.internal
import com.typesafe.config.Config
import net.corda.common.validation.internal.Validated
import net.corda.common.validation.internal.Validated.Companion.invalid
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test

View File

@ -16,6 +16,8 @@ dependencies {
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
testCompile project(":test-utils")
testCompile "com.nhaarman:mockito-kotlin:$mockito_kotlin_version"
testCompile "org.mockito:mockito-core:$mockito_version"
}

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.5-SNAPSHOT"
internal const val CURRENT_MAJOR_RELEASE = "4.6-SNAPSHOT"

View File

@ -0,0 +1,31 @@
package net.corda.common.logging.errorReporting
import net.corda.common.logging.CordaVersion
import java.util.*
/**
* Provides information specific to Corda to the error reporting library.
*
* The primary use of this is to provide the URL to the docs site where the error information is hosted.
*/
class CordaErrorContextProvider : ErrorContextProvider {
companion object {
private const val BASE_URL = "https://docs.corda.net/docs"
private const val OS_PAGES = "corda-os"
private const val ENTERPRISE_PAGES = "corda-enterprise"
private const val ERROR_CODE_PAGE = "error-codes.html"
}
override fun getURL(locale: Locale): String {
val versionNumber = CordaVersion.releaseVersion
// This slightly strange block here allows the code to be merged across to Enterprise with no changes.
val productVersion = if (CordaVersion.platformEditionCode == "OS") {
OS_PAGES
} else {
ENTERPRISE_PAGES
}
return "$BASE_URL/$productVersion/$versionNumber/$ERROR_CODE_PAGE"
}
}

View File

@ -0,0 +1,28 @@
package net.corda.common.logging.errorReporting
/**
* A type representing an error condition.
*
* Error codes should be used in situations where an error is expected and information can be provided back to the user about what they've
* done wrong. Each error code should have a resource bundle defined for it, which contains set of properties that define the error string
* in different languages. See the resource bundles in common/logging/src/main/resources/errorReporting for more details.
*/
interface ErrorCode<CODES> where CODES: ErrorCodes, CODES: Enum<CODES> {
/**
* The error code.
*
* Error codes are used to indicate what sort of error occurred. A unique code should be returned for each possible
* error condition that could be reported within the defined namespace. The code should very briefly describe what has gone wrong, e.g.
* "failed-to-store" or "connection-unavailable".
*/
val code: CODES
/**
* Parameters to pass to the string template when reporting this error. The corresponding template that defines the error string in the
* resource bundle must be expecting this list of parameters. Parameters should be in the order required by the message template - for
* example, if the message template is "This error has argument {0} and argument {1}", the first element of this list will be placed
* into {0}.
*/
val parameters: List<Any>
}

View File

@ -0,0 +1,16 @@
package net.corda.common.logging.errorReporting
/**
* A collection of error codes.
*
* Types implementing this are required to be enum classes to be used in an error.
*/
interface ErrorCodes {
/**
* The namespace of this collection of errors.
*
* These are used to partition errors into categories, e.g. "database" or "cordapp". Namespaces should be unique, which can be enforced
* by using enum elements.
*/
val namespace: String
}

View File

@ -0,0 +1,24 @@
package net.corda.common.logging.errorReporting
import java.util.*
/**
* Provide context around reported errors by supplying product specific information.
*/
interface ErrorContextProvider {
/**
* Get the URL to the docs site where the error codes are hosted.
*
* Note that the correct docs site link is likely to depend on the following:
* - The locale of the error message
* - The product the error was reported from
* - The version of the product the error was reported from
*
* The returned URL must be the link the to the error code table in the documentation.
*
* @param locale The locale of the link
* @return The URL of the docs site, to be printed in the logs
*/
fun getURL(locale: Locale) : String
}

View File

@ -0,0 +1,16 @@
package net.corda.common.logging.errorReporting
import org.slf4j.Logger
/**
* Reports error conditions to the logs, using localised error messages.
*/
internal interface ErrorReporter {
/**
* Report a particular error condition
*
* @param error The error to report
* @param logger The logger to use when reporting this error
*/
fun report(error: ErrorCode<*>, logger: Logger)
}

View File

@ -0,0 +1,36 @@
package net.corda.common.logging.errorReporting
import org.slf4j.Logger
import java.text.MessageFormat
import java.util.*
internal const val ERROR_INFO_RESOURCE = "ErrorInfo"
internal const val ERROR_CODE_MESSAGE = "errorCodeMessage"
internal const val ERROR_CODE_URL = "errorCodeUrl"
internal class ErrorReporterImpl(private val resourceLocation: String,
private val locale: Locale,
private val errorContextProvider: ErrorContextProvider) : ErrorReporter {
private fun fetchAndFormat(resource: String, property: String, params: Array<out Any>) : String {
val bundle = ResourceBundle.getBundle(resource, locale)
val template = bundle.getString(property)
val formatter = MessageFormat(template, locale)
return formatter.format(params)
}
// Returns the string appended to all reported errors, indicating the error code and the URL to go to.
// e.g. [Code: my-error-code, For further information, please go to https://docs.corda.net/corda-os/4.5/error-codes.html]
private fun getErrorInfo(error: ErrorCode<*>) : String {
val resource = "$resourceLocation/$ERROR_INFO_RESOURCE"
val codeMessage = fetchAndFormat(resource, ERROR_CODE_MESSAGE, arrayOf(error.formatCode()))
val urlMessage = fetchAndFormat(resource, ERROR_CODE_URL, arrayOf(errorContextProvider.getURL(locale)))
return "[$codeMessage, $urlMessage]"
}
override fun report(error: ErrorCode<*>, logger: Logger) {
val errorResource = ErrorResource.fromErrorCode(error, resourceLocation, locale)
val message = "${errorResource.getErrorMessage(error.parameters.toTypedArray())} ${getErrorInfo(error)}"
logger.error(message)
}
}

View File

@ -0,0 +1,70 @@
package net.corda.common.logging.errorReporting
import java.util.*
/**
* Entry point into the Error Reporting framework.
*
* This creates the error reporter used to report errors. The `initialiseReporting` method should be called to build a reporter before any
* errors are reported.
*/
class ErrorReporting private constructor(private val localeString: String,
private val resourceLocation: String,
private val contextProvider: ErrorContextProvider?) {
constructor() : this(DEFAULT_LOCALE, DEFAULT_LOCATION, null)
private companion object {
private const val DEFAULT_LOCALE = "en-US"
private const val DEFAULT_LOCATION = "."
private var errorReporter: ErrorReporter? = null
}
/**
* Set the locale to use when reporting errors
*
* @param locale The locale tag to use when reporting errors, e.g. en-US
*/
@Suppress("UNUSED_PARAMETER")
fun withLocale(locale: String) : ErrorReporting {
// Currently, if anything other than the default is used this is entirely untested. As a result, an exception is thrown here to
// indicate that this functionality is not ready at this point in time.
throw LocaleSettingUnsupportedException()
}
/**
* Set the location of the resource bundles containing the error codes.
*
* @param location The location within the JAR of the resource bundle
*/
fun usingResourcesAt(location: String) : ErrorReporting {
return ErrorReporting(localeString, location, contextProvider)
}
/**
* Set the context provider to supply project-specific information about the errors.
*
* @param contextProvider The context provider to use with error reporting
*/
fun withContextProvider(contextProvider: ErrorContextProvider) : ErrorReporting {
return ErrorReporting(localeString, resourceLocation, contextProvider)
}
/**
* Set up the reporting of errors.
*/
fun initialiseReporting() {
if (contextProvider == null) {
throw NoContextProviderSuppliedException()
}
if (errorReporter != null) {
throw DoubleInitializationException()
}
errorReporter = ErrorReporterImpl(resourceLocation, Locale.forLanguageTag(localeString), contextProvider)
}
internal fun getReporter() : ErrorReporter {
return errorReporter ?: throw ReportingUninitializedException()
}
}

View File

@ -0,0 +1,27 @@
package net.corda.common.logging.errorReporting
abstract class ErrorReportingException(message: String, cause: Throwable? = null) : Exception(message, cause)
/**
* Occurs when reporting is requested before the error reporting code has been initialized
*/
class ReportingUninitializedException : ErrorReportingException("Error reporting is uninitialized")
/**
* Occurs when no error context provider is supplied while initializing error reporting
*/
class NoContextProviderSuppliedException
: ErrorReportingException("No error context provider was supplied when initializing error reporting")
/**
* Occurs if the error reporting framework has been initialized twice
*/
class DoubleInitializationException : ErrorReportingException("Error reporting has previously been initialized")
/**
* Occurs if a locale is set while initializing the error reporting framework.
*
* This is done as locale support has not yet been properly designed, and so using anything other than the default is untested.
*/
class LocaleSettingUnsupportedException :
ErrorReportingException("Setting a locale other than the default is not supported in the first release")

View File

@ -0,0 +1,18 @@
package net.corda.common.logging.errorReporting
import org.slf4j.Logger
/**
* Report errors that have occurred.
*
* Doing this allows the error reporting framework to find the corresponding resources for the error and pick the correct locale.
*
* @param error The error that has occurred.
*/
fun Logger.report(error: ErrorCode<*>) = ErrorReporting().getReporter().report(error, this)
internal fun ErrorCode<*>.formatCode() : String {
val namespaceString = this.code.namespace.toLowerCase().replace("_", "-")
val codeString = this.code.toString().toLowerCase().replace("_", "-")
return "$namespaceString-$codeString"
}

View File

@ -0,0 +1,60 @@
package net.corda.common.logging.errorReporting
import net.corda.common.logging.errorReporting.ResourceBundleProperties.ACTIONS_TO_FIX
import net.corda.common.logging.errorReporting.ResourceBundleProperties.ALIASES
import net.corda.common.logging.errorReporting.ResourceBundleProperties.MESSAGE_TEMPLATE
import net.corda.common.logging.errorReporting.ResourceBundleProperties.SHORT_DESCRIPTION
import java.text.MessageFormat
import java.util.*
/**
* A representation of a single error resource file.
*
* This handles selecting the right properties from the resource bundle and formatting the error message.
*/
class ErrorResource private constructor(private val bundle: ResourceBundle,
private val locale: Locale) {
companion object {
/**
* Construct an error resource from a provided code.
*
* @param errorCode The code to get the resource bundle for
* @param resourceLocation The location in the JAR of the error code resource bundles
* @param locale The locale to use for this resource
*/
fun fromErrorCode(errorCode: ErrorCode<*>, resourceLocation: String, locale: Locale) : ErrorResource {
val resource = "$resourceLocation/${errorCode.formatCode()}"
val bundle = ResourceBundle.getBundle(resource, locale)
return ErrorResource(bundle, locale)
}
/**
* Construct an error resource using resources loaded in a given classloader
*
* @param resource The resource bundle to load
* @param classLoader The classloader used to load the resource bundles
* @param locale The locale to use for this resource
*/
fun fromLoader(resource: String, classLoader: ClassLoader, locale: Locale) : ErrorResource {
val bundle = ResourceBundle.getBundle(resource, locale, classLoader)
return ErrorResource(bundle, locale)
}
}
private fun getProperty(propertyName: String) : String = bundle.getString(propertyName)
private fun formatTemplate(template: String, args: Array<Any>) : String {
val formatter = MessageFormat(template, locale)
return formatter.format(args)
}
fun getErrorMessage(args: Array<Any>): String {
val template = getProperty(MESSAGE_TEMPLATE)
return formatTemplate(template, args)
}
val shortDescription: String = getProperty(SHORT_DESCRIPTION)
val actionsToFix: String = getProperty(ACTIONS_TO_FIX)
val aliases: String = getProperty(ALIASES)
}

View File

@ -0,0 +1,33 @@
package net.corda.common.logging.errorReporting
/**
* Namespaces for errors within the node.
*/
enum class NodeNamespaces {
DATABASE,
CORDAPP
}
/**
* Errors related to database connectivity
*/
enum class NodeDatabaseErrors : ErrorCodes {
COULD_NOT_CONNECT,
MISSING_DRIVER,
FAILED_STARTUP,
PASSWORD_REQUIRED_FOR_H2;
override val namespace = NodeNamespaces.DATABASE.toString()
}
/**
* Errors related to loading of Cordapps
*/
enum class CordappErrors : ErrorCodes {
DUPLICATE_CORDAPPS_INSTALLED,
MULTIPLE_CORDAPPS_FOR_FLOW,
MISSING_VERSION_ATTRIBUTE,
INVALID_VERSION_IDENTIFIER;
override val namespace = NodeNamespaces.CORDAPP.toString()
}

View File

@ -0,0 +1,11 @@
package net.corda.common.logging.errorReporting
/**
* Constants defining the properties available in the resource bundle files.
*/
object ResourceBundleProperties {
const val MESSAGE_TEMPLATE = "errorTemplate"
const val SHORT_DESCRIPTION = "shortDescription"
const val ACTIONS_TO_FIX = "actionsToFix"
const val ALIASES = "aliases"
}

View File

@ -0,0 +1,2 @@
errorCodeMessage = Error Code: {0}
errorCodeUrl = For further information, please go to {0}

View File

@ -0,0 +1,2 @@
errorCodeMessage = Error Code: {0}
errorCodeUrl = For further information, please go to {0}

View File

@ -0,0 +1,4 @@
errorTemplate = The CorDapp (name: {0}, file: {1}) is installed multiple times on the node. The following files correspond to the exact same content: {2}
shortDescription = A CorDapp has been installed multiple times on the same node.
actionsToFix = Investigate the logs to determine the files with duplicate content, and remove one of them from the cordapps directory.
aliases = iw8d4e

View File

@ -0,0 +1,3 @@
errorTemplate = The CorDapp (name: {0}, file: {1}) is installed multiple times on the node. The following files correspond to the exact same content: {2}
shortDescription = A CorDapp has been installed multiple times on the same node.
actionsToFix = Investigate the logs to determine the files with duplicate content, and remove one of them from the cordapps directory.

View File

@ -0,0 +1,4 @@
errorTemplate = Version identifier ({0}) for attribute {1} must be a whole number starting from 1.
shortDescription = A version attribute was specified in the CorDapp manifest with an invalid value. The value must be a whole number, and it must be greater than or equal to 1.
actionsToFix = Investigate the logs to find the invalid attribute, and change the attribute value to be valid (a whole number greater than or equal to 1).
aliases =

View File

@ -0,0 +1,4 @@
errorTemplate = Version identifier ({0}) for attribute {1} must be a whole number starting from 1.
shortDescription = A version attribute was specified in the CorDapp manifest with an invalid value. The value must be a whole number, and it must be greater than or equal to 1.
actionsToFix = Investigate the logs to find the invalid attribute, and change the attribute value to be valid (a whole number greater than or equal to 1).
aliases =

View File

@ -0,0 +1,4 @@
errorTemplate = Target versionId attribute {0} not specified. Please specify a whole number starting from 1.
shortDescription = A required version attribute was not specified in the manifest of the CorDapp JAR.
actionsToFix = Investigate the logs to find out which version attribute has not been specified, and add that version attribute to the CorDapp manifest.
aliases =

View File

@ -0,0 +1,3 @@
errorTemplate = Target versionId attribute {0} not specified. Please specify a whole number starting from 1.
shortDescription = A required version attribute was not specified in the manifest of the CorDapp JAR.
actionsToFix = Investigate the logs to find out which version attribute has not been specified, and add that version attribute to the CorDapp manifest.

View File

@ -0,0 +1,4 @@
errorTemplate = There are multiple CorDapp JARs on the classpath for the flow {0}: [{1}]
shortDescription = Multiple CorDapp JARs on the classpath define the same flow class. As a result, the platform will not know which version of the flow to start when the flow is invoked.
actionsToFix = Investigate the logs to find out which CorDapp JARs define the same flow classes. The developers of these apps will need to resolve the clash.
aliases =

View File

@ -0,0 +1,4 @@
errorTemplate = There are multiple CorDapp JARs on the classpath for the flow {0}: [{1}]
shortDescription = Multiple CorDapp JARs on the classpath define the same flow class. As a result, the platform will not know which version of the flow to start when the flow is invoked.
actionsToFix = Investigate the logs to find out which CorDapp JARs define the same flow classes. The developers of these apps will need to resolve the clash.
aliases =

View File

@ -0,0 +1,4 @@
errorTemplate = Could not connect to the database. Please check your JDBC connection URL, or the connectivity to the database.
shortDescription = The node failed to connect to the database on node startup, preventing the node from starting correctly.
actionsToFix = This happens either because the database connection has been misconfigured or the database is unreachable. Check that the JDBC URL is configured correctly in your node.conf. If this is correctly configured, then check your database connection.
aliases =

View File

@ -0,0 +1,3 @@
errorTemplate = Could not connect to the database. Please check your JDBC connection URL, or the connectivity to the database.
shortDescription = The node failed to connect to the database on node startup, preventing the node from starting correctly.
actionsToFix = This happens either because the database connection has been misconfigured or the database is unreachable. Check that the JDBC URL is configured correctly in your node.conf. If this is correctly configured, then check your database connection.

View File

@ -0,0 +1,4 @@
errorTemplate = Failed to create the datasource. See the logs for further information and the cause.
shortDescription = The datasource could not be created for unknown reasons.
actionsToFix = The logs in the logs directory should contain more information on what went wrong.
aliases =

View File

@ -0,0 +1,3 @@
errorTemplate = Failed to create the datasource. See the logs for further information and the cause.
shortDescription = The datasource could not be created for unknown reasons.
actionsToFix = The logs in the logs directory should contain more information on what went wrong.

View File

@ -0,0 +1,4 @@
errorTemplate = Could not find the database driver class. Please add it to the 'drivers' folder.
shortDescription = The node could not find the driver in the 'drivers' directory.
actionsToFix = Please ensure that the correct database driver has been placed in the 'drivers' folder. The driver must contain the driver main class specified in 'node.conf'.
aliases =

View File

@ -0,0 +1,3 @@
errorTemplate = Could not find the database driver class. Please add it to the 'drivers' folder.
shortDescription = The node could not find the driver in the 'drivers' directory.
actionsToFix = Please ensure that the correct database driver has been placed in the 'drivers' folder. The driver must contain the driver main class specified in 'node.conf'.

View File

@ -0,0 +1,4 @@
errorTemplate = Database password is required for H2 server listening on {0}
shortDescription = A password is required to access the H2 server the node is trying to access, and this password is missing.
actionsToFix = Add the required password to the 'datasource.password' configuration in 'node.conf'.
aliases =

View File

@ -0,0 +1,3 @@
errorTemplate = Database password is required for H2 server listening on {0}
shortDescription = A password is required to access the H2 server the node is trying to access, and this password is missing.
actionsToFix = Add the required password to the 'datasource.password' configuration in 'node.conf'.

View File

@ -0,0 +1,20 @@
package net.corda.commmon.logging.errorReporting
import net.corda.common.logging.CordaVersion
import net.corda.common.logging.errorReporting.CordaErrorContextProvider
import org.junit.Test
import java.util.*
import kotlin.test.assertEquals
class CordaErrorContextProviderTest {
@Test(timeout = 300_000)
fun `check that correct URL is returned from context provider`() {
val context = CordaErrorContextProvider()
val expectedURL = "https://docs.corda.net/docs/corda-os/${CordaVersion.releaseVersion}/error-codes.html"
// In this first release, there is only one localisation and the URL structure for future localisations is currently unknown. As
// a result, the same URL is expected for all locales.
assertEquals(expectedURL, context.getURL(Locale.getDefault()))
assertEquals(expectedURL, context.getURL(Locale.forLanguageTag("es-ES")))
}
}

View File

@ -0,0 +1,12 @@
package net.corda.commmon.logging.errorReporting
import net.corda.common.logging.errorReporting.CordappErrors
class CordappErrorsTest : ErrorCodeTest<CordappErrors>(CordappErrors::class.java, true) {
override val dataForCodes = mapOf(
CordappErrors.MISSING_VERSION_ATTRIBUTE to listOf("test-attribute"),
CordappErrors.INVALID_VERSION_IDENTIFIER to listOf(-1, "test-attribute"),
CordappErrors.MULTIPLE_CORDAPPS_FOR_FLOW to listOf("MyTestFlow", "Jar 1, Jar 2"),
CordappErrors.DUPLICATE_CORDAPPS_INSTALLED to listOf("TestCordapp", "testapp.jar", "testapp2.jar")
)
}

View File

@ -0,0 +1,13 @@
package net.corda.commmon.logging.errorReporting
import net.corda.common.logging.errorReporting.NodeDatabaseErrors
import java.net.InetAddress
class DatabaseErrorsTest : ErrorCodeTest<NodeDatabaseErrors>(NodeDatabaseErrors::class.java) {
override val dataForCodes = mapOf(
NodeDatabaseErrors.COULD_NOT_CONNECT to listOf<Any>(),
NodeDatabaseErrors.FAILED_STARTUP to listOf(),
NodeDatabaseErrors.MISSING_DRIVER to listOf(),
NodeDatabaseErrors.PASSWORD_REQUIRED_FOR_H2 to listOf(InetAddress.getLocalHost())
)
}

View File

@ -0,0 +1,61 @@
package net.corda.commmon.logging.errorReporting
import junit.framework.TestCase.assertFalse
import net.corda.common.logging.errorReporting.ErrorCode
import net.corda.common.logging.errorReporting.ErrorCodes
import net.corda.common.logging.errorReporting.ErrorResource
import net.corda.common.logging.errorReporting.ResourceBundleProperties
import org.junit.Test
import java.util.*
import kotlin.test.assertTrue
/**
* Utility for testing that error code resource files behave as expected.
*
* This allows for testing that error messages are printed correctly if they are provided the correct parameters. The test will fail if any
* of the parameters of the template are not filled in.
*
* To use, override the `dataForCodes` with a map from an error code enum value to a list of parameters the message template takes. If any
* are missed, the test will fail.
*
* `printProperties`, if set to true, will print the properties out the resource files, with the error message filled in. This allows the
* message to be inspected.
*/
abstract class ErrorCodeTest<T>(private val clazz: Class<T>,
private val printProperties: Boolean = false) where T: Enum<T>, T: ErrorCodes {
abstract val dataForCodes: Map<T, List<Any>>
private class TestError<T>(override val code: T,
override val parameters: List<Any>) : ErrorCode<T> where T: Enum<T>, T: ErrorCodes
@Test(timeout = 300_000)
fun `test error codes`() {
for ((code, params) in dataForCodes) {
val error = TestError(code, params)
val resource = ErrorResource.fromErrorCode(error, "error-codes", Locale.forLanguageTag("en-US"))
val message = resource.getErrorMessage(error.parameters.toTypedArray())
assertFalse(
"The error message reported for code $code contains missing parameters",
message.contains("\\{.*}".toRegex())
)
val otherProperties = Triple(resource.shortDescription, resource.actionsToFix, resource.aliases)
if (printProperties) {
println("Data for $code")
println("Error Message = $message")
println("${ResourceBundleProperties.SHORT_DESCRIPTION} = ${otherProperties.first}")
println("${ResourceBundleProperties.ACTIONS_TO_FIX} = ${otherProperties.second}")
println("${ResourceBundleProperties.ALIASES} = ${otherProperties.third}")
println("")
}
}
}
@Test(timeout = 300_000)
fun `ensure all error codes tested`() {
val expected = clazz.enumConstants.toSet()
val actual = dataForCodes.keys.toSet()
val missing = expected - actual
assertTrue(missing.isEmpty(), "The following codes have not been tested: $missing")
}
}

View File

@ -0,0 +1,121 @@
package net.corda.commmon.logging.errorReporting
import junit.framework.TestCase.assertEquals
import net.corda.common.logging.errorReporting.ErrorCode
import net.corda.common.logging.errorReporting.ErrorCodes
import net.corda.common.logging.errorReporting.ErrorContextProvider
import net.corda.common.logging.errorReporting.ErrorReporterImpl
import org.junit.After
import org.junit.Test
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mockito
import org.slf4j.Logger
import java.text.DateFormat
import java.time.Instant
import java.util.*
class ErrorReporterImplTest {
companion object {
private const val TEST_URL = "test-url"
}
private val logs: MutableList<Any> = mutableListOf()
private val loggerMock = Mockito.mock(Logger::class.java).also {
Mockito.`when`(it.error(anyString())).then { logs.addAll(it.arguments) }
}
private val contextProvider: ErrorContextProvider = object : ErrorContextProvider {
override fun getURL(locale: Locale): String {
return "$TEST_URL/${locale.toLanguageTag()}"
}
}
private enum class TestNamespaces {
TEST
}
private enum class TestErrors : ErrorCodes {
CASE1,
CASE2,
CASE_3;
override val namespace = TestNamespaces.TEST.toString()
}
private val TEST_ERROR_1 = object : ErrorCode<TestErrors> {
override val code = TestErrors.CASE1
override val parameters = listOf<Any>()
}
private class TestError2(currentDate: Date) : ErrorCode<TestErrors> {
override val code = TestErrors.CASE2
override val parameters: List<Any> = listOf("foo", 1, currentDate)
}
private val TEST_ERROR_3 = object : ErrorCode<TestErrors> {
override val code = TestErrors.CASE_3
override val parameters = listOf<Any>()
}
private fun createReporterImpl(localeTag: String?) : ErrorReporterImpl {
val locale = if (localeTag != null) Locale.forLanguageTag(localeTag) else Locale.getDefault()
return ErrorReporterImpl("errorReporting", locale, contextProvider)
}
@After
fun tearDown() {
logs.clear()
}
@Test(timeout = 300_000)
fun `error codes logged correctly`() {
val error = TEST_ERROR_1
val testReporter = createReporterImpl("en-US")
testReporter.report(error, loggerMock)
assertEquals(listOf("This is a test message [Code: test-case1, URL: $TEST_URL/en-US]"), logs)
}
@Test(timeout = 300_00)
fun `error code with parameters correctly reported`() {
val currentDate = Date.from(Instant.now())
val error = TestError2(currentDate)
val testReporter = createReporterImpl("en-US")
testReporter.report(error, loggerMock)
val format = DateFormat.getDateInstance(DateFormat.LONG, Locale.forLanguageTag("en-US"))
assertEquals(listOf("This is the second case with string foo, number 1, date ${format.format(currentDate)} [Code: test-case2, URL: $TEST_URL/en-US]"), logs)
}
@Test(timeout = 300_000)
fun `locale used with no corresponding resource falls back to default`() {
val error = TEST_ERROR_1
val testReporter = createReporterImpl("fr-FR")
testReporter.report(error, loggerMock)
assertEquals(listOf("This is a test message [Code: test-case1, URL: $TEST_URL/fr-FR]"), logs)
}
@Test(timeout = 300_000)
fun `locale with corresponding resource causes correct error to be printed`() {
val error = TEST_ERROR_1
val testReporter = createReporterImpl("ga-IE")
testReporter.report(error, loggerMock)
assertEquals(listOf("Is teachtaireacht earráide é seo [Code: test-case1, URL: $TEST_URL/ga-IE]"), logs)
}
@Test(timeout = 300_000)
fun `locale with missing properties falls back to default properties`() {
val error = TEST_ERROR_1
val testReporter = createReporterImpl("es-ES")
testReporter.report(error, loggerMock)
assertEquals(listOf("This is a test message [Code: test-case1, URL: $TEST_URL/es-ES]"), logs)
}
@Test(timeout = 300_000)
fun `error code with underscore in translated to resource file successfully`() {
val error = TEST_ERROR_3
val testReporter = createReporterImpl("en-US")
testReporter.report(error, loggerMock)
assertEquals(listOf("This is the third test message [Code: test-case-3, URL: $TEST_URL/en-US]"), logs)
}
}

View File

@ -0,0 +1,2 @@
errorCodeMessage = Code: {0}
errorCodeUrl = URL: {0}

View File

@ -0,0 +1,2 @@
errorCodeMessage = Code: {0}
errorCodeUrl = URL: {0}

View File

@ -0,0 +1,4 @@
errorTemplate = This is the third test message
shortDescription = Test description
actionsToFix = Actions
aliases =

View File

@ -0,0 +1,4 @@
errorTemplate = This is the third test message
shortDescription = Test description
actionsToFix = Actions
aliases =

View File

@ -0,0 +1,4 @@
errorTemplate = This is a test message
shortDescription = Test description
actionsToFix = Actions
aliases = foo, bar

View File

@ -0,0 +1,3 @@
errorTemplate = This is a test message
shortDescription = Test description
actionsToFix = Actions

View File

@ -0,0 +1 @@
shortDescription = Descripción de la prueba

View File

@ -0,0 +1,3 @@
errorTemplate = Is teachtaireacht earráide é seo
shortDescription = Teachtaireacht tástála
actionsToFix = Roinnt gníomhartha

View File

@ -0,0 +1,4 @@
errorTemplate = This is the second case with string {0}, number {1, number, integer}, date {2, date, long}
shortDescription = Test description
actionsToFix = Actions
aliases = ""

View File

@ -0,0 +1,4 @@
errorTemplate = This is the second case with string {0}, number {1, number, integer}, date {2, date, long}
shortDescription = Test description
actionsToFix = Actions
aliases =

View File

@ -0,0 +1,4 @@
errorTemplate = Seo an dara cás le sreang {0}, uimhir {1, uimhir, slánuimhir}, dáta {2, dáta, fada}
shortDescription = An dara tuairisc
actionsToFix = Roinnt gníomhartha eile
aliases =

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.5
cordaVersion=4.6
versionSuffix=SNAPSHOT
gradlePluginsVersion=5.0.8
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=6
platformVersion=7
guavaVersion=28.0-jre
# Quasar version to use with Java 8:
quasarVersion=0.7.12_r3
@ -21,7 +21,7 @@ quasarVersion11=0.8.0_r3
jdkClassifier11=jdk11
proguardVersion=6.1.1
bouncycastleVersion=1.60
classgraphVersion=4.8.58
classgraphVersion=4.8.71
disruptorVersion=3.4.2
typesafeConfigVersion=1.3.4
jsr305Version=3.0.2
@ -30,7 +30,7 @@ snakeYamlVersion=1.19
caffeineVersion=2.7.0
metricsVersion=4.1.0
metricsNewRelicVersion=1.1.1
djvmVersion=1.0
djvmVersion=1.1-RC02
deterministicRtVersion=1.0-RC02
openSourceBranch=https://github.com/corda/corda/blob/release/os/4.4
openSourceSamplesBranch=https://github.com/corda/samples/blob/release-V4

View File

@ -3,6 +3,7 @@ package net.corda.core.internal
/**
* Stubbing out non-deterministic method.
*/
fun <T: Any> createInstancesOfClassesImplementing(@Suppress("UNUSED_PARAMETER") classloader: ClassLoader, @Suppress("UNUSED_PARAMETER") clazz: Class<T>): Set<T> {
fun <T: Any> createInstancesOfClassesImplementing(@Suppress("UNUSED_PARAMETER") classloader: ClassLoader, @Suppress("UNUSED_PARAMETER") clazz: Class<T>,
@Suppress("UNUSED_PARAMETER") classVersionRange: IntRange? = null): Set<T> {
return emptySet()
}

View File

@ -324,4 +324,17 @@ class TransactionVerificationExceptionSerialisationTests {
assertEquals(exception.cause?.message, exception2.cause?.message)
assertEquals(exception.txId, exception2.txId)
}
@Test(timeout=300_000)
fun unsupportedClassVersionErrorTest() {
val cause = UnsupportedClassVersionError("wobble")
val exception = TransactionVerificationException.UnsupportedClassVersionError(txid, cause.message!!, cause)
val exception2 = DeserializationInput(factory).deserialize(
SerializationOutput(factory).serialize(exception, context),
context)
assertEquals(exception.message, exception2.message)
assertEquals("java.lang.UnsupportedClassVersionError: ${exception.cause?.message}", exception2.cause?.message)
assertEquals(exception.txId, exception2.txId)
}
}

View File

@ -11,7 +11,6 @@ import net.corda.core.node.services.queryBy
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.minutes
import net.corda.node.services.statemachine.StaffedFlowHospital
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyState
import net.corda.testing.core.ALICE_NAME
@ -22,7 +21,6 @@ import net.corda.testing.driver.driver
import net.corda.testing.node.internal.cordappsForPackages
import org.junit.Test
import java.sql.SQLTransientConnectionException
import java.util.concurrent.Semaphore
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue

View File

@ -0,0 +1,364 @@
package net.corda.coretests.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.KilledFlowException
import net.corda.core.flows.StartableByRPC
import net.corda.core.flows.StateMachineRunId
import net.corda.core.flows.UnexpectedFlowEndException
import net.corda.core.identity.Party
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.minutes
import net.corda.core.utilities.seconds
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 org.apache.logging.log4j.Level
import org.apache.logging.log4j.core.config.Configurator
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.Before
import org.junit.Test
import java.util.concurrent.Semaphore
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class FlowIsKilledTest {
private companion object {
const val EXCEPTION_MESSAGE = "Goodbye, cruel world!"
}
@Before
fun setup() {
Configurator.setLevel("net.corda.node.services.statemachine", Level.DEBUG)
}
@Test(timeout = 300_000)
fun `manually handle the isKilled check`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
alice.rpc.let { rpc ->
val handle = rpc.startFlow(::AFlowThatWantsToDie)
AFlowThatWantsToDie.lockA.acquire()
rpc.killFlow(handle.id)
AFlowThatWantsToDie.lockB.release()
assertThatExceptionOfType(KilledFlowException::class.java)
.isThrownBy { handle.returnValue.getOrThrow(1.minutes) }
.withMessage(EXCEPTION_MESSAGE)
assertEquals(11, AFlowThatWantsToDie.position)
val checkpoints = rpc.startFlow(::GetNumberOfCheckpointsFlow).returnValue.getOrThrow(20.seconds)
assertEquals(1, checkpoints)
}
}
}
@Test(timeout = 300_000)
fun `manually handled killed flows propagate error to counter parties`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
val charlie = startNode(providedName = CHARLIE_NAME).getOrThrow()
alice.rpc.let { rpc ->
val handle = rpc.startFlow(
::AFlowThatWantsToDieAndKillsItsFriends,
listOf(bob.nodeInfo.singleIdentity(), charlie.nodeInfo.singleIdentity())
)
AFlowThatWantsToDieAndKillsItsFriends.lockA.acquire()
AFlowThatWantsToDieAndKillsItsFriendsResponder.locks.forEach { it.value.acquire() }
rpc.killFlow(handle.id)
AFlowThatWantsToDieAndKillsItsFriends.lockB.release()
assertThatExceptionOfType(KilledFlowException::class.java)
.isThrownBy { handle.returnValue.getOrThrow(1.minutes) }
.withMessage(EXCEPTION_MESSAGE)
AFlowThatWantsToDieAndKillsItsFriendsResponder.locks.forEach { it.value.acquire() }
assertEquals(11, AFlowThatWantsToDieAndKillsItsFriends.position)
assertTrue(AFlowThatWantsToDieAndKillsItsFriendsResponder.receivedKilledExceptions[BOB_NAME]!!)
assertTrue(AFlowThatWantsToDieAndKillsItsFriendsResponder.receivedKilledExceptions[CHARLIE_NAME]!!)
val aliceCheckpoints = alice.rpc.startFlow(::GetNumberOfCheckpointsFlow).returnValue.getOrThrow(20.seconds)
assertEquals(1, aliceCheckpoints)
val bobCheckpoints = bob.rpc.startFlow(::GetNumberOfCheckpointsFlow).returnValue.getOrThrow(20.seconds)
assertEquals(1, bobCheckpoints)
}
}
}
@Test(timeout = 300_000)
fun `a manually killed initiated flow will propagate the killed error to the initiator and its counter parties`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
val handle = alice.rpc.startFlow(
::AFlowThatGetsMurderedByItsFriend,
bob.nodeInfo.singleIdentity()
)
AFlowThatGetsMurderedByItsFriendResponder.lockA.acquire()
val initiatedFlowId = AFlowThatGetsMurderedByItsFriendResponder.flowId!!
bob.rpc.killFlow(initiatedFlowId)
AFlowThatGetsMurderedByItsFriendResponder.lockB.release()
assertFailsWith<UnexpectedFlowEndException> {
handle.returnValue.getOrThrow(1.minutes)
}
assertTrue(AFlowThatGetsMurderedByItsFriend.receivedKilledException)
assertEquals(11, AFlowThatGetsMurderedByItsFriendResponder.position)
val aliceCheckpoints = alice.rpc.startFlow(::GetNumberOfCheckpointsFlow).returnValue.getOrThrow(20.seconds)
assertEquals(1, aliceCheckpoints)
val bobCheckpoints = bob.rpc.startFlow(::GetNumberOfCheckpointsFlow).returnValue.getOrThrow(20.seconds)
assertEquals(1, bobCheckpoints)
}
}
@Test(timeout = 300_000)
fun `manually handle killed flows using checkForIsNotKilled`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
alice.rpc.let { rpc ->
val handle = rpc.startFlow(::AFlowThatChecksIfItWantsToDie)
AFlowThatChecksIfItWantsToDie.lockA.acquire()
rpc.killFlow(handle.id)
AFlowThatChecksIfItWantsToDie.lockB.release()
assertThatExceptionOfType(KilledFlowException::class.java)
.isThrownBy { handle.returnValue.getOrThrow(1.minutes) }
.withMessageNotContaining(EXCEPTION_MESSAGE)
assertEquals(11, AFlowThatChecksIfItWantsToDie.position)
val checkpoints = rpc.startFlow(::GetNumberOfCheckpointsFlow).returnValue.getOrThrow(20.seconds)
assertEquals(1, checkpoints)
}
}
}
@Test(timeout = 300_000)
fun `manually handle killed flows using checkForIsNotKilled with lazy message`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
alice.rpc.let { rpc ->
val handle = rpc.startFlow(::AFlowThatChecksIfItWantsToDieAndLeavesANote)
AFlowThatChecksIfItWantsToDieAndLeavesANote.lockA.acquire()
rpc.killFlow(handle.id)
AFlowThatChecksIfItWantsToDieAndLeavesANote.lockB.release()
assertThatExceptionOfType(KilledFlowException::class.java)
.isThrownBy { handle.returnValue.getOrThrow(1.minutes) }
.withMessage(EXCEPTION_MESSAGE)
assertEquals(11, AFlowThatChecksIfItWantsToDieAndLeavesANote.position)
val checkpoints = rpc.startFlow(::GetNumberOfCheckpointsFlow).returnValue.getOrThrow(20.seconds)
assertEquals(1, checkpoints)
}
}
}
@StartableByRPC
class AFlowThatWantsToDie : FlowLogic<Unit>() {
companion object {
val lockA = Semaphore(0)
val lockB = Semaphore(0)
var position = 0
}
@Suspendable
override fun call() {
for (i in 0..100) {
position = i
logger.info("i = $i")
if (isKilled) {
throw KilledFlowException(runId, EXCEPTION_MESSAGE)
}
if (i == 10) {
lockA.release()
lockB.acquire()
}
}
}
}
@StartableByRPC
@InitiatingFlow
class AFlowThatWantsToDieAndKillsItsFriends(private val parties: List<Party>) : FlowLogic<Unit>() {
companion object {
val lockA = Semaphore(0)
val lockB = Semaphore(0)
var position = 0
}
@Suspendable
override fun call() {
val sessionOne = initiateFlow(parties[0])
val sessionTwo = initiateFlow(parties[1])
// trigger sessions with 2 counter parties
sessionOne.sendAndReceive<String>("what is up")
sessionOne.send("what is up 2")
sessionTwo.sendAndReceive<String>("what is up")
sessionTwo.send("what is up 2")
for (i in 0..100) {
position = i
logger.info("i = $i")
if (isKilled) {
throw KilledFlowException(runId, EXCEPTION_MESSAGE)
}
if (i == 10) {
lockA.release()
lockB.acquire()
}
}
}
}
@InitiatedBy(AFlowThatWantsToDieAndKillsItsFriends::class)
class AFlowThatWantsToDieAndKillsItsFriendsResponder(private val session: FlowSession) : FlowLogic<Unit>() {
companion object {
val locks = mapOf(
BOB_NAME to Semaphore(0),
CHARLIE_NAME to Semaphore(0)
)
var receivedKilledExceptions = mutableMapOf(
BOB_NAME to false,
CHARLIE_NAME to false
)
}
@Suspendable
override fun call() {
session.receive<String>()
session.send("hi")
session.receive<String>()
locks[ourIdentity.name]!!.release()
try {
session.receive<String>()
} catch (e: UnexpectedFlowEndException) {
receivedKilledExceptions[ourIdentity.name] = true
locks[ourIdentity.name]!!.release()
throw e
}
}
}
@StartableByRPC
@InitiatingFlow
class AFlowThatGetsMurderedByItsFriend(private val party: Party) : FlowLogic<Unit>() {
companion object {
var receivedKilledException = false
}
@Suspendable
override fun call() {
val sessionOne = initiateFlow(party)
// trigger sessions with 2 counter parties
sessionOne.sendAndReceive<String>("what is up")
try {
sessionOne.receive<String>()
} catch (e: UnexpectedFlowEndException) {
receivedKilledException = true
throw e
}
}
}
@InitiatedBy(AFlowThatGetsMurderedByItsFriend::class)
class AFlowThatGetsMurderedByItsFriendResponder(private val session: FlowSession) : FlowLogic<Unit>() {
companion object {
val lockA = Semaphore(0)
val lockB = Semaphore(0)
var flowId: StateMachineRunId? = null
var position = 0
}
@Suspendable
override fun call() {
flowId = runId
session.receive<String>()
session.send("hi")
for (i in 0..100) {
position = i
if (isKilled) {
throw KilledFlowException(runId, EXCEPTION_MESSAGE)
}
if (i == 10) {
lockA.release()
lockB.acquire()
}
}
}
}
@StartableByRPC
class AFlowThatChecksIfItWantsToDie : FlowLogic<Unit>() {
companion object {
val lockA = Semaphore(0)
val lockB = Semaphore(0)
var position = 0
}
@Suspendable
override fun call() {
for (i in 0..100) {
position = i
logger.info("i = $i")
checkFlowIsNotKilled()
if (i == 10) {
lockA.release()
lockB.acquire()
}
}
}
}
@StartableByRPC
class AFlowThatChecksIfItWantsToDieAndLeavesANote : FlowLogic<Unit>() {
companion object {
val lockA = Semaphore(0)
val lockB = Semaphore(0)
var position = 0
}
@Suspendable
override fun call() {
for (i in 0..100) {
position = i
logger.info("i = $i")
checkFlowIsNotKilled { EXCEPTION_MESSAGE }
if (i == 10) {
lockA.release()
lockB.acquire()
}
}
}
}
@StartableByRPC
class GetNumberOfCheckpointsFlow : FlowLogic<Long>() {
override fun call(): Long {
return serviceHub.jdbcSession().prepareStatement("select count(*) from node_checkpoints").use { ps ->
ps.executeQuery().use { rs ->
rs.next()
rs.getLong(1)
}
}
}
}
}

View File

@ -0,0 +1,146 @@
package net.corda.coretests.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.minutes
import net.corda.core.utilities.seconds
import net.corda.core.utilities.unwrap
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.core.config.Configurator
import org.junit.Before
import org.junit.Test
import java.time.Duration
import java.time.Instant
import kotlin.test.assertTrue
class FlowSleepTest {
@Before
fun setup() {
Configurator.setLevel("net.corda.node.services.statemachine", Level.DEBUG)
}
@Test(timeout = 300_000)
fun `flow can sleep`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val (start, finish) = alice.rpc.startFlow(::SleepyFlow).returnValue.getOrThrow(1.minutes)
val difference = Duration.between(start, finish)
assertTrue(difference >= 5.seconds)
assertTrue(difference < 7.seconds)
}
}
@Test(timeout = 300_000)
fun `flow can sleep multiple times`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val (start, middle, finish) = alice.rpc.startFlow(::AnotherSleepyFlow).returnValue.getOrThrow(1.minutes)
val differenceBetweenStartAndMiddle = Duration.between(start, middle)
val differenceBetweenMiddleAndFinish = Duration.between(middle, finish)
assertTrue(differenceBetweenStartAndMiddle >= 5.seconds)
assertTrue(differenceBetweenStartAndMiddle < 7.seconds)
assertTrue(differenceBetweenMiddleAndFinish >= 10.seconds)
assertTrue(differenceBetweenMiddleAndFinish < 12.seconds)
}
}
@Test(timeout = 300_000)
fun `flow can sleep and perform other suspending functions`() {
// ensures that events received while the flow is sleeping are not processed
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
val (start, finish) = alice.rpc.startFlow(
::SleepAndInteractWithPartyFlow,
bob.nodeInfo.singleIdentity()
).returnValue.getOrThrow(1.minutes)
val difference = Duration.between(start, finish)
assertTrue(difference >= 5.seconds)
assertTrue(difference < 7.seconds)
}
}
@StartableByRPC
class SleepyFlow : FlowLogic<Pair<Instant, Instant>>() {
@Suspendable
override fun call(): Pair<Instant, Instant> {
val start = Instant.now()
sleep(5.seconds)
return start to Instant.now()
}
}
@StartableByRPC
class AnotherSleepyFlow : FlowLogic<Triple<Instant, Instant, Instant>>() {
@Suspendable
override fun call(): Triple<Instant, Instant, Instant> {
val start = Instant.now()
sleep(5.seconds)
val middle = Instant.now()
sleep(10.seconds)
return Triple(start, middle, Instant.now())
}
}
@StartableByRPC
@InitiatingFlow
class SleepAndInteractWithPartyFlow(private val party: Party) : FlowLogic<Pair<Instant, Instant>>() {
@Suspendable
override fun call(): Pair<Instant, Instant> {
subFlow(PingPongFlow(party))
val start = Instant.now()
sleep(5.seconds)
val finish = Instant.now()
val session = initiateFlow(party)
session.sendAndReceive<String>("hi")
session.sendAndReceive<String>("hi")
subFlow(PingPongFlow(party))
return start to finish
}
}
@InitiatedBy(SleepAndInteractWithPartyFlow::class)
class SleepAndInteractWithPartyResponder(val session: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
session.receive<String>().unwrap { it }
session.send("go away")
session.receive<String>().unwrap { it }
session.send("go away")
}
}
@InitiatingFlow
class PingPongFlow(val party: Party) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val session = initiateFlow(party)
session.sendAndReceive<String>("ping pong").unwrap { it }
}
}
@InitiatedBy(PingPongFlow::class)
class PingPongResponder(val session: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
session.receive<String>().unwrap { it }
session.send("I got you bro")
}
}
}

View File

@ -29,6 +29,7 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.security.PublicKey
import java.time.Instant
class TransactionBuilderTest {
@Rule
@ -150,4 +151,100 @@ class TransactionBuilderTest {
override val signerKeys: List<PublicKey> get() = parties.map { it.owningKey }
}, DummyContract.PROGRAM_ID, signerKeys = parties.map { it.owningKey })
@Test(timeout=300_000)
fun `list accessors are mutable copies`() {
val inputState1 = TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary)
val inputStateRef1 = StateRef(SecureHash.randomSHA256(), 0)
val referenceState1 = TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary)
val referenceStateRef1 = StateRef(SecureHash.randomSHA256(), 1)
val builder = TransactionBuilder(notary)
.addInputState(StateAndRef(inputState1, inputStateRef1))
.addAttachment(SecureHash.allOnesHash)
.addOutputState(TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary))
.addCommand(DummyCommandData, notary.owningKey)
.addReferenceState(StateAndRef(referenceState1, referenceStateRef1).referenced())
val inputStateRef2 = StateRef(SecureHash.randomSHA256(), 0)
val referenceStateRef2 = StateRef(SecureHash.randomSHA256(), 1)
// List accessors are mutable.
assertThat((builder.inputStates() as ArrayList).also { it.add(inputStateRef2) }).hasSize(2)
assertThat((builder.attachments() as ArrayList).also { it.add(SecureHash.zeroHash) }).hasSize(2)
assertThat((builder.outputStates() as ArrayList).also { it.add(TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary)) }).hasSize(2)
assertThat((builder.commands() as ArrayList).also { it.add(Command(DummyCommandData, notary.owningKey)) }).hasSize(2)
assertThat((builder.referenceStates() as ArrayList).also { it.add(referenceStateRef2) }).hasSize(2)
// List accessors are copies.
assertThat(builder.inputStates()).hasSize(1)
assertThat(builder.attachments()).hasSize(1)
assertThat(builder.outputStates()).hasSize(1)
assertThat(builder.commands()).hasSize(1)
assertThat(builder.referenceStates()).hasSize(1)
}
@Test(timeout=300_000)
fun `copy makes copy except lockId`() {
val inputState = TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary)
val inputStateRef = StateRef(SecureHash.randomSHA256(), 0)
val referenceState = TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary)
val referenceStateRef = StateRef(SecureHash.randomSHA256(), 1)
val timeWindow = TimeWindow.untilOnly(Instant.now())
val builder = TransactionBuilder(notary)
.addInputState(StateAndRef(inputState, inputStateRef))
.addAttachment(SecureHash.allOnesHash)
.addOutputState(TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary))
.addCommand(DummyCommandData, notary.owningKey)
.setTimeWindow(timeWindow)
.setPrivacySalt(PrivacySalt())
.addReferenceState(StateAndRef(referenceState, referenceStateRef).referenced())
val copy = builder.copy()
assertThat(builder.notary).isEqualTo(copy.notary)
assertThat(builder.lockId).isNotEqualTo(copy.lockId)
assertThat(builder.inputStates()).isEqualTo(copy.inputStates())
assertThat(builder.attachments()).isEqualTo(copy.attachments())
assertThat(builder.outputStates()).isEqualTo(copy.outputStates())
assertThat(builder.commands()).isEqualTo(copy.commands())
// assertThat(builder.timeWindow()).isEqualTo(copy.timeWindow())
// assertThat(builder.privacySalt()).isEqualTo(copy.privacySalt())
assertThat(builder.referenceStates()).isEqualTo(copy.referenceStates())
}
@Test(timeout=300_000)
fun `copy makes deep copy of lists`() {
val inputState1 = TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary)
val inputStateRef1 = StateRef(SecureHash.randomSHA256(), 0)
val referenceState1 = TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary)
val referenceStateRef1 = StateRef(SecureHash.randomSHA256(), 1)
val builder = TransactionBuilder(notary)
.addInputState(StateAndRef(inputState1, inputStateRef1))
.addAttachment(SecureHash.allOnesHash)
.addOutputState(TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary))
.addCommand(DummyCommandData, notary.owningKey)
.addReferenceState(StateAndRef(referenceState1, referenceStateRef1).referenced())
val inputState2 = TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary)
val inputStateRef2 = StateRef(SecureHash.randomSHA256(), 0)
val referenceState2 = TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary)
val referenceStateRef2 = StateRef(SecureHash.randomSHA256(), 1)
val copy = builder.copy()
.addInputState(StateAndRef(inputState2, inputStateRef2))
.addAttachment(SecureHash.zeroHash)
.addOutputState(TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary))
.addCommand(DummyCommandData, notary.owningKey)
.addReferenceState(StateAndRef(referenceState2, referenceStateRef2).referenced())
// Lists on the copy are longer
assertThat(copy.inputStates()).hasSize(2)
assertThat(copy.attachments()).hasSize(2)
assertThat(copy.outputStates()).hasSize(2)
assertThat(copy.commands()).hasSize(2)
assertThat(copy.referenceStates()).hasSize(2)
// Lists on the original are unchanged
assertThat(builder.inputStates()).hasSize(1)
assertThat(builder.attachments()).hasSize(1)
assertThat(builder.outputStates()).hasSize(1)
assertThat(builder.commands()).hasSize(1)
assertThat(builder.referenceStates()).hasSize(1)
}
}

View File

@ -101,7 +101,8 @@ task copyQuasarJar(type: Copy) {
jar {
finalizedBy(copyQuasarJar)
baseName 'corda-core'
archiveBaseName = 'corda-core'
archiveClassifier = ''
}
configurations {

View File

@ -49,7 +49,7 @@ interface Attachment : NamedByHash {
/**
* Finds the named file case insensitively and copies it to the output stream.
* @throws FileNotFoundException if the given path doesn't exist in the attachment.
* @throws [FileNotFoundException] if the given path doesn't exist in the attachment.
*/
@JvmDefault
fun extractFile(path: String, outputTo: OutputStream) = openAsJAR().use { it.extractFile(path, outputTo) }

View File

@ -337,6 +337,8 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S
class InvalidAttachmentException(txId: SecureHash, @Suppress("unused") val attachmentHash: AttachmentId) : TransactionVerificationException(txId,
"The attachment $attachmentHash is not a valid ZIP or JAR file.".trimIndent(), null)
class UnsupportedClassVersionError(txId: SecureHash, message: String, cause: Throwable) : TransactionVerificationException(txId, message, cause)
// TODO: Make this descend from TransactionVerificationException so that untrusted attachments cause flows to be hospitalized.
/** Thrown during classloading upon encountering an untrusted attachment (eg. not in the [TRUSTED_UPLOADERS] list) */
@KeepForDJVM

View File

@ -3,7 +3,6 @@ package net.corda.core.crypto
import net.corda.core.CordaOID
import net.corda.core.DeleteForDJVM
import net.corda.core.KeepForDJVM
import net.corda.core.StubOutForDJVM
import net.corda.core.crypto.internal.AliasPrivateKey
import net.corda.core.crypto.internal.Instances.withSignature
import net.corda.core.crypto.internal.`id-Curve25519ph`
@ -108,7 +107,7 @@ object Crypto {
AlgorithmIdentifier(X9ObjectIdentifiers.ecdsa_with_SHA256, SECObjectIdentifiers.secp256k1),
listOf(AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, SECObjectIdentifiers.secp256k1)),
cordaBouncyCastleProvider.name,
"ECDSA",
"EC",
"SHA256withECDSA",
ECNamedCurveTable.getParameterSpec("secp256k1"),
256,
@ -123,7 +122,7 @@ object Crypto {
AlgorithmIdentifier(X9ObjectIdentifiers.ecdsa_with_SHA256, SECObjectIdentifiers.secp256r1),
listOf(AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, SECObjectIdentifiers.secp256r1)),
cordaBouncyCastleProvider.name,
"ECDSA",
"EC",
"SHA256withECDSA",
ECNamedCurveTable.getParameterSpec("secp256r1"),
256,
@ -220,11 +219,12 @@ object Crypto {
* Map of supported digital signature schemes associated by [SignatureScheme.schemeNumberID].
* SchemeNumberID is the scheme identifier attached to [SignatureMetadata].
*/
private val signatureSchemeNumberIDMap: Map<Int, SignatureScheme> = Crypto.supportedSignatureSchemes().associateBy { it.schemeNumberID }
private val signatureSchemeNumberIDMap: Map<Int, SignatureScheme> = supportedSignatureSchemes().associateBy { it.schemeNumberID }
@JvmStatic
fun supportedSignatureSchemes(): List<SignatureScheme> = ArrayList(signatureSchemeMap.values)
@DeleteForDJVM
@JvmStatic
fun findProvider(name: String): Provider {
return providerMap[name] ?: throw IllegalArgumentException("Unrecognised provider: $name")
@ -303,6 +303,7 @@ object Crypto {
* @throws IllegalArgumentException on not supported scheme or if the given key specification
* is inappropriate for this key factory to produce a private key.
*/
@DeleteForDJVM
@JvmStatic
fun decodePrivateKey(encodedKey: ByteArray): PrivateKey {
val keyInfo = PrivateKeyInfo.getInstance(encodedKey)
@ -314,6 +315,7 @@ object Crypto {
return keyFactory.generatePrivate(PKCS8EncodedKeySpec(encodedKey))
}
@DeleteForDJVM
private fun decodeAliasPrivateKey(keyInfo: PrivateKeyInfo): PrivateKey {
val encodable = keyInfo.parsePrivateKey() as DLSequence
val derutF8String = encodable.getObjectAt(0)
@ -329,6 +331,7 @@ object Crypto {
* @throws IllegalArgumentException on not supported scheme or if the given key specification
* is inappropriate for this key factory to produce a private key.
*/
@DeleteForDJVM
@JvmStatic
@Throws(InvalidKeySpecException::class)
fun decodePrivateKey(schemeCodeName: String, encodedKey: ByteArray): PrivateKey {
@ -407,7 +410,7 @@ object Crypto {
val keyFactory = keyFactory(signatureScheme)
return keyFactory.generatePublic(X509EncodedKeySpec(encodedKey))
} catch (ikse: InvalidKeySpecException) {
throw throw InvalidKeySpecException("This public key cannot be decoded, please ensure it is X509 encoded and " +
throw InvalidKeySpecException("This public key cannot be decoded, please ensure it is X509 encoded and " +
"that it corresponds to the input scheme's code name.", ikse)
}
}
@ -499,14 +502,14 @@ object Crypto {
@JvmStatic
@Throws(InvalidKeyException::class, SignatureException::class)
fun doSign(keyPair: KeyPair, signableData: SignableData): TransactionSignature {
val sigKey: SignatureScheme = Crypto.findSignatureScheme(keyPair.private)
val sigMetaData: SignatureScheme = Crypto.findSignatureScheme(signableData.signatureMetadata.schemeNumberID)
val sigKey: SignatureScheme = findSignatureScheme(keyPair.private)
val sigMetaData: SignatureScheme = findSignatureScheme(signableData.signatureMetadata.schemeNumberID)
// Special handling if the advertised SignatureScheme is CompositeKey.
// TODO fix notaries that advertise [CompositeKey] in their signature Metadata. Currently, clustered notary nodes
// mention Crypto.COMPOSITE_KEY in their SignatureMetadata, but they are actually signing with a leaf-key
// (and if they refer to it as a Composite key, then we lose info about the actual type of their signing key).
// In short, their metadata should be the leaf key-type, until we support CompositeKey signatures.
require(sigKey == sigMetaData || sigMetaData == Crypto.COMPOSITE_KEY) {
require(sigKey == sigMetaData || sigMetaData == COMPOSITE_KEY) {
"Metadata schemeCodeName: ${sigMetaData.schemeCodeName} is not aligned with the key type: ${sigKey.schemeCodeName}."
}
val signatureBytes = doSign(sigKey.schemeCodeName, keyPair.private, signableData.serialize().bytes)
@ -601,7 +604,7 @@ object Crypto {
@Throws(InvalidKeyException::class, SignatureException::class)
fun doVerify(txId: SecureHash, transactionSignature: TransactionSignature): Boolean {
val signableData = SignableData(originalSignedHash(txId, transactionSignature.partialMerkleTree), transactionSignature.signatureMetadata)
return Crypto.doVerify(transactionSignature.by, transactionSignature.bytes, signableData.serialize().bytes)
return doVerify(transactionSignature.by, transactionSignature.bytes, signableData.serialize().bytes)
}
/**
@ -975,15 +978,6 @@ object Crypto {
@JvmStatic
fun validatePublicKey(key: PublicKey): Boolean = validatePublicKey(findSignatureScheme(key), key)
// Validate a key, by checking its algorithmic params.
private fun validateKey(signatureScheme: SignatureScheme, key: Key): Boolean {
return when (key) {
is PublicKey -> validatePublicKey(signatureScheme, key)
is PrivateKey -> validatePrivateKey(signatureScheme, key)
else -> throw IllegalArgumentException("Unsupported key type: ${key::class}")
}
}
// Check if a public key satisfies algorithm specs (for ECC: key should lie on the curve and not being point-at-infinity).
private fun validatePublicKey(signatureScheme: SignatureScheme, key: PublicKey): Boolean {
return when (key) {
@ -994,16 +988,6 @@ object Crypto {
}
}
// Check if a private key satisfies algorithm specs.
private fun validatePrivateKey(signatureScheme: SignatureScheme, key: PrivateKey): Boolean {
return when (key) {
is BCECPrivateKey -> key.parameters == signatureScheme.algSpec
is EdDSAPrivateKey -> key.params == signatureScheme.algSpec
is BCRSAPrivateKey, is BCSphincs256PrivateKey -> true // TODO: Check if non-ECC keys satisfy params (i.e. approved/valid RSA modulus size).
else -> throw IllegalArgumentException("Unsupported key type: ${key::class}")
}
}
/**
* Convert a public key to a supported implementation.
* @param key a public key.
@ -1043,6 +1027,7 @@ object Crypto {
* @throws IllegalArgumentException on not supported scheme or if the given key specification
* is inappropriate for a supported key factory to produce a private key.
*/
@DeleteForDJVM
@JvmStatic
fun toSupportedPrivateKey(key: PrivateKey): PrivateKey {
return when (key) {
@ -1078,6 +1063,7 @@ object Crypto {
* CRL & CSR checks etc.).
*/
// TODO: perform all cryptographic operations via Crypto.
@DeleteForDJVM
@JvmStatic
fun registerProviders() {
providerMap
@ -1088,7 +1074,7 @@ object Crypto {
setBouncyCastleRNG()
}
@StubOutForDJVM
@DeleteForDJVM
private fun setBouncyCastleRNG() {
CryptoServicesRegistrar.setSecureRandom(newSecureRandom())
}

View File

@ -2,7 +2,6 @@ package net.corda.core.crypto
import net.corda.core.KeepForDJVM
import net.corda.core.identity.AnonymousParty
import net.corda.core.serialization.CordaSerializable
import java.security.PublicKey
@KeepForDJVM

View File

@ -15,8 +15,10 @@ import org.bouncycastle.jcajce.provider.asymmetric.ec.AlgorithmParametersSpi
import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider
import java.security.Provider
import java.security.SecureRandom
import java.security.Security
import java.util.Collections.unmodifiableMap
val cordaSecurityProvider = CordaSecurityProvider().also {
// Among the others, we should register [CordaSecurityProvider] as the first provider, to ensure that when invoking [SecureRandom()]
@ -52,8 +54,11 @@ val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply {
// This map is required to defend against users that forcibly call Security.addProvider / Security.removeProvider
// that could cause unexpected and suspicious behaviour.
// i.e. if someone removes a Provider and then he/she adds a new one with the same name.
// The val is private to avoid any harmful state changes.
val providerMap = listOf(cordaBouncyCastleProvider, cordaSecurityProvider, bouncyCastlePQCProvider).map { it.name to it }.toMap()
// The val is immutable to avoid any harmful state changes.
internal val providerMap: Map<String, Provider> = unmodifiableMap(
listOf(cordaBouncyCastleProvider, cordaSecurityProvider, bouncyCastlePQCProvider)
.associateByTo(LinkedHashMap(), Provider::getName)
)
@DeleteForDJVM
fun platformSecureRandomFactory(): SecureRandom = platformSecureRandom() // To minimise diff of CryptoUtils against open-source.

View File

@ -1,6 +1,7 @@
package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.CordaInternal
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.identity.Party
@ -45,6 +46,13 @@ class FinalityFlow private constructor(val transaction: SignedTransaction,
private val sessions: Collection<FlowSession>,
private val newApi: Boolean,
private val statesToRecord: StatesToRecord = ONLY_RELEVANT) : FlowLogic<SignedTransaction>() {
@CordaInternal
data class ExtraConstructorArgs(val oldParticipants: Collection<Party>, val sessions: Collection<FlowSession>, val newApi: Boolean, val statesToRecord: StatesToRecord)
@CordaInternal
fun getExtraConstructorArgs() = ExtraConstructorArgs(oldParticipants, sessions, newApi, statesToRecord)
@Deprecated(DEPRECATION_MSG)
constructor(transaction: SignedTransaction, extraRecipients: Set<Party>, progressTracker: ProgressTracker) : this(
transaction, extraRecipients, progressTracker, emptyList(), false

View File

@ -1,7 +1,5 @@
package net.corda.core.flows
import net.corda.core.internal.ServiceHubCoreInternal
import net.corda.core.node.ServiceHub
import java.util.concurrent.CompletableFuture
/**

View File

@ -24,9 +24,6 @@ import net.corda.core.messaging.DataFeed
import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.serialize
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData
@ -130,6 +127,32 @@ abstract class FlowLogic<out T> {
*/
val serviceHub: ServiceHub get() = stateMachine.serviceHub
/**
* Returns `true` when the current [FlowLogic] has been killed (has received a command to halt its progress and terminate).
*
* Check this property in long-running computation loops to exit a flow that has been killed:
* ```
* while (!isKilled) {
* // do some computation
* }
* ```
*
* Ideal usage would include throwing a [KilledFlowException] which will lead to the termination of the flow:
* ```
* for (item in list) {
* if (isKilled) {
* throw KilledFlowException(runId)
* }
* // do some computation
* }
* ```
*
* Note, once the [isKilled] flag is set to `true` the flow may terminate once it reaches the next API function marked with the
* @[Suspendable] annotation. Therefore, it is possible to write a flow that does not interact with the [isKilled] flag while still
* terminating correctly.
*/
val isKilled: Boolean get() = stateMachine.isKilled
/**
* Creates a communication session with [destination]. Subsequently you may send/receive using this session object. How the messaging
* is routed depends on the [Destination] type, including whether this call does any initial communication.
@ -267,7 +290,7 @@ abstract class FlowLogic<out T> {
@Suspendable
internal fun <R : Any> FlowSession.sendAndReceiveWithRetry(receiveType: Class<R>, payload: Any): UntrustworthyData<R> {
val request = FlowIORequest.SendAndReceive(
sessionToMessage = mapOf(this to payload.serialize(context = SerializationDefaults.P2P_CONTEXT)),
sessionToMessage = stateMachine.serialize(mapOf(this to payload)),
shouldRetrySend = true
)
return stateMachine.suspend(request, maySkipCheckpoint = false)[this]!!.checkPayloadIs(receiveType)
@ -350,21 +373,11 @@ abstract class FlowLogic<out T> {
@JvmOverloads
fun sendAllMap(payloadsPerSession: Map<FlowSession, Any>, maySkipCheckpoint: Boolean = false) {
val request = FlowIORequest.Send(
sessionToMessage = serializePayloads(payloadsPerSession)
sessionToMessage = stateMachine.serialize(payloadsPerSession)
)
stateMachine.suspend(request, maySkipCheckpoint)
}
@Suspendable
private fun serializePayloads(payloadsPerSession: Map<FlowSession, Any>): Map<FlowSession, SerializedBytes<Any>> {
val cachedSerializedPayloads = mutableMapOf<Any, SerializedBytes<Any>>()
return payloadsPerSession.mapValues { (_, payload) ->
cachedSerializedPayloads[payload] ?: payload.serialize(context = SerializationDefaults.P2P_CONTEXT).also { cachedSerializedPayloads[payload] = it }
}
}
/**
* Invokes the given subflow. This function returns once the subflow completes successfully with the result
* returned by that subflow's [call] method. If the subflow has a progress tracker, it is attached to the
@ -583,6 +596,46 @@ abstract class FlowLogic<out T> {
val request = FlowIORequest.ExecuteAsyncOperation(flowAsyncOperation)
return stateMachine.suspend(request, false)
}
/**
* Helper function that throws a [KilledFlowException] if the current [FlowLogic] has been killed.
*
* Call this function in long-running computation loops to exit a flow that has been killed:
* ```
* for (item in list) {
* checkFlowIsNotKilled()
* // do some computation
* }
* ```
*
* See the [isKilled] property for more information.
*/
fun checkFlowIsNotKilled() {
if (isKilled) {
throw KilledFlowException(runId)
}
}
/**
* Helper function that throws a [KilledFlowException] if the current [FlowLogic] has been killed. The provided message is added to the
* thrown [KilledFlowException].
*
* Call this function in long-running computation loops to exit a flow that has been killed:
* ```
* for (item in list) {
* checkFlowIsNotKilled { "The flow $runId was killed while iterating through the list of items" }
* // do some computation
* }
* ```
*
* See the [isKilled] property for more information.
*/
fun checkFlowIsNotKilled(lazyMessage: () -> Any) {
if (isKilled) {
val message = lazyMessage()
throw KilledFlowException(runId, message.toString())
}
}
}
/**

View File

@ -0,0 +1,14 @@
package net.corda.core.flows
import net.corda.core.CordaRuntimeException
/**
* An exception that is thrown when a flow has been killed.
*
* This exception can be returned and thrown to RPC clients waiting for the result of a flow's future.
*
* It can also be used in conjunction with [FlowLogic.isKilled] to escape long-running computation loops when a flow has been killed.
*/
class KilledFlowException(val id: StateMachineRunId, message: String) : CordaRuntimeException(message) {
constructor(id: StateMachineRunId) : this(id, "The flow $id was killed")
}

View File

@ -9,17 +9,20 @@ import net.corda.core.serialization.internal.AttachmentURLStreamHandlerFactory.a
* Creates instances of all the classes in the classpath of the provided classloader, which implement the interface of the provided class.
* @param classloader the classloader, which will be searched for the classes.
* @param clazz the class of the interface, which the classes - to be returned - must implement.
* @param classVersionRange if specified an exception is raised if class version is not within the passed range.
*
* @return instances of the identified classes.
* @throws IllegalArgumentException if the classes found do not have proper constructors.
* @throws UnsupportedClassVersionError if the class version is not within range.
*
* Note: In order to be instantiated, the associated classes must:
* - be non-abstract
* - either be a Kotlin object or have a constructor with no parameters (or only optional ones)
*/
@StubOutForDJVM
fun <T: Any> createInstancesOfClassesImplementing(classloader: ClassLoader, clazz: Class<T>): Set<T> {
return getNamesOfClassesImplementing(classloader, clazz)
fun <T: Any> createInstancesOfClassesImplementing(classloader: ClassLoader, clazz: Class<T>,
classVersionRange: IntRange? = null): Set<T> {
return getNamesOfClassesImplementing(classloader, clazz, classVersionRange)
.map { classloader.loadClass(it).asSubclass(clazz) }
.mapTo(LinkedHashSet()) { it.kotlin.objectOrNewInstance() }
}
@ -28,17 +31,26 @@ fun <T: Any> createInstancesOfClassesImplementing(classloader: ClassLoader, claz
* Scans for all the non-abstract classes in the classpath of the provided classloader which implement the interface of the provided class.
* @param classloader the classloader, which will be searched for the classes.
* @param clazz the class of the interface, which the classes - to be returned - must implement.
* @param classVersionRange if specified an exception is raised if class version is not within the passed range.
*
* @return names of the identified classes.
* @throws UnsupportedClassVersionError if the class version is not within range.
*/
@StubOutForDJVM
fun <T: Any> getNamesOfClassesImplementing(classloader: ClassLoader, clazz: Class<T>): Set<String> {
fun <T: Any> getNamesOfClassesImplementing(classloader: ClassLoader, clazz: Class<T>,
classVersionRange: IntRange? = null): Set<String> {
return ClassGraph().overrideClassLoaders(classloader)
.enableURLScheme(attachmentScheme)
.ignoreParentClassLoaders()
.enableClassInfo()
.pooledScan()
.use { result ->
classVersionRange?.let {
result.allClasses.firstOrNull { c -> c.classfileMajorVersion !in classVersionRange }?.also {
throw UnsupportedClassVersionError("Class ${it.name} found in ${it.classpathElementURL} " +
"has an unsupported class version of ${it.classfileMajorVersion}")
}
}
result.getClassesImplementing(clazz.name)
.filterNot(ClassInfo::isAbstract)
.mapTo(LinkedHashSet(), ClassInfo::getName)

View File

@ -28,7 +28,7 @@ import java.util.jar.JarInputStream
// *Internal* Corda-specific utilities.
const val PLATFORM_VERSION = 6
const val PLATFORM_VERSION = 7
fun ServicesForResolution.ensureMinimumPlatformVersion(requiredMinPlatformVersion: Int, feature: String) {
checkMinimumPlatformVersion(networkParameters.minimumPlatformVersion, requiredMinPlatformVersion, feature)
@ -48,6 +48,20 @@ fun checkMinimumPlatformVersion(minimumPlatformVersion: Int, requiredMinPlatform
@Throws(NumberFormatException::class)
fun getJavaUpdateVersion(javaVersion: String): Long = javaVersion.substringAfter("_").substringBefore("-").toLong()
enum class JavaVersion(val versionString: String) {
Java_1_8("1.8"),
Java_11("11");
companion object {
fun isVersionAtLeast(version: JavaVersion): Boolean {
return currentVersion.toFloat() >= version.versionString.toFloat()
}
private val currentVersion: String = System.getProperty("java.specification.version") ?:
throw IllegalStateException("Unable to retrieve system property java.specification.version")
}
}
/** Provide access to internal method for AttachmentClassLoaderTests. */
@DeleteForDJVM
fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serializationContext: SerializationContext): WireTransaction {

View File

@ -8,6 +8,7 @@ import net.corda.core.context.InvocationContext
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.SerializedBytes
import org.slf4j.Logger
/** This is an internal interface that is implemented by code in the node module. You should look at [FlowLogic]. */
@ -17,6 +18,8 @@ interface FlowStateMachine<FLOWRETURN> {
@Suspendable
fun <SUSPENDRETURN : Any> suspend(ioRequest: FlowIORequest<SUSPENDRETURN>, maySkipCheckpoint: Boolean): SUSPENDRETURN
fun serialize(payloads: Map<FlowSession, Any>): Map<FlowSession, SerializedBytes<Any>>
@Suspendable
fun initiateFlow(destination: Destination, wellKnownParty: Party): FlowSession
@ -44,4 +47,5 @@ interface FlowStateMachine<FLOWRETURN> {
val ourIdentity: Party
val ourSenderUUID: String?
val creationTime: Long
val isKilled: Boolean
}

View File

@ -636,3 +636,6 @@ fun Logger.warnOnce(warning: String) {
this.warn(warning)
}
}
const val JDK1_2_CLASS_FILE_FORMAT_MAJOR_VERSION = 46
const val JDK8_CLASS_FILE_FORMAT_MAJOR_VERSION = 52

View File

@ -0,0 +1,10 @@
package net.corda.core.internal
/*
Constants for new features that can only be switched on at specific platform versions can be specified in this file.
The text constant describes the feature and the numeric specifies the platform version the feature is enabled at.
*/
object PlatformVersionSwitches {
const val REMOVE_NO_OVERLAP_RULE_FOR_REFERENCE_DATA_ATTACHMENTS = 7
const val ENABLE_P2P_COMPRESSION = 7
}

View File

@ -2,6 +2,7 @@ package net.corda.core.internal
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.DeleteForDJVM
import net.corda.core.internal.notary.NotaryService
import net.corda.core.node.ServiceHub
import net.corda.core.node.StatesToRecord
import java.util.concurrent.ExecutorService
@ -14,6 +15,11 @@ interface ServiceHubCoreInternal : ServiceHub {
val attachmentTrustCalculator: AttachmentTrustCalculator
/**
* Optional `NotaryService` which will be `null` for all non-Notary nodes.
*/
val notaryService: NotaryService?
fun createTransactionsResolver(flow: ResolveTransactionsFlow): TransactionsResolver
}

View File

@ -77,7 +77,12 @@ fun <T : Any> deserialiseComponentGroup(componentGroups: List<ComponentGroup>,
try {
factory.deserialize(component, clazz.java, context)
} catch (e: MissingAttachmentsException) {
throw e
/**
* [ServiceHub.signInitialTransaction] forgets to declare that
* it may throw any checked exceptions. Wrap this one inside
* an unchecked version to avoid breaking Java CorDapps.
*/
throw MissingAttachmentsRuntimeException(e.ids, e.message, e)
} catch (e: Exception) {
throw TransactionDeserialisationException(groupEnum, internalIndex, e)
}
@ -88,7 +93,7 @@ fun <T : Any> deserialiseComponentGroup(componentGroups: List<ComponentGroup>,
* Exception raised if an error was encountered while attempting to deserialise a component group in a transaction.
*/
class TransactionDeserialisationException(groupEnum: ComponentGroupEnum, index: Int, cause: Exception):
Exception("Failed to deserialise group $groupEnum at index $index in transaction: ${cause.message}", cause)
RuntimeException("Failed to deserialise group $groupEnum at index $index in transaction: ${cause.message}", cause)
/**
* Method to deserialise Commands from its two groups:

View File

@ -401,8 +401,7 @@ class ContractVerifier(private val transactionClassLoader: ClassLoader) : Functi
override fun apply(ltx: LedgerTransaction) {
val contractClassNames = (ltx.inputs.map(StateAndRef<ContractState>::state) + ltx.outputs)
.map(TransactionState<*>::contract)
.toSet()
.mapTo(LinkedHashSet(), TransactionState<*>::contract)
contractClassNames.associateBy(
{ it }, { createContractClass(ltx.id, it) }
@ -410,7 +409,7 @@ class ContractVerifier(private val transactionClassLoader: ClassLoader) : Functi
try {
/**
* This function must execute with the DJVM's sandbox, which does not
* permit user code to access [java.lang.reflect.Constructor] objects.
* permit user code to invoke [java.lang.Class.getDeclaredConstructor].
*
* [Class.newInstance] is deprecated as of Java 9.
*/

View File

@ -4,7 +4,6 @@ package net.corda.core.internal
import net.corda.core.KeepForDJVM
import net.corda.core.identity.CordaX500Name
import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.x500.AttributeTypeAndValue
import org.bouncycastle.asn1.x500.X500Name

View File

@ -14,6 +14,19 @@ abstract class NotaryService : SingletonSerializeAsToken() {
abstract val services: ServiceHub
abstract val notaryIdentityKey: PublicKey
/**
* Interfaces for the request and result formats of queries supported by notary services. To
* implement a new query, you must:
*
* - Define data classes which implement the [Query.Request] and [Query.Result] interfaces
* - Add corresponding handling for the new classes within the notary service implementations
* that you want to support the query.
*/
interface Query {
interface Request
interface Result
}
abstract fun start()
abstract fun stop()
@ -22,4 +35,18 @@ abstract class NotaryService : SingletonSerializeAsToken() {
* @param otherPartySession client [Party] making the request
*/
abstract fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?>
/**
* Processes a [Query.Request] and returns a [Query.Result].
*
* Note that this always throws an [UnsupportedOperationException] to handle notary
* implementations that do not support this functionality. This must be overridden by
* notary implementations wishing to support query functionality.
*
* Overrides of this function may themselves still throw an [UnsupportedOperationException],
* if they do not support specific query implementations
*/
open fun processQuery(query: Query.Request): Query.Result {
throw UnsupportedOperationException("Notary has not implemented query support")
}
}

View File

@ -252,7 +252,7 @@ interface CordaRPCOps : RPCOps {
* Note: This operation may be restricted only to node administrators.
* @param parametersHash hash of network parameters to accept
* @throws IllegalArgumentException if network map advertises update with different parameters hash then the one accepted by node's operator.
* @throws IOException if failed to send the approval to network map
* @throws [IOException] if failed to send the approval to network map
*/
// TODO This operation should be restricted to just node admins.
fun acceptNewNetworkParameters(parametersHash: SecureHash)

View File

@ -380,6 +380,26 @@ interface ServiceHub : ServicesForResolution {
* When used within a flow, this session automatically forms part of the enclosing flow transaction boundary,
* and thus queryable data will include everything committed as of the last checkpoint.
*
* We want to make sure users have a restricted access to administrative functions, this function will return a [RestrictedConnection] instance.
* The following methods are blocked:
* - abort(executor: Executor?)
* - clearWarnings()
* - close()
* - commit()
* - setSavepoint()
* - setSavepoint(name : String?)
* - releaseSavepoint(savepoint: Savepoint?)
* - rollback()
* - rollback(savepoint: Savepoint?)
* - setCatalog(catalog : String?)
* - setTransactionIsolation(level: Int)
* - setTypeMap(map: MutableMap<String, Class<*>>?)
* - setHoldability(holdability: Int)
* - setSchema(schema: String?)
* - setNetworkTimeout(executor: Executor?, milliseconds: Int)
* - setAutoCommit(autoCommit: Boolean)
* - setReadOnly(readOnly: Boolean)
*
* @throws IllegalStateException if called outside of a transaction.
* @return A [Connection]
*/
@ -393,6 +413,24 @@ interface ServiceHub : ServicesForResolution {
* NOTE: Suspendable flow operations such as send, receive, subFlow and sleep, cannot be called within the lambda.
*
* @param block a lambda function with access to an [EntityManager].
*
* We want to make sure users have a restricted access to administrative functions.
* The following methods are blocked:
* - close()
* - unwrap(cls: Class<T>?)
* - getDelegate(): Any
* - getMetamodel()
* - joinTransaction()
* - lock(entity: Any?, lockMode: LockModeType?)
* - lock(entity: Any?, lockMode: LockModeType?, properties: MutableMap<String, Any>?)
* - setProperty(propertyName: String?, value: Any?)
*
* getTransaction returns a [RestrictedEntityTransaction] to prevent unsafe manipulation of a flow's underlying
* database transaction.
* The following methods are blocked:
* - begin()
* - commit()
* - rollback()
*/
fun <T : Any?> withEntityManager(block: EntityManager.() -> T): T
@ -404,6 +442,24 @@ interface ServiceHub : ServicesForResolution {
* NOTE: Suspendable flow operations such as send, receive, subFlow and sleep, cannot be called within the lambda.
*
* @param block a lambda function with access to an [EntityManager].
*
* We want to make sure users have a restricted access to administrative functions.
* The following methods are blocked:
* - close()
* - unwrap(cls: Class<T>?)
* - getDelegate(): Any
* - getMetamodel()
* - joinTransaction()
* - lock(entity: Any?, lockMode: LockModeType?)
* - lock(entity: Any?, lockMode: LockModeType?, properties: MutableMap<String, Any>?)
* - setProperty(propertyName: String?, value: Any?)
*
* getTransaction returns a [RestrictedEntityTransaction] to prevent unsafe manipulation of a flow's underlying
* database transaction.
* The following methods are blocked:
* - begin()
* - commit()
* - rollback()
*/
fun withEntityManager(block: Consumer<EntityManager>)

View File

@ -8,7 +8,6 @@ import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.node.services.Vault
import net.corda.core.schemas.StatePersistable
import net.corda.core.serialization.CordaSerializable

View File

@ -0,0 +1,17 @@
package net.corda.core.serialization
import net.corda.core.CordaRuntimeException
import net.corda.core.KeepForDJVM
import net.corda.core.node.services.AttachmentId
@KeepForDJVM
@CordaSerializable
class MissingAttachmentsRuntimeException(val ids: List<AttachmentId>, message: String?, cause: Throwable?)
: CordaRuntimeException(message, cause) {
@Suppress("unused")
constructor(ids: List<AttachmentId>, message: String?) : this(ids, message, null)
@Suppress("unused")
constructor(ids: List<AttachmentId>) : this(ids, null, null)
}

View File

@ -323,7 +323,13 @@ object AttachmentsClassLoaderBuilder {
val serializationContext = cache.computeIfAbsent(Key(attachmentIds, params)) {
// Create classloader and load serializers, whitelisted classes
val transactionClassLoader = AttachmentsClassLoader(attachments, params, txId, isAttachmentTrusted, parent)
val serializers = createInstancesOfClassesImplementing(transactionClassLoader, SerializationCustomSerializer::class.java)
val serializers = try {
createInstancesOfClassesImplementing(transactionClassLoader, SerializationCustomSerializer::class.java,
JDK1_2_CLASS_FILE_FORMAT_MAJOR_VERSION..JDK8_CLASS_FILE_FORMAT_MAJOR_VERSION)
}
catch(ex: UnsupportedClassVersionError) {
throw TransactionVerificationException.UnsupportedClassVersionError(txId, ex.message!!, ex)
}
val whitelistedClasses = ServiceLoader.load(SerializationWhitelist::class.java, transactionClassLoader)
.flatMap(SerializationWhitelist::whitelist)

View File

@ -3,7 +3,6 @@ package net.corda.core.serialization.internal
import net.corda.core.DeleteForDJVM
import net.corda.core.DoNotImplement
import net.corda.core.KeepForDJVM
import net.corda.core.crypto.SecureHash
import net.corda.core.serialization.*
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.sequence

View File

@ -100,7 +100,7 @@ open class TransactionBuilder(
commands = ArrayList(commands),
window = window,
privacySalt = privacySalt,
references = references,
references = ArrayList(references),
serviceHub = serviceHub
)
t.inputsWithTransactionState.addAll(this.inputsWithTransactionState)
@ -134,7 +134,7 @@ open class TransactionBuilder(
*
* @returns A new [WireTransaction] that will be unaffected by further changes to this [TransactionBuilder].
*
* @throws ZoneVersionTooLowException if there are reference states and the zone minimum platform version is less than 4.
* @throws [ZoneVersionTooLowException] if there are reference states and the zone minimum platform version is less than 4.
*/
@Throws(MissingContractAttachments::class)
fun toWireTransaction(services: ServicesForResolution): WireTransaction = toWireTransactionWithContext(services, null)

View File

@ -317,7 +317,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
/**
* Checks that the given signature matches one of the commands and that it is a correct signature over the tx.
*
* @throws SignatureException if the signature didn't match the transaction contents.
* @throws [SignatureException] if the signature didn't match the transaction contents.
* @throws IllegalArgumentException if the signature key doesn't appear in any command.
*/
fun checkSignature(sig: TransactionSignature) {

View File

@ -471,9 +471,9 @@ class CryptoUtilsTest {
val privKeyDecoded = Crypto.decodePrivateKey(privKey.encoded)
val pubKeyDecoded = Crypto.decodePublicKey(pubKey.encoded)
assertEquals(privKeyDecoded.algorithm, "ECDSA")
assertEquals(privKeyDecoded.algorithm, "EC")
assertEquals((privKeyDecoded as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256k1"))
assertEquals(pubKeyDecoded.algorithm, "ECDSA")
assertEquals(pubKeyDecoded.algorithm, "EC")
assertEquals((pubKeyDecoded as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256k1"))
}
@ -481,9 +481,9 @@ class CryptoUtilsTest {
fun `ECDSA secp256r1 scheme finder by key type`() {
val keyPairR1 = Crypto.generateKeyPair(ECDSA_SECP256R1_SHA256)
val (privR1, pubR1) = keyPairR1
assertEquals(privR1.algorithm, "ECDSA")
assertEquals(privR1.algorithm, "EC")
assertEquals((privR1 as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256r1"))
assertEquals(pubR1.algorithm, "ECDSA")
assertEquals(pubR1.algorithm, "EC")
assertEquals((pubR1 as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256r1"))
}
@ -530,11 +530,11 @@ class CryptoUtilsTest {
val encodedPubK1 = pubK1.encoded
val decodedPrivK1 = Crypto.decodePrivateKey(encodedPrivK1)
assertEquals(decodedPrivK1.algorithm, "ECDSA")
assertEquals(decodedPrivK1.algorithm, "EC")
assertEquals(decodedPrivK1, privK1)
val decodedPubK1 = Crypto.decodePublicKey(encodedPubK1)
assertEquals(decodedPubK1.algorithm, "ECDSA")
assertEquals(decodedPubK1.algorithm, "EC")
assertEquals(decodedPubK1, pubK1)
}
@ -546,11 +546,11 @@ class CryptoUtilsTest {
val encodedPubR1 = pubR1.encoded
val decodedPrivR1 = Crypto.decodePrivateKey(encodedPrivR1)
assertEquals(decodedPrivR1.algorithm, "ECDSA")
assertEquals(decodedPrivR1.algorithm, "EC")
assertEquals(decodedPrivR1, privR1)
val decodedPubR1 = Crypto.decodePublicKey(encodedPubR1)
assertEquals(decodedPubR1.algorithm, "ECDSA")
assertEquals(decodedPubR1.algorithm, "EC")
assertEquals(decodedPubR1, pubR1)
}

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