mirror of
https://github.com/corda/corda.git
synced 2025-01-15 09:20:22 +00:00
Merge remote-tracking branch 'corda-public/master'
This commit is contained in:
commit
f2df9d36fa
@ -3,7 +3,7 @@
|
|||||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||||
<option name="MAIN_CLASS_NAME" value="net.corda.attachmentdemo.AttachmentDemoKt" />
|
<option name="MAIN_CLASS_NAME" value="net.corda.attachmentdemo.AttachmentDemoKt" />
|
||||||
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
|
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
|
||||||
<option name="PROGRAM_PARAMETERS" value="--role=RECIPIENT" />
|
<option name="PROGRAM_PARAMETERS" value="--role=RECIPIENT --certificates="build/attachment-demo-nodes/Bank B/certificates"" />
|
||||||
<option name="WORKING_DIRECTORY" value="" />
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||||
<option name="ALTERNATIVE_JRE_PATH" />
|
<option name="ALTERNATIVE_JRE_PATH" />
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||||
<option name="MAIN_CLASS_NAME" value="net.corda.attachmentdemo.AttachmentDemoKt" />
|
<option name="MAIN_CLASS_NAME" value="net.corda.attachmentdemo.AttachmentDemoKt" />
|
||||||
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
|
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
|
||||||
<option name="PROGRAM_PARAMETERS" value="--role SENDER" />
|
<option name="PROGRAM_PARAMETERS" value="--role SENDER --certificates="build/attachment-demo-nodes/Bank A/certificates"" />
|
||||||
<option name="WORKING_DIRECTORY" value="" />
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||||
<option name="ALTERNATIVE_JRE_PATH" />
|
<option name="ALTERNATIVE_JRE_PATH" />
|
||||||
|
15
.idea/runConfigurations/Explorer___demo_nodes.xml
generated
Normal file
15
.idea/runConfigurations/Explorer___demo_nodes.xml
generated
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Explorer - demo nodes" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
|
||||||
|
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="net.corda.explorer.MainKt" />
|
||||||
|
<option name="VM_PARAMETERS" value="" />
|
||||||
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
||||||
|
<option name="ALTERNATIVE_JRE_PATH" value="1.8" />
|
||||||
|
<option name="PASS_PARENT_ENVS" value="true" />
|
||||||
|
<module name="explorer_main" />
|
||||||
|
<envs />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
15
.idea/runConfigurations/Explorer___demo_nodes__simulation_.xml
generated
Normal file
15
.idea/runConfigurations/Explorer___demo_nodes__simulation_.xml
generated
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Explorer - demo nodes (simulation)" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
|
||||||
|
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="net.corda.explorer.MainKt" />
|
||||||
|
<option name="VM_PARAMETERS" value="" />
|
||||||
|
<option name="PROGRAM_PARAMETERS" value="-S" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
||||||
|
<option name="ALTERNATIVE_JRE_PATH" value="1.8" />
|
||||||
|
<option name="PASS_PARENT_ENVS" value="true" />
|
||||||
|
<module name="explorer_main" />
|
||||||
|
<envs />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
15
.idea/runConfigurations/Notary_Demo__Run_Nodes.xml
generated
15
.idea/runConfigurations/Notary_Demo__Run_Nodes.xml
generated
@ -1,15 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="Notary Demo: Run Nodes" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
|
|
||||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
|
||||||
<option name="MAIN_CLASS_NAME" value="net.corda.notarydemo.MainKt" />
|
|
||||||
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
|
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
|
||||||
<option name="WORKING_DIRECTORY" value="" />
|
|
||||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
|
||||||
<option name="ALTERNATIVE_JRE_PATH" />
|
|
||||||
<option name="PASS_PARENT_ENVS" value="true" />
|
|
||||||
<module name="raft-notary-demo_main" />
|
|
||||||
<envs />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
@ -1,15 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="Notary Demo: Run Notarisation" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
|
|
||||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
|
||||||
<option name="MAIN_CLASS_NAME" value="net.corda.notarydemo.NotaryDemoKt" />
|
|
||||||
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
|
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
|
||||||
<option name="WORKING_DIRECTORY" value="" />
|
|
||||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
|
||||||
<option name="ALTERNATIVE_JRE_PATH" />
|
|
||||||
<option name="PASS_PARENT_ENVS" value="true" />
|
|
||||||
<module name="raft-notary-demo_main" />
|
|
||||||
<envs />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
15
.idea/runConfigurations/Raft_Notary_Demo__Run_Nodes.xml
generated
Normal file
15
.idea/runConfigurations/Raft_Notary_Demo__Run_Nodes.xml
generated
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Raft Notary Demo: Run Nodes" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
|
||||||
|
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="net.corda.notarydemo.MainKt" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
|
||||||
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||||
|
<option name="ALTERNATIVE_JRE_PATH" />
|
||||||
|
<option name="PASS_PARENT_ENVS" value="true" />
|
||||||
|
<module name="raft-notary-demo_main" />
|
||||||
|
<envs />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
15
.idea/runConfigurations/Raft_Notary_Demo__Run_Notarisation.xml
generated
Normal file
15
.idea/runConfigurations/Raft_Notary_Demo__Run_Notarisation.xml
generated
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Raft Notary Demo: Run Notarisation" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
|
||||||
|
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="net.corda.notarydemo.NotaryDemoKt" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
|
||||||
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||||
|
<option name="ALTERNATIVE_JRE_PATH" />
|
||||||
|
<option name="PASS_PARENT_ENVS" value="true" />
|
||||||
|
<module name="raft-notary-demo_main" />
|
||||||
|
<envs />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
@ -3,7 +3,7 @@
|
|||||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||||
<option name="MAIN_CLASS_NAME" value="net.corda.traderdemo.TraderDemoKt" />
|
<option name="MAIN_CLASS_NAME" value="net.corda.traderdemo.TraderDemoKt" />
|
||||||
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
|
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
|
||||||
<option name="PROGRAM_PARAMETERS" value="--role BUYER" />
|
<option name="PROGRAM_PARAMETERS" value="--role BUYER --certificates="build/trader-demo-nodes/Bank A/certificates"" />
|
||||||
<option name="WORKING_DIRECTORY" value="" />
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||||
<option name="ALTERNATIVE_JRE_PATH" />
|
<option name="ALTERNATIVE_JRE_PATH" />
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||||
<option name="MAIN_CLASS_NAME" value="net.corda.traderdemo.TraderDemoKt" />
|
<option name="MAIN_CLASS_NAME" value="net.corda.traderdemo.TraderDemoKt" />
|
||||||
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
|
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
|
||||||
<option name="PROGRAM_PARAMETERS" value="--role SELLER" />
|
<option name="PROGRAM_PARAMETERS" value="--role SELLER --certificates="build/trader-demo-nodes/Bank B/certificates"" />
|
||||||
<option name="WORKING_DIRECTORY" value="" />
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||||
<option name="ALTERNATIVE_JRE_PATH" />
|
<option name="ALTERNATIVE_JRE_PATH" />
|
||||||
|
16
README.md
16
README.md
@ -19,7 +19,13 @@ Read our full and planned feature list [here](https://docs.corda.net/inthebox.ht
|
|||||||
|
|
||||||
Firstly, read the [Getting started](https://docs.corda.net/getting-set-up.html) documentation.
|
Firstly, read the [Getting started](https://docs.corda.net/getting-set-up.html) documentation.
|
||||||
|
|
||||||
Watching the following webinars will give you a great introduction to Corda:
|
Next, use the following guides to set up your dev environment:
|
||||||
|
|
||||||
|
* If you are on **Windows** [use this getting started guide](https://www.corda.net/wp-content/uploads/2017/01/Corda-Windows-Quick-start-guide-1.pdf) which also explains through how to run the sample apps.
|
||||||
|
|
||||||
|
* Alternatively if you are on **Mac/Linux**, [watch this brief Webinar](https://vimeo.com/200167665) which walks through getting Corda, installing it, building it, running nodes and opening projects in IntelliJ.
|
||||||
|
|
||||||
|
After the above, watching the following webinars will give you a great introduction to Corda:
|
||||||
|
|
||||||
### Webinar 1 – [Introduction to Corda](https://vimeo.com/192757743/c2ec39c1e1)
|
### Webinar 1 – [Introduction to Corda](https://vimeo.com/192757743/c2ec39c1e1)
|
||||||
|
|
||||||
@ -72,3 +78,11 @@ Please read [here](./CONTRIBUTING.md).
|
|||||||
## License
|
## License
|
||||||
|
|
||||||
[Apache 2.0](./LICENCE)
|
[Apache 2.0](./LICENCE)
|
||||||
|
|
||||||
|
## Acknowledgements
|
||||||
|
|
||||||
|
![YourKit](https://www.yourkit.com/images/yklogo.png)
|
||||||
|
|
||||||
|
YourKit supports open source projects with its full-featured Java Profiler.
|
||||||
|
|
||||||
|
YourKit, LLC is the creator of [YourKit Java Profiler](https://www.yourkit.com/java/profiler/) and [YourKit .NET Profiler](https://www.yourkit.com/.net/profiler/), innovative and intelligent tools for profiling Java and .NET applications.
|
||||||
|
61
build.gradle
61
build.gradle
@ -4,19 +4,32 @@ buildscript {
|
|||||||
file("publish.properties").withInputStream { props.load(it) }
|
file("publish.properties").withInputStream { props.load(it) }
|
||||||
|
|
||||||
// Our version: bump this on release.
|
// Our version: bump this on release.
|
||||||
ext.corda_version = "0.7-SNAPSHOT"
|
ext.corda_version = "0.8-SNAPSHOT"
|
||||||
ext.gradle_plugins_version = props.getProperty("gradlePluginsVersion")
|
ext.gradle_plugins_version = props.getProperty("gradlePluginsVersion")
|
||||||
|
|
||||||
|
// Dependency versions. Can run 'gradle dependencyUpdates' to find new versions of things.
|
||||||
|
//
|
||||||
|
// TODO: Sort this alphabetically.
|
||||||
ext.kotlin_version = '1.0.5-2'
|
ext.kotlin_version = '1.0.5-2'
|
||||||
ext.quasar_version = '0.7.6'
|
ext.quasar_version = '0.7.6' // TODO: Upgrade to 0.7.7+ when Quasar bug 238 is resolved.
|
||||||
ext.asm_version = '0.5.3'
|
ext.asm_version = '0.5.3'
|
||||||
ext.artemis_version = '1.4.0'
|
ext.artemis_version = '1.5.1'
|
||||||
ext.jackson_version = '2.8.0.rc2'
|
ext.jackson_version = '2.8.5'
|
||||||
ext.jetty_version = '9.3.9.v20160517'
|
ext.jetty_version = '9.3.9.v20160517'
|
||||||
ext.jersey_version = '2.23.1'
|
ext.jersey_version = '2.25'
|
||||||
ext.jolokia_version = '2.0.0-M1'
|
ext.jolokia_version = '2.0.0-M3'
|
||||||
ext.assertj_version = '3.5.1'
|
ext.assertj_version = '3.6.1'
|
||||||
ext.log4j_version = '2.6.2'
|
ext.log4j_version = '2.7'
|
||||||
ext.bouncycastle_version = '1.54'
|
ext.bouncycastle_version = '1.56'
|
||||||
|
ext.guava_version = '19.0'
|
||||||
|
ext.quickcheck_version = '0.7'
|
||||||
|
ext.okhttp_version = '3.5.0'
|
||||||
|
ext.typesafe_config_version = '1.3.1'
|
||||||
|
ext.junit_version = '4.12'
|
||||||
|
ext.jopt_simple_version = '5.0.2'
|
||||||
|
ext.jansi_version = '1.14'
|
||||||
|
ext.hibernate_version = '5.2.6.Final'
|
||||||
|
ext.dokka_version = '0.9.13'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
@ -33,9 +46,8 @@ buildscript {
|
|||||||
classpath "net.corda.plugins:publish-utils:$gradle_plugins_version"
|
classpath "net.corda.plugins:publish-utils:$gradle_plugins_version"
|
||||||
classpath "net.corda.plugins:quasar-utils:$gradle_plugins_version"
|
classpath "net.corda.plugins:quasar-utils:$gradle_plugins_version"
|
||||||
classpath "net.corda.plugins:cordformation:$gradle_plugins_version"
|
classpath "net.corda.plugins:cordformation:$gradle_plugins_version"
|
||||||
|
classpath 'com.github.ben-manes:gradle-versions-plugin:0.13.0'
|
||||||
// Can run 'gradle dependencyUpdates' to find new versions of things.
|
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}"
|
||||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.12.0'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +63,7 @@ apply plugin: 'com.github.ben-manes.versions'
|
|||||||
apply plugin: 'net.corda.plugins.publish-utils'
|
apply plugin: 'net.corda.plugins.publish-utils'
|
||||||
apply plugin: 'net.corda.plugins.quasar-utils'
|
apply plugin: 'net.corda.plugins.quasar-utils'
|
||||||
apply plugin: 'net.corda.plugins.cordformation'
|
apply plugin: 'net.corda.plugins.cordformation'
|
||||||
|
apply plugin: 'org.jetbrains.dokka'
|
||||||
|
|
||||||
// We need the following three lines even though they're inside an allprojects {} block below because otherwise
|
// We need the following three lines even though they're inside an allprojects {} block below because otherwise
|
||||||
// IntelliJ gets confused when importing the project and ends up erasing and recreating the .idea directory, along
|
// IntelliJ gets confused when importing the project and ends up erasing and recreating the .idea directory, along
|
||||||
@ -95,7 +108,7 @@ repositories {
|
|||||||
// Required for building out the fat JAR.
|
// Required for building out the fat JAR.
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':node')
|
compile project(':node')
|
||||||
compile "com.google.guava:guava:19.0"
|
compile "com.google.guava:guava:$guava_version"
|
||||||
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +142,6 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
|
|||||||
networkMap "Controller"
|
networkMap "Controller"
|
||||||
node {
|
node {
|
||||||
name "Controller"
|
name "Controller"
|
||||||
dirName "controller"
|
|
||||||
nearestCity "London"
|
nearestCity "London"
|
||||||
advertisedServices = ["corda.notary.validating"]
|
advertisedServices = ["corda.notary.validating"]
|
||||||
artemisPort 10002
|
artemisPort 10002
|
||||||
@ -138,7 +150,6 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
|
|||||||
}
|
}
|
||||||
node {
|
node {
|
||||||
name "Bank A"
|
name "Bank A"
|
||||||
dirName "nodea"
|
|
||||||
nearestCity "London"
|
nearestCity "London"
|
||||||
advertisedServices = []
|
advertisedServices = []
|
||||||
artemisPort 10004
|
artemisPort 10004
|
||||||
@ -147,7 +158,6 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
|
|||||||
}
|
}
|
||||||
node {
|
node {
|
||||||
name "Bank B"
|
name "Bank B"
|
||||||
dirName "nodeb"
|
|
||||||
nearestCity "New York"
|
nearestCity "New York"
|
||||||
advertisedServices = []
|
advertisedServices = []
|
||||||
artemisPort 10006
|
artemisPort 10006
|
||||||
@ -178,3 +188,22 @@ bintrayConfig {
|
|||||||
email = 'dev@corda.net'
|
email = 'dev@corda.net'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// API docs
|
||||||
|
|
||||||
|
dokka {
|
||||||
|
moduleName = 'corda'
|
||||||
|
outputDirectory = 'docs/build/html/api/kotlin'
|
||||||
|
processConfigurations = ['compile']
|
||||||
|
sourceDirs = files('core/src/main/kotlin', 'client/src/main/kotlin', 'node/src/main/kotlin', 'finance/src/main/kotlin')
|
||||||
|
}
|
||||||
|
|
||||||
|
task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
|
||||||
|
moduleName = 'corda'
|
||||||
|
outputFormat = "javadoc"
|
||||||
|
outputDirectory = 'docs/build/html/api/javadoc'
|
||||||
|
processConfigurations = ['compile']
|
||||||
|
sourceDirs = files('core/src/main/kotlin', 'client/src/main/kotlin', 'node/src/main/kotlin', 'finance/src/main/kotlin')
|
||||||
|
}
|
||||||
|
|
||||||
|
task apidocs(dependsOn: ['dokka', 'dokkaJavadoc'])
|
@ -5,5 +5,6 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile "com.google.guava:guava:19.0"
|
// Cannot use ext.guava_version here :(
|
||||||
|
compile "com.google.guava:guava:20.0"
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ dependencies {
|
|||||||
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
|
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
|
||||||
compile "org.apache.logging.log4j:log4j-core:${log4j_version}"
|
compile "org.apache.logging.log4j:log4j-core:${log4j_version}"
|
||||||
|
|
||||||
compile "com.google.guava:guava:19.0"
|
compile "com.google.guava:guava:$guava_version"
|
||||||
|
|
||||||
// ReactFX: Functional reactive UI programming.
|
// ReactFX: Functional reactive UI programming.
|
||||||
compile 'org.reactfx:reactfx:2.0-M5'
|
compile 'org.reactfx:reactfx:2.0-M5'
|
||||||
@ -61,13 +61,13 @@ dependencies {
|
|||||||
compile "org.apache.activemq:artemis-core-client:${artemis_version}"
|
compile "org.apache.activemq:artemis-core-client:${artemis_version}"
|
||||||
|
|
||||||
// Unit testing helpers.
|
// Unit testing helpers.
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile "junit:junit:$junit_version"
|
||||||
testCompile "org.assertj:assertj-core:${assertj_version}"
|
testCompile "org.assertj:assertj-core:${assertj_version}"
|
||||||
|
|
||||||
testCompile project(':test-utils')
|
testCompile project(':test-utils')
|
||||||
|
|
||||||
// Integration test helpers
|
// Integration test helpers
|
||||||
integrationTestCompile 'junit:junit:4.12'
|
integrationTestCompile "junit:junit:$junit_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
task integrationTest(type: Test) {
|
task integrationTest(type: Test) {
|
||||||
|
@ -8,48 +8,26 @@ import net.corda.core.random63BitValue
|
|||||||
import net.corda.core.serialization.OpaqueBytes
|
import net.corda.core.serialization.OpaqueBytes
|
||||||
import net.corda.flows.CashCommand
|
import net.corda.flows.CashCommand
|
||||||
import net.corda.flows.CashFlow
|
import net.corda.flows.CashFlow
|
||||||
import net.corda.node.driver.NodeInfoAndConfig
|
import net.corda.node.driver.DriverBasedTest
|
||||||
|
import net.corda.node.driver.NodeHandle
|
||||||
import net.corda.node.driver.driver
|
import net.corda.node.driver.driver
|
||||||
import net.corda.node.services.User
|
import net.corda.node.services.User
|
||||||
import net.corda.node.services.config.configureTestSSL
|
|
||||||
import net.corda.node.services.messaging.ArtemisMessagingComponent.Companion.toHostAndPort
|
|
||||||
import net.corda.node.services.messaging.CordaRPCClient
|
import net.corda.node.services.messaging.CordaRPCClient
|
||||||
import net.corda.node.services.startFlowPermission
|
import net.corda.node.services.startFlowPermission
|
||||||
import net.corda.node.services.transactions.ValidatingNotaryService
|
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||||
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
|
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
|
||||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||||
import org.junit.After
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.util.concurrent.CountDownLatch
|
|
||||||
import kotlin.concurrent.thread
|
|
||||||
|
|
||||||
class CordaRPCClientTest {
|
|
||||||
|
|
||||||
|
class CordaRPCClientTest : DriverBasedTest() {
|
||||||
private val rpcUser = User("user1", "test", permissions = setOf(startFlowPermission<CashFlow>()))
|
private val rpcUser = User("user1", "test", permissions = setOf(startFlowPermission<CashFlow>()))
|
||||||
private val stopDriver = CountDownLatch(1)
|
private lateinit var node: NodeHandle
|
||||||
private var driverThread: Thread? = null
|
|
||||||
private lateinit var client: CordaRPCClient
|
private lateinit var client: CordaRPCClient
|
||||||
private lateinit var driverInfo: NodeInfoAndConfig
|
|
||||||
|
|
||||||
@Before
|
override fun setup() = driver(isDebug = true) {
|
||||||
fun start() {
|
node = startNode(rpcUsers = listOf(rpcUser), advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type))).getOrThrow()
|
||||||
val driverStarted = CountDownLatch(1)
|
client = node.rpcClientToNode()
|
||||||
driverThread = thread {
|
runTest()
|
||||||
driver(isDebug = true) {
|
|
||||||
driverInfo = startNode(rpcUsers = listOf(rpcUser), advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type))).getOrThrow()
|
|
||||||
client = CordaRPCClient(toHostAndPort(driverInfo.nodeInfo.address), configureTestSSL())
|
|
||||||
driverStarted.countDown()
|
|
||||||
stopDriver.await()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
driverStarted.await()
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
fun stop() {
|
|
||||||
stopDriver.countDown()
|
|
||||||
driverThread?.join()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -78,7 +56,9 @@ class CordaRPCClientTest {
|
|||||||
println("Creating proxy")
|
println("Creating proxy")
|
||||||
val proxy = client.proxy()
|
val proxy = client.proxy()
|
||||||
println("Starting flow")
|
println("Starting flow")
|
||||||
val flowHandle = proxy.startFlow(::CashFlow, CashCommand.IssueCash(20.DOLLARS, OpaqueBytes.of(0), driverInfo.nodeInfo.legalIdentity, driverInfo.nodeInfo.legalIdentity))
|
val flowHandle = proxy.startFlow(
|
||||||
|
::CashFlow,
|
||||||
|
CashCommand.IssueCash(20.DOLLARS, OpaqueBytes.of(0), node.nodeInfo.legalIdentity, node.nodeInfo.legalIdentity))
|
||||||
println("Started flow, waiting on result")
|
println("Started flow, waiting on result")
|
||||||
flowHandle.progress.subscribe {
|
flowHandle.progress.subscribe {
|
||||||
println("PROGRESS $it")
|
println("PROGRESS $it")
|
||||||
|
@ -9,7 +9,9 @@ import net.corda.core.contracts.PartyAndReference
|
|||||||
import net.corda.core.contracts.USD
|
import net.corda.core.contracts.USD
|
||||||
import net.corda.core.flows.StateMachineRunId
|
import net.corda.core.flows.StateMachineRunId
|
||||||
import net.corda.core.getOrThrow
|
import net.corda.core.getOrThrow
|
||||||
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.messaging.StateMachineUpdate
|
import net.corda.core.messaging.StateMachineUpdate
|
||||||
|
import net.corda.core.messaging.startFlow
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.services.NetworkMapCache
|
import net.corda.core.node.services.NetworkMapCache
|
||||||
import net.corda.core.node.services.ServiceInfo
|
import net.corda.core.node.services.ServiceInfo
|
||||||
@ -19,6 +21,7 @@ import net.corda.core.serialization.OpaqueBytes
|
|||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.flows.CashCommand
|
import net.corda.flows.CashCommand
|
||||||
import net.corda.flows.CashFlow
|
import net.corda.flows.CashFlow
|
||||||
|
import net.corda.node.driver.DriverBasedTest
|
||||||
import net.corda.node.driver.driver
|
import net.corda.node.driver.driver
|
||||||
import net.corda.node.services.User
|
import net.corda.node.services.User
|
||||||
import net.corda.node.services.config.configureTestSSL
|
import net.corda.node.services.config.configureTestSSL
|
||||||
@ -29,34 +32,23 @@ import net.corda.node.services.transactions.SimpleNotaryService
|
|||||||
import net.corda.testing.expect
|
import net.corda.testing.expect
|
||||||
import net.corda.testing.expectEvents
|
import net.corda.testing.expectEvents
|
||||||
import net.corda.testing.sequence
|
import net.corda.testing.sequence
|
||||||
import org.junit.After
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Observer
|
|
||||||
import java.util.concurrent.CountDownLatch
|
|
||||||
import kotlin.concurrent.thread
|
|
||||||
|
|
||||||
class NodeMonitorModelTest {
|
class NodeMonitorModelTest : DriverBasedTest() {
|
||||||
lateinit var aliceNode: NodeInfo
|
lateinit var aliceNode: NodeInfo
|
||||||
lateinit var notaryNode: NodeInfo
|
lateinit var notaryNode: NodeInfo
|
||||||
val stopDriver = CountDownLatch(1)
|
|
||||||
var driverThread: Thread? = null
|
|
||||||
|
|
||||||
|
lateinit var rpc: CordaRPCOps
|
||||||
lateinit var stateMachineTransactionMapping: Observable<StateMachineTransactionMapping>
|
lateinit var stateMachineTransactionMapping: Observable<StateMachineTransactionMapping>
|
||||||
lateinit var stateMachineUpdates: Observable<StateMachineUpdate>
|
lateinit var stateMachineUpdates: Observable<StateMachineUpdate>
|
||||||
lateinit var progressTracking: Observable<ProgressTrackingEvent>
|
lateinit var progressTracking: Observable<ProgressTrackingEvent>
|
||||||
lateinit var transactions: Observable<SignedTransaction>
|
lateinit var transactions: Observable<SignedTransaction>
|
||||||
lateinit var vaultUpdates: Observable<Vault.Update>
|
lateinit var vaultUpdates: Observable<Vault.Update>
|
||||||
lateinit var networkMapUpdates: Observable<NetworkMapCache.MapChange>
|
lateinit var networkMapUpdates: Observable<NetworkMapCache.MapChange>
|
||||||
lateinit var clientToService: Observer<CashCommand>
|
|
||||||
lateinit var newNode: (String) -> NodeInfo
|
lateinit var newNode: (String) -> NodeInfo
|
||||||
|
|
||||||
@Before
|
override fun setup() = driver {
|
||||||
fun start() {
|
|
||||||
val driverStarted = CountDownLatch(1)
|
|
||||||
driverThread = thread {
|
|
||||||
driver {
|
|
||||||
val cashUser = User("user1", "test", permissions = setOf(startFlowPermission<CashFlow>()))
|
val cashUser = User("user1", "test", permissions = setOf(startFlowPermission<CashFlow>()))
|
||||||
val aliceNodeFuture = startNode("Alice", rpcUsers = listOf(cashUser))
|
val aliceNodeFuture = startNode("Alice", rpcUsers = listOf(cashUser))
|
||||||
val notaryNodeFuture = startNode("Notary", advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
|
val notaryNodeFuture = startNode("Notary", advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
|
||||||
@ -72,20 +64,10 @@ class NodeMonitorModelTest {
|
|||||||
transactions = monitor.transactions.bufferUntilSubscribed()
|
transactions = monitor.transactions.bufferUntilSubscribed()
|
||||||
vaultUpdates = monitor.vaultUpdates.bufferUntilSubscribed()
|
vaultUpdates = monitor.vaultUpdates.bufferUntilSubscribed()
|
||||||
networkMapUpdates = monitor.networkMap.bufferUntilSubscribed()
|
networkMapUpdates = monitor.networkMap.bufferUntilSubscribed()
|
||||||
clientToService = monitor.clientToService
|
|
||||||
|
|
||||||
monitor.register(ArtemisMessagingComponent.toHostAndPort(aliceNode.address), configureTestSSL(), cashUser.username, cashUser.password)
|
monitor.register(ArtemisMessagingComponent.toHostAndPort(aliceNode.address), configureTestSSL(), cashUser.username, cashUser.password)
|
||||||
driverStarted.countDown()
|
rpc = monitor.proxyObservable.value!!
|
||||||
stopDriver.await()
|
runTest()
|
||||||
}
|
|
||||||
}
|
|
||||||
driverStarted.await()
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
fun stop() {
|
|
||||||
stopDriver.countDown()
|
|
||||||
driverThread?.join()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -112,7 +94,7 @@ class NodeMonitorModelTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `cash issue works end to end`() {
|
fun `cash issue works end to end`() {
|
||||||
clientToService.onNext(CashCommand.IssueCash(
|
rpc.startFlow(::CashFlow, CashCommand.IssueCash(
|
||||||
amount = Amount(100, USD),
|
amount = Amount(100, USD),
|
||||||
issueRef = OpaqueBytes(ByteArray(1, { 1 })),
|
issueRef = OpaqueBytes(ByteArray(1, { 1 })),
|
||||||
recipient = aliceNode.legalIdentity,
|
recipient = aliceNode.legalIdentity,
|
||||||
@ -137,14 +119,14 @@ class NodeMonitorModelTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `cash issue and move`() {
|
fun `cash issue and move`() {
|
||||||
clientToService.onNext(CashCommand.IssueCash(
|
rpc.startFlow(::CashFlow, CashCommand.IssueCash(
|
||||||
amount = Amount(100, USD),
|
amount = Amount(100, USD),
|
||||||
issueRef = OpaqueBytes(ByteArray(1, { 1 })),
|
issueRef = OpaqueBytes(ByteArray(1, { 1 })),
|
||||||
recipient = aliceNode.legalIdentity,
|
recipient = aliceNode.legalIdentity,
|
||||||
notary = notaryNode.notaryIdentity
|
notary = notaryNode.notaryIdentity
|
||||||
))
|
))
|
||||||
|
|
||||||
clientToService.onNext(CashCommand.PayCash(
|
rpc.startFlow(::CashFlow, CashCommand.PayCash(
|
||||||
amount = Amount(100, Issued(PartyAndReference(aliceNode.legalIdentity, OpaqueBytes(ByteArray(1, { 1 }))), USD)),
|
amount = Amount(100, Issued(PartyAndReference(aliceNode.legalIdentity, OpaqueBytes(ByteArray(1, { 1 }))), USD)),
|
||||||
recipient = aliceNode.legalIdentity
|
recipient = aliceNode.legalIdentity
|
||||||
))
|
))
|
||||||
|
@ -7,6 +7,7 @@ import javafx.collections.FXCollections
|
|||||||
import javafx.collections.ObservableList
|
import javafx.collections.ObservableList
|
||||||
import javafx.collections.ObservableMap
|
import javafx.collections.ObservableMap
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple utilities for converting an [rx.Observable] into a javafx [ObservableValue]/[ObservableList]
|
* Simple utilities for converting an [rx.Observable] into a javafx [ObservableValue]/[ObservableList]
|
||||||
@ -29,76 +30,46 @@ fun <A, B> Observable<A>.foldToObservableValue(initial: B, folderFun: (A, B) ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [foldToObservableList] takes an [rx.Observable] stream and creates an [ObservableList] out of it, while maintaining
|
* [fold] takes an [rx.Observable] stream and applies fold function on it, and collects all elements using the accumulator.
|
||||||
* an accumulator.
|
* @param accumulator The accumulator for accumulating elements.
|
||||||
* @param initialAccumulator The initial value of the accumulator.
|
|
||||||
* @param folderFun The transformation function to be called on the observable list when a new element is emitted on
|
* @param folderFun The transformation function to be called on the observable list when a new element is emitted on
|
||||||
* the stream, which should modify the list as needed.
|
* the stream, which should modify the list as needed.
|
||||||
*/
|
*/
|
||||||
fun <A, B, C> Observable<A>.foldToObservableList(
|
fun <T, R> Observable<T>.fold(accumulator: R, folderFun: (R, T) -> Unit): R {
|
||||||
initialAccumulator: C, folderFun: (A, C, ObservableList<B>) -> C
|
|
||||||
): ObservableList<B> {
|
|
||||||
val result = FXCollections.observableArrayList<B>()
|
|
||||||
/**
|
/**
|
||||||
* This capture is fine, as [Platform.runLater] runs closures in order
|
* This capture is fine, as [Platform.runLater] runs closures in order.
|
||||||
|
* The buffer is to avoid flooding FX thread with runnable.
|
||||||
*/
|
*/
|
||||||
var currentAccumulator = initialAccumulator
|
buffer(1, TimeUnit.SECONDS).subscribe {
|
||||||
subscribe {
|
if (it.isNotEmpty()) {
|
||||||
Platform.runLater {
|
Platform.runLater {
|
||||||
currentAccumulator = folderFun(it, currentAccumulator, result)
|
it.fold(accumulator) { list, item ->
|
||||||
|
folderFun.invoke(list, item)
|
||||||
|
list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
}
|
||||||
|
}
|
||||||
|
return accumulator
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [recordInSequence] records incoming events on the [rx.Observable] in sequence.
|
* [recordInSequence] records incoming events on the [rx.Observable] in sequence.
|
||||||
*/
|
*/
|
||||||
fun <A> Observable<A>.recordInSequence(): ObservableList<A> {
|
fun <A> Observable<A>.recordInSequence(): ObservableList<A> {
|
||||||
return foldToObservableList(Unit) { newElement, _unit, list ->
|
return fold(FXCollections.observableArrayList()) { list, newElement ->
|
||||||
list.add(newElement)
|
list.add(newElement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* [foldToObservableMap] takes an [rx.Observable] stream and creates an [ObservableMap] out of it, while maintaining
|
|
||||||
* an accumulator.
|
|
||||||
* @param initialAccumulator The initial value of the accumulator.
|
|
||||||
* @param folderFun The transformation function to be called on the observable map when a new element is emitted on
|
|
||||||
* the stream, which should modify the map as needed.
|
|
||||||
*/
|
|
||||||
fun <A, B, K, C> Observable<A>.foldToObservableMap(
|
|
||||||
initialAccumulator: C, folderFun: (A, C, ObservableMap<K, B>) -> C
|
|
||||||
): ObservableMap<K, out B> {
|
|
||||||
val result = FXCollections.observableHashMap<K, B>()
|
|
||||||
/**
|
|
||||||
* This capture is fine, as [Platform.runLater] runs closures in order
|
|
||||||
*/
|
|
||||||
var currentAccumulator = initialAccumulator
|
|
||||||
subscribe {
|
|
||||||
Platform.runLater {
|
|
||||||
currentAccumulator = folderFun(it, currentAccumulator, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This variant simply associates each event with its key.
|
* This variant simply associates each event with its key.
|
||||||
* @param toKey Function retrieving the key to associate with.
|
* @param toKey Function retrieving the key to associate with.
|
||||||
* @param merge The function to be called if there is an existing element at the key.
|
* @param merge The function to be called if there is an existing element at the key.
|
||||||
*/
|
*/
|
||||||
fun <A, K> Observable<A>.recordAsAssociation(
|
fun <A, K> Observable<A>.recordAsAssociation(toKey: (A) -> K, merge: (K, oldValue: A, newValue: A) -> A = { _key, _oldValue, newValue -> newValue }): ObservableMap<K, A> {
|
||||||
toKey: (A) -> K,
|
return fold(FXCollections.observableHashMap<K, A>()) { map, item ->
|
||||||
merge: (K, oldValue: A, newValue: A) -> A = { _key, _oldValue, newValue -> newValue }
|
val key = toKey(item)
|
||||||
): ObservableMap<K, out A> {
|
map[key] = map[key]?.let { merge(key, it, item) } ?: item
|
||||||
return foldToObservableMap(Unit) { newElement, _unit, map ->
|
|
||||||
val key = toKey(newElement)
|
|
||||||
val oldValue = map.get(key)
|
|
||||||
if (oldValue != null) {
|
|
||||||
map.set(key, merge(key, oldValue, newElement))
|
|
||||||
} else {
|
|
||||||
map.set(key, newElement)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import net.corda.core.crypto.Party
|
|||||||
import net.corda.core.serialization.OpaqueBytes
|
import net.corda.core.serialization.OpaqueBytes
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import net.corda.flows.CashCommand
|
import net.corda.flows.CashCommand
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Generator]s for incoming/outgoing events to/from the [WalletMonitorService]. Internally it keeps track of owned
|
* [Generator]s for incoming/outgoing events to/from the [WalletMonitorService]. Internally it keeps track of owned
|
||||||
@ -13,15 +14,15 @@ import net.corda.flows.CashCommand
|
|||||||
*/
|
*/
|
||||||
class EventGenerator(
|
class EventGenerator(
|
||||||
val parties: List<Party>,
|
val parties: List<Party>,
|
||||||
val notary: Party
|
val notary: Party,
|
||||||
|
val currencies: List<Currency> = listOf(USD, GBP, CHF),
|
||||||
|
val issuers: List<Party> = parties
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private var vault = listOf<StateAndRef<Cash.State>>()
|
private var vault = listOf<StateAndRef<Cash.State>>()
|
||||||
|
|
||||||
val issuerGenerator =
|
val issuerGenerator =
|
||||||
Generator.pickOne(parties).combine(Generator.intRange(0, 1)) { party, ref -> party.ref(ref.toByte()) }
|
Generator.pickOne(issuers).combine(Generator.intRange(0, 1)) { party, ref -> party.ref(ref.toByte()) }
|
||||||
|
|
||||||
val currencies = setOf(USD, GBP, CHF).toList() // + Currency.getAvailableCurrencies().toList().subList(0, 3).toSet()).toList()
|
|
||||||
val currencyGenerator = Generator.pickOne(currencies)
|
val currencyGenerator = Generator.pickOne(currencies)
|
||||||
|
|
||||||
val issuedGenerator = issuerGenerator.combine(currencyGenerator) { issuer, currency -> Issued(issuer, currency) }
|
val issuedGenerator = issuerGenerator.combine(currencyGenerator) { issuer, currency -> Issued(issuer, currency) }
|
||||||
@ -93,8 +94,11 @@ class EventGenerator(
|
|||||||
1.0 to moveCashGenerator
|
1.0 to moveCashGenerator
|
||||||
)
|
)
|
||||||
|
|
||||||
val bankOfCordaCommandGenerator = Generator.frequency(
|
val bankOfCordaExitGenerator = Generator.frequency(
|
||||||
0.6 to issueCashGenerator,
|
|
||||||
0.4 to exitCashGenerator
|
0.4 to exitCashGenerator
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val bankOfCordaIssueGenerator = Generator.frequency(
|
||||||
|
0.6 to issueCashGenerator
|
||||||
|
)
|
||||||
}
|
}
|
@ -1,19 +1,19 @@
|
|||||||
package net.corda.client.model
|
package net.corda.client.model
|
||||||
|
|
||||||
|
import javafx.collections.FXCollections
|
||||||
import javafx.collections.ObservableList
|
import javafx.collections.ObservableList
|
||||||
import kotlinx.support.jdk8.collections.removeIf
|
import kotlinx.support.jdk8.collections.removeIf
|
||||||
import net.corda.client.fxutils.foldToObservableList
|
import net.corda.client.fxutils.fold
|
||||||
import net.corda.client.fxutils.map
|
import net.corda.client.fxutils.map
|
||||||
import net.corda.contracts.asset.Cash
|
import net.corda.contracts.asset.Cash
|
||||||
import net.corda.core.contracts.ContractState
|
import net.corda.core.contracts.ContractState
|
||||||
import net.corda.core.contracts.StateAndRef
|
import net.corda.core.contracts.StateAndRef
|
||||||
import net.corda.core.contracts.StateRef
|
|
||||||
import net.corda.core.node.services.Vault
|
import net.corda.core.node.services.Vault
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
data class Diff<out T : ContractState>(
|
data class Diff<out T : ContractState>(
|
||||||
val added: Collection<StateAndRef<T>>,
|
val added: Collection<StateAndRef<T>>,
|
||||||
val removed: Collection<StateRef>
|
val removed: Collection<StateAndRef<T>>
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,13 +26,11 @@ class ContractStateModel {
|
|||||||
Diff(it.produced, it.consumed)
|
Diff(it.produced, it.consumed)
|
||||||
}
|
}
|
||||||
private val cashStatesDiff: Observable<Diff<Cash.State>> = contractStatesDiff.map {
|
private val cashStatesDiff: Observable<Diff<Cash.State>> = contractStatesDiff.map {
|
||||||
// We can't filter removed hashes here as we don't have type info
|
Diff(it.added.filterCashStateAndRefs(), it.removed.filterCashStateAndRefs())
|
||||||
Diff(it.added.filterCashStateAndRefs(), it.removed)
|
|
||||||
}
|
}
|
||||||
val cashStates: ObservableList<StateAndRef<Cash.State>> =
|
val cashStates: ObservableList<StateAndRef<Cash.State>> = cashStatesDiff.fold(FXCollections.observableArrayList()) { list, statesDiff ->
|
||||||
cashStatesDiff.foldToObservableList(Unit) { statesDiff, _accumulator, observableList ->
|
list.removeIf { it in statesDiff.removed }
|
||||||
observableList.removeIf { it.ref in statesDiff.removed }
|
list.addAll(statesDiff.added)
|
||||||
observableList.addAll(statesDiff.added)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val cash = cashStates.map { it.state.data.amount }
|
val cash = cashStates.map { it.state.data.amount }
|
||||||
|
@ -1,115 +0,0 @@
|
|||||||
package net.corda.client.model
|
|
||||||
|
|
||||||
import javafx.beans.property.SimpleObjectProperty
|
|
||||||
import javafx.beans.value.ObservableValue
|
|
||||||
import javafx.collections.ObservableList
|
|
||||||
import javafx.collections.ObservableMap
|
|
||||||
import net.corda.client.fxutils.*
|
|
||||||
import net.corda.core.contracts.ContractState
|
|
||||||
import net.corda.core.contracts.StateAndRef
|
|
||||||
import net.corda.core.contracts.StateRef
|
|
||||||
import net.corda.core.crypto.SecureHash
|
|
||||||
import net.corda.core.flows.StateMachineRunId
|
|
||||||
import net.corda.core.messaging.StateMachineUpdate
|
|
||||||
import net.corda.core.transactions.SignedTransaction
|
|
||||||
import org.fxmisc.easybind.EasyBind
|
|
||||||
|
|
||||||
data class GatheredTransactionData(
|
|
||||||
val transaction: PartiallyResolvedTransaction,
|
|
||||||
val stateMachines: ObservableList<out StateMachineData>
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [PartiallyResolvedTransaction] holds a [SignedTransaction] that has zero or more inputs resolved. The intent is
|
|
||||||
* to prepare clients for cases where an input can only be resolved in the future/cannot be resolved at all (for example
|
|
||||||
* because of permissioning)
|
|
||||||
*/
|
|
||||||
data class PartiallyResolvedTransaction(
|
|
||||||
val transaction: SignedTransaction,
|
|
||||||
val inputs: List<ObservableValue<InputResolution>>) {
|
|
||||||
val id = transaction.id
|
|
||||||
|
|
||||||
sealed class InputResolution(val stateRef: StateRef) {
|
|
||||||
class Unresolved(stateRef: StateRef) : InputResolution(stateRef)
|
|
||||||
class Resolved(val stateAndRef: StateAndRef<ContractState>) : InputResolution(stateAndRef.ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun fromSignedTransaction(
|
|
||||||
transaction: SignedTransaction,
|
|
||||||
transactions: ObservableMap<SecureHash, SignedTransaction>
|
|
||||||
) = PartiallyResolvedTransaction(
|
|
||||||
transaction = transaction,
|
|
||||||
inputs = transaction.tx.inputs.map { stateRef ->
|
|
||||||
EasyBind.map(transactions.getObservableValue(stateRef.txhash)) {
|
|
||||||
if (it == null) {
|
|
||||||
InputResolution.Unresolved(stateRef)
|
|
||||||
} else {
|
|
||||||
InputResolution.Resolved(it.tx.outRef(stateRef.index))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class TransactionCreateStatus(val message: String?) {
|
|
||||||
class Started(message: String?) : TransactionCreateStatus(message)
|
|
||||||
class Failed(message: String?) : TransactionCreateStatus(message)
|
|
||||||
|
|
||||||
override fun toString(): String = message ?: javaClass.simpleName
|
|
||||||
}
|
|
||||||
|
|
||||||
data class FlowStatus(
|
|
||||||
val status: String
|
|
||||||
)
|
|
||||||
|
|
||||||
sealed class StateMachineStatus(val stateMachineName: String) {
|
|
||||||
class Added(stateMachineName: String) : StateMachineStatus(stateMachineName)
|
|
||||||
class Removed(stateMachineName: String) : StateMachineStatus(stateMachineName)
|
|
||||||
|
|
||||||
override fun toString(): String = "${javaClass.simpleName}($stateMachineName)"
|
|
||||||
}
|
|
||||||
|
|
||||||
data class StateMachineData(
|
|
||||||
val id: StateMachineRunId,
|
|
||||||
val flowStatus: ObservableValue<FlowStatus?>,
|
|
||||||
val stateMachineStatus: ObservableValue<StateMachineStatus>
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This model provides an observable list of transactions and what state machines/flows recorded them
|
|
||||||
*/
|
|
||||||
class GatheredTransactionDataModel {
|
|
||||||
private val transactions by observable(NodeMonitorModel::transactions)
|
|
||||||
private val stateMachineUpdates by observable(NodeMonitorModel::stateMachineUpdates)
|
|
||||||
private val progressTracking by observable(NodeMonitorModel::progressTracking)
|
|
||||||
private val stateMachineTransactionMapping by observable(NodeMonitorModel::stateMachineTransactionMapping)
|
|
||||||
|
|
||||||
private val collectedTransactions = transactions.recordInSequence()
|
|
||||||
private val transactionMap = collectedTransactions.associateBy(SignedTransaction::id)
|
|
||||||
private val progressEvents = progressTracking.recordAsAssociation(ProgressTrackingEvent::stateMachineId)
|
|
||||||
private val stateMachineStatus = stateMachineUpdates.foldToObservableMap(Unit) { update, _unit, map: ObservableMap<StateMachineRunId, SimpleObjectProperty<StateMachineStatus>> ->
|
|
||||||
when (update) {
|
|
||||||
is StateMachineUpdate.Added -> {
|
|
||||||
val added: SimpleObjectProperty<StateMachineStatus> =
|
|
||||||
SimpleObjectProperty(StateMachineStatus.Added(update.stateMachineInfo.flowLogicClassName))
|
|
||||||
map[update.id] = added
|
|
||||||
}
|
|
||||||
is StateMachineUpdate.Removed -> {
|
|
||||||
val added = map[update.id]
|
|
||||||
added ?: throw Exception("State machine removed with unknown id ${update.id}")
|
|
||||||
added.set(StateMachineStatus.Removed(added.value.stateMachineName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
|
|
||||||
StateMachineData(id, progress.map { it?.let { FlowStatus(it.message) } }, status)
|
|
||||||
}.getObservableValues()
|
|
||||||
// TODO : Create a new screen for state machines.
|
|
||||||
private val stateMachineDataMap = stateMachineDataList.associateBy(StateMachineData::id)
|
|
||||||
private val smTxMappingList = stateMachineTransactionMapping.recordInSequence()
|
|
||||||
val partiallyResolvedTransactions = collectedTransactions.map {
|
|
||||||
PartiallyResolvedTransaction.fromSignedTransaction(it, transactionMap)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +1,12 @@
|
|||||||
package net.corda.client.model
|
package net.corda.client.model
|
||||||
|
|
||||||
import javafx.beans.value.ObservableValue
|
import javafx.beans.value.ObservableValue
|
||||||
|
import javafx.collections.FXCollections
|
||||||
import javafx.collections.ObservableList
|
import javafx.collections.ObservableList
|
||||||
import kotlinx.support.jdk8.collections.removeIf
|
import kotlinx.support.jdk8.collections.removeIf
|
||||||
import net.corda.client.fxutils.firstOrDefault
|
import net.corda.client.fxutils.firstOrDefault
|
||||||
import net.corda.client.fxutils.firstOrNullObservable
|
import net.corda.client.fxutils.firstOrNullObservable
|
||||||
import net.corda.client.fxutils.foldToObservableList
|
import net.corda.client.fxutils.fold
|
||||||
import net.corda.client.fxutils.map
|
import net.corda.client.fxutils.map
|
||||||
import net.corda.core.crypto.CompositeKey
|
import net.corda.core.crypto.CompositeKey
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
@ -17,15 +18,15 @@ class NetworkIdentityModel {
|
|||||||
private val networkIdentityObservable by observable(NodeMonitorModel::networkMap)
|
private val networkIdentityObservable by observable(NodeMonitorModel::networkMap)
|
||||||
|
|
||||||
val networkIdentities: ObservableList<NodeInfo> =
|
val networkIdentities: ObservableList<NodeInfo> =
|
||||||
networkIdentityObservable.foldToObservableList(Unit) { update, _accumulator, observableList ->
|
networkIdentityObservable.fold(FXCollections.observableArrayList()) { list, update ->
|
||||||
observableList.removeIf {
|
list.removeIf {
|
||||||
when (update) {
|
when (update) {
|
||||||
is MapChange.Removed -> it == update.node
|
is MapChange.Removed -> it == update.node
|
||||||
is MapChange.Modified -> it == update.previousNode
|
is MapChange.Modified -> it == update.previousNode
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
observableList.addAll(update.node)
|
list.addAll(update.node)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable)
|
private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable)
|
||||||
|
@ -6,14 +6,11 @@ import net.corda.core.flows.StateMachineRunId
|
|||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.messaging.StateMachineInfo
|
import net.corda.core.messaging.StateMachineInfo
|
||||||
import net.corda.core.messaging.StateMachineUpdate
|
import net.corda.core.messaging.StateMachineUpdate
|
||||||
import net.corda.core.messaging.startFlow
|
|
||||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
import net.corda.core.node.services.NetworkMapCache.MapChange
|
||||||
import net.corda.core.node.services.StateMachineTransactionMapping
|
import net.corda.core.node.services.StateMachineTransactionMapping
|
||||||
import net.corda.core.node.services.Vault
|
import net.corda.core.node.services.Vault
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.flows.CashCommand
|
import net.corda.node.services.config.SSLConfiguration
|
||||||
import net.corda.flows.CashFlow
|
|
||||||
import net.corda.node.services.config.NodeSSLConfiguration
|
|
||||||
import net.corda.node.services.messaging.CordaRPCClient
|
import net.corda.node.services.messaging.CordaRPCClient
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
@ -48,16 +45,13 @@ class NodeMonitorModel {
|
|||||||
val progressTracking: Observable<ProgressTrackingEvent> = progressTrackingSubject
|
val progressTracking: Observable<ProgressTrackingEvent> = progressTrackingSubject
|
||||||
val networkMap: Observable<MapChange> = networkMapSubject
|
val networkMap: Observable<MapChange> = networkMapSubject
|
||||||
|
|
||||||
private val clientToServiceSource = PublishSubject.create<CashCommand>()
|
|
||||||
val clientToService: PublishSubject<CashCommand> = clientToServiceSource
|
|
||||||
|
|
||||||
val proxyObservable = SimpleObjectProperty<CordaRPCOps?>()
|
val proxyObservable = SimpleObjectProperty<CordaRPCOps?>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register for updates to/from a given vault.
|
* Register for updates to/from a given vault.
|
||||||
* TODO provide an unsubscribe mechanism
|
* TODO provide an unsubscribe mechanism
|
||||||
*/
|
*/
|
||||||
fun register(nodeHostAndPort: HostAndPort, sslConfig: NodeSSLConfiguration, username: String, password: String) {
|
fun register(nodeHostAndPort: HostAndPort, sslConfig: SSLConfiguration, username: String, password: String) {
|
||||||
val client = CordaRPCClient(nodeHostAndPort, sslConfig)
|
val client = CordaRPCClient(nodeHostAndPort, sslConfig)
|
||||||
client.start(username, password)
|
client.start(username, password)
|
||||||
val proxy = client.proxy()
|
val proxy = client.proxy()
|
||||||
@ -98,10 +92,6 @@ class NodeMonitorModel {
|
|||||||
val (parties, futurePartyUpdate) = proxy.networkMapUpdates()
|
val (parties, futurePartyUpdate) = proxy.networkMapUpdates()
|
||||||
futurePartyUpdate.startWith(parties.map { MapChange.Added(it) }).subscribe(networkMapSubject)
|
futurePartyUpdate.startWith(parties.map { MapChange.Added(it) }).subscribe(networkMapSubject)
|
||||||
|
|
||||||
// Client -> Service
|
|
||||||
clientToServiceSource.subscribe {
|
|
||||||
proxy.startFlow(::CashFlow, it)
|
|
||||||
}
|
|
||||||
proxyObservable.set(proxy)
|
proxyObservable.set(proxy)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
package net.corda.client.model
|
||||||
|
|
||||||
|
import javafx.beans.property.SimpleObjectProperty
|
||||||
|
import javafx.beans.value.ObservableValue
|
||||||
|
import javafx.collections.FXCollections
|
||||||
|
import javafx.collections.ObservableList
|
||||||
|
import javafx.collections.ObservableMap
|
||||||
|
import net.corda.client.fxutils.*
|
||||||
|
import net.corda.core.contracts.ContractState
|
||||||
|
import net.corda.core.contracts.StateAndRef
|
||||||
|
import net.corda.core.contracts.StateRef
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.flows.StateMachineRunId
|
||||||
|
import net.corda.core.messaging.StateMachineUpdate
|
||||||
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
import org.fxmisc.easybind.EasyBind
|
||||||
|
|
||||||
|
data class GatheredTransactionData(
|
||||||
|
val transaction: PartiallyResolvedTransaction,
|
||||||
|
val stateMachines: ObservableList<out StateMachineData>
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [PartiallyResolvedTransaction] holds a [SignedTransaction] that has zero or more inputs resolved. The intent is
|
||||||
|
* to prepare clients for cases where an input can only be resolved in the future/cannot be resolved at all (for example
|
||||||
|
* because of permissioning)
|
||||||
|
*/
|
||||||
|
data class PartiallyResolvedTransaction(
|
||||||
|
val transaction: SignedTransaction,
|
||||||
|
val inputs: List<ObservableValue<InputResolution>>) {
|
||||||
|
val id = transaction.id
|
||||||
|
|
||||||
|
sealed class InputResolution(val stateRef: StateRef) {
|
||||||
|
class Unresolved(stateRef: StateRef) : InputResolution(stateRef)
|
||||||
|
class Resolved(val stateAndRef: StateAndRef<ContractState>) : InputResolution(stateAndRef.ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromSignedTransaction(
|
||||||
|
transaction: SignedTransaction,
|
||||||
|
transactions: ObservableMap<SecureHash, SignedTransaction>
|
||||||
|
) = PartiallyResolvedTransaction(
|
||||||
|
transaction = transaction,
|
||||||
|
inputs = transaction.tx.inputs.map { stateRef ->
|
||||||
|
EasyBind.map(transactions.getObservableValue(stateRef.txhash)) {
|
||||||
|
if (it == null) {
|
||||||
|
InputResolution.Unresolved(stateRef)
|
||||||
|
} else {
|
||||||
|
InputResolution.Resolved(it.tx.outRef(stateRef.index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class TransactionCreateStatus(val message: String?) {
|
||||||
|
class Started(message: String?) : TransactionCreateStatus(message)
|
||||||
|
class Failed(message: String?) : TransactionCreateStatus(message)
|
||||||
|
|
||||||
|
override fun toString(): String = message ?: javaClass.simpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
data class FlowStatus(
|
||||||
|
val status: String
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed class StateMachineStatus(val stateMachineName: String) {
|
||||||
|
class Added(stateMachineName: String) : StateMachineStatus(stateMachineName)
|
||||||
|
class Removed(stateMachineName: String) : StateMachineStatus(stateMachineName)
|
||||||
|
|
||||||
|
override fun toString(): String = "${javaClass.simpleName}($stateMachineName)"
|
||||||
|
}
|
||||||
|
|
||||||
|
data class StateMachineData(
|
||||||
|
val id: StateMachineRunId,
|
||||||
|
val flowStatus: ObservableValue<FlowStatus?>,
|
||||||
|
val stateMachineStatus: ObservableValue<StateMachineStatus>
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This model provides an observable list of transactions and what state machines/flows recorded them
|
||||||
|
*/
|
||||||
|
class TransactionDataModel {
|
||||||
|
private val transactions by observable(NodeMonitorModel::transactions)
|
||||||
|
private val stateMachineUpdates by observable(NodeMonitorModel::stateMachineUpdates)
|
||||||
|
private val progressTracking by observable(NodeMonitorModel::progressTracking)
|
||||||
|
private val stateMachineTransactionMapping by observable(NodeMonitorModel::stateMachineTransactionMapping)
|
||||||
|
|
||||||
|
private val collectedTransactions = transactions.recordInSequence()
|
||||||
|
private val transactionMap = collectedTransactions.associateBy(SignedTransaction::id)
|
||||||
|
private val progressEvents = progressTracking.recordAsAssociation(ProgressTrackingEvent::stateMachineId)
|
||||||
|
private val stateMachineStatus = stateMachineUpdates.fold(FXCollections.observableHashMap<StateMachineRunId, SimpleObjectProperty<StateMachineStatus>>()) { map, update ->
|
||||||
|
when (update) {
|
||||||
|
is StateMachineUpdate.Added -> {
|
||||||
|
val added: SimpleObjectProperty<StateMachineStatus> =
|
||||||
|
SimpleObjectProperty(StateMachineStatus.Added(update.stateMachineInfo.flowLogicClassName))
|
||||||
|
map[update.id] = added
|
||||||
|
}
|
||||||
|
is StateMachineUpdate.Removed -> {
|
||||||
|
val added = map[update.id]
|
||||||
|
added ?: throw Exception("State machine removed with unknown id ${update.id}")
|
||||||
|
added.set(StateMachineStatus.Removed(added.value.stateMachineName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
|
||||||
|
StateMachineData(id, progress.map { it?.let { FlowStatus(it.message) } }, status)
|
||||||
|
}.getObservableValues()
|
||||||
|
// TODO : Create a new screen for state machines.
|
||||||
|
private val stateMachineDataMap = stateMachineDataList.associateBy(StateMachineData::id)
|
||||||
|
private val smTxMappingList = stateMachineTransactionMapping.recordInSequence()
|
||||||
|
val partiallyResolvedTransactions = collectedTransactions.map {
|
||||||
|
PartiallyResolvedTransaction.fromSignedTransaction(it, transactionMap)
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,3 @@
|
|||||||
basedir : "./nodea"
|
|
||||||
myLegalName : "Bank A"
|
myLegalName : "Bank A"
|
||||||
nearestCity : "London"
|
nearestCity : "London"
|
||||||
keyStorePassword : "cordacadevpass"
|
keyStorePassword : "cordacadevpass"
|
||||||
@ -6,5 +5,8 @@ trustStorePassword : "trustpass"
|
|||||||
artemisAddress : "localhost:31337"
|
artemisAddress : "localhost:31337"
|
||||||
webAddress : "localhost:31339"
|
webAddress : "localhost:31339"
|
||||||
extraAdvertisedServiceIds: "corda.interest_rates"
|
extraAdvertisedServiceIds: "corda.interest_rates"
|
||||||
networkMapAddress : "localhost:12345"
|
networkMapService : {
|
||||||
|
address : "localhost:12345"
|
||||||
|
legalName : "Network Map Service"
|
||||||
|
}
|
||||||
useHTTPS : false
|
useHTTPS : false
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
basedir : "./nodeb"
|
|
||||||
myLegalName : "Bank B"
|
myLegalName : "Bank B"
|
||||||
nearestCity : "London"
|
nearestCity : "London"
|
||||||
keyStorePassword : "cordacadevpass"
|
keyStorePassword : "cordacadevpass"
|
||||||
@ -6,5 +5,8 @@ trustStorePassword : "trustpass"
|
|||||||
artemisAddress : "localhost:31338"
|
artemisAddress : "localhost:31338"
|
||||||
webAddress : "localhost:31340"
|
webAddress : "localhost:31340"
|
||||||
extraAdvertisedServiceIds: "corda.interest_rates"
|
extraAdvertisedServiceIds: "corda.interest_rates"
|
||||||
networkMapAddress : "localhost:12345"
|
networkMapService : {
|
||||||
|
address : "localhost:12345"
|
||||||
|
legalName : "Network Map Service"
|
||||||
|
}
|
||||||
useHTTPS : false
|
useHTTPS : false
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
basedir : "./nameserver"
|
|
||||||
myLegalName : "Notary Service"
|
myLegalName : "Notary Service"
|
||||||
nearestCity : "London"
|
nearestCity : "London"
|
||||||
keyStorePassword : "cordacadevpass"
|
keyStorePassword : "cordacadevpass"
|
||||||
|
@ -31,11 +31,11 @@ sourceSets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile "junit:junit:$junit_version"
|
||||||
testCompile "commons-fileupload:commons-fileupload:1.3.2"
|
testCompile "commons-fileupload:commons-fileupload:1.3.2"
|
||||||
|
|
||||||
// Guava: Google test library (collections test suite)
|
// Guava: Google test library (collections test suite)
|
||||||
testCompile "com.google.guava:guava-testlib:19.0"
|
testCompile "com.google.guava:guava-testlib:$guava_version"
|
||||||
|
|
||||||
// Bring in the MockNode infrastructure for writing protocol unit tests.
|
// Bring in the MockNode infrastructure for writing protocol unit tests.
|
||||||
testCompile project(":node")
|
testCompile project(":node")
|
||||||
@ -43,7 +43,7 @@ dependencies {
|
|||||||
|
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
compile "org.jetbrains.kotlinx:kotlinx-support-jdk8:0.2"
|
compile "org.jetbrains.kotlinx:kotlinx-support-jdk8:0.3"
|
||||||
compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||||
|
|
||||||
// Thread safety annotations
|
// Thread safety annotations
|
||||||
@ -56,18 +56,18 @@ dependencies {
|
|||||||
// AssertJ: for fluent assertions for testing
|
// AssertJ: for fluent assertions for testing
|
||||||
testCompile "org.assertj:assertj-core:${assertj_version}"
|
testCompile "org.assertj:assertj-core:${assertj_version}"
|
||||||
|
|
||||||
compile 'com.pholser:junit-quickcheck-core:0.6'
|
compile "com.pholser:junit-quickcheck-core:$quickcheck_version"
|
||||||
compile 'com.pholser:junit-quickcheck-generators:0.6'
|
compile "com.pholser:junit-quickcheck-generators:$quickcheck_version"
|
||||||
|
|
||||||
// Guava: Google utilities library.
|
// Guava: Google utilities library.
|
||||||
compile "com.google.guava:guava:19.0"
|
compile "com.google.guava:guava:$guava_version"
|
||||||
|
|
||||||
// RxJava: observable streams of events.
|
// RxJava: observable streams of events.
|
||||||
compile "io.reactivex:rxjava:1.1.6"
|
compile "io.reactivex:rxjava:1.2.4"
|
||||||
|
|
||||||
// Kryo: object graph serialization.
|
// Kryo: object graph serialization.
|
||||||
compile "com.esotericsoftware:kryo:4.0.0"
|
compile "com.esotericsoftware:kryo:4.0.0"
|
||||||
compile "de.javakaffee:kryo-serializers:0.38"
|
compile "de.javakaffee:kryo-serializers:0.41"
|
||||||
|
|
||||||
// Apache JEXL: An embeddable expression evaluation library.
|
// Apache JEXL: An embeddable expression evaluation library.
|
||||||
// This may be temporary until we experiment with other ways to do on-the-fly contract specialisation via an API.
|
// This may be temporary until we experiment with other ways to do on-the-fly contract specialisation via an API.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// TODO Move out the Kotlin specific stuff into a separate file
|
||||||
@file:JvmName("Utils")
|
@file:JvmName("Utils")
|
||||||
|
|
||||||
package net.corda.core
|
package net.corda.core
|
||||||
@ -26,10 +27,7 @@ import java.nio.file.*
|
|||||||
import java.nio.file.attribute.FileAttribute
|
import java.nio.file.attribute.FileAttribute
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.temporal.Temporal
|
import java.time.temporal.Temporal
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.*
|
||||||
import java.util.concurrent.ExecutionException
|
|
||||||
import java.util.concurrent.Executor
|
|
||||||
import java.util.concurrent.Future
|
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
import java.util.function.BiConsumer
|
import java.util.function.BiConsumer
|
||||||
import java.util.stream.Stream
|
import java.util.stream.Stream
|
||||||
@ -67,9 +65,9 @@ infix fun Long.checkedAdd(b: Long) = Math.addExact(this, b)
|
|||||||
fun random63BitValue(): Long = Math.abs(newSecureRandom().nextLong())
|
fun random63BitValue(): Long = Math.abs(newSecureRandom().nextLong())
|
||||||
|
|
||||||
/** Same as [Future.get] but with a more descriptive name, and doesn't throw [ExecutionException], instead throwing its cause */
|
/** Same as [Future.get] but with a more descriptive name, and doesn't throw [ExecutionException], instead throwing its cause */
|
||||||
fun <T> Future<T>.getOrThrow(): T {
|
fun <T> Future<T>.getOrThrow(timeout: Duration? = null): T {
|
||||||
try {
|
return try {
|
||||||
return get()
|
if (timeout == null) get() else get(timeout.toNanos(), TimeUnit.NANOSECONDS)
|
||||||
} catch (e: ExecutionException) {
|
} catch (e: ExecutionException) {
|
||||||
throw e.cause!!
|
throw e.cause!!
|
||||||
}
|
}
|
||||||
@ -204,19 +202,31 @@ fun <T> List<T>.randomOrNull(predicate: (T) -> Boolean) = filter(predicate).rand
|
|||||||
// An alias that can sometimes make code clearer to read.
|
// An alias that can sometimes make code clearer to read.
|
||||||
val RunOnCallerThread: Executor = MoreExecutors.directExecutor()
|
val RunOnCallerThread: Executor = MoreExecutors.directExecutor()
|
||||||
|
|
||||||
|
inline fun elapsedTime(block: () -> Unit): Duration {
|
||||||
|
val start = System.nanoTime()
|
||||||
|
block()
|
||||||
|
val end = System.nanoTime()
|
||||||
|
return Duration.ofNanos(end-start)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Add inline back when a new Kotlin version is released and check if the java.lang.VerifyError
|
// TODO: Add inline back when a new Kotlin version is released and check if the java.lang.VerifyError
|
||||||
// returns in the IRSSimulationTest. If not, commit the inline back.
|
// returns in the IRSSimulationTest. If not, commit the inline back.
|
||||||
fun <T> logElapsedTime(label: String, logger: Logger? = null, body: () -> T): T {
|
fun <T> logElapsedTime(label: String, logger: Logger? = null, body: () -> T): T {
|
||||||
val now = System.currentTimeMillis()
|
// Use nanoTime as it's monotonic.
|
||||||
val r = body()
|
val now = System.nanoTime()
|
||||||
val elapsed = System.currentTimeMillis() - now
|
try {
|
||||||
|
return body()
|
||||||
|
} finally {
|
||||||
|
val elapsed = Duration.ofNanos(System.nanoTime() - now).toMillis()
|
||||||
if (logger != null)
|
if (logger != null)
|
||||||
logger.info("$label took $elapsed msec")
|
logger.info("$label took $elapsed msec")
|
||||||
else
|
else
|
||||||
println("$label took $elapsed msec")
|
println("$label took $elapsed msec")
|
||||||
return r
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> Logger.logElapsedTime(label: String, body: () -> T): T = logElapsedTime(label, this, body)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A threadbox is a simple utility that makes it harder to forget to take a lock before accessing some shared state.
|
* A threadbox is a simple utility that makes it harder to forget to take a lock before accessing some shared state.
|
||||||
* Simply define a private class to hold the data that must be grouped under the same lock, and then pass the only
|
* Simply define a private class to hold the data that must be grouped under the same lock, and then pass the only
|
||||||
@ -271,19 +281,17 @@ class TransientProperty<out T>(private val initializer: () -> T) {
|
|||||||
/**
|
/**
|
||||||
* Given a path to a zip file, extracts it to the given directory.
|
* Given a path to a zip file, extracts it to the given directory.
|
||||||
*/
|
*/
|
||||||
fun extractZipFile(zipPath: Path, toPath: Path) {
|
fun extractZipFile(zipFile: Path, toDirectory: Path) {
|
||||||
val normalisedToPath = toPath.normalize()
|
val normalisedDirectory = toDirectory.normalize().createDirectories()
|
||||||
normalisedToPath.createDirectories()
|
|
||||||
|
|
||||||
zipPath.read {
|
zipFile.read {
|
||||||
val zip = ZipInputStream(BufferedInputStream(it))
|
val zip = ZipInputStream(BufferedInputStream(it))
|
||||||
while (true) {
|
while (true) {
|
||||||
val e = zip.nextEntry ?: break
|
val e = zip.nextEntry ?: break
|
||||||
val outPath = normalisedToPath / e.name
|
val outPath = (normalisedDirectory / e.name).normalize()
|
||||||
|
|
||||||
// Security checks: we should reject a zip that contains tricksy paths that try to escape toPath.
|
// Security checks: we should reject a zip that contains tricksy paths that try to escape toDirectory.
|
||||||
if (!outPath.normalize().startsWith(normalisedToPath))
|
check(outPath.startsWith(normalisedDirectory)) { "ZIP contained a path that resolved incorrectly: ${e.name}" }
|
||||||
throw IllegalStateException("ZIP contained a path that resolved incorrectly: ${e.name}")
|
|
||||||
|
|
||||||
if (e.isDirectory) {
|
if (e.isDirectory) {
|
||||||
outPath.createDirectories()
|
outPath.createDirectories()
|
||||||
@ -355,8 +363,7 @@ data class ErrorOr<out A> private constructor(val value: A?, val error: Throwabl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an observable that buffers events until subscribed.
|
* Returns an Observable that buffers events until subscribed.
|
||||||
*
|
|
||||||
* @see UnicastSubject
|
* @see UnicastSubject
|
||||||
*/
|
*/
|
||||||
fun <T> Observable<T>.bufferUntilSubscribed(): Observable<T> {
|
fun <T> Observable<T>.bufferUntilSubscribed(): Observable<T> {
|
||||||
@ -375,5 +382,18 @@ fun <T> Observer<T>.tee(vararg teeTo: Observer<T>): Observer<T> {
|
|||||||
return subject
|
return subject
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Allows summing big decimals that are in iterable collections */
|
/**
|
||||||
|
* Returns a [ListenableFuture] bound to the *first* item emitted by this Observable. The future will complete with a
|
||||||
|
* NoSuchElementException if no items are emitted or any other error thrown by the Observable.
|
||||||
|
*/
|
||||||
|
fun <T> Observable<T>.toFuture(): ListenableFuture<T> {
|
||||||
|
val future = SettableFuture.create<T>()
|
||||||
|
first().subscribe(
|
||||||
|
{ future.set(it) },
|
||||||
|
{ future.setException(it) }
|
||||||
|
)
|
||||||
|
return future
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return the sum of an Iterable of [BigDecimal]s. */
|
||||||
fun Iterable<BigDecimal>.sum(): BigDecimal = fold(BigDecimal.ZERO) { a, b -> a + b }
|
fun Iterable<BigDecimal>.sum(): BigDecimal = fold(BigDecimal.ZERO) { a, b -> a + b }
|
||||||
|
@ -119,7 +119,7 @@ inline fun <reified T : MoveCommand> verifyMoveCommand(inputs: List<OwnableState
|
|||||||
val command = commands.requireSingleCommand<T>()
|
val command = commands.requireSingleCommand<T>()
|
||||||
val keysThatSigned = command.signers.toSet()
|
val keysThatSigned = command.signers.toSet()
|
||||||
requireThat {
|
requireThat {
|
||||||
"the owning keys are the same as the signing keys" by keysThatSigned.containsAll(owningPubKeys)
|
"the owning keys are a subset of the signing keys" by keysThatSigned.containsAll(owningPubKeys)
|
||||||
}
|
}
|
||||||
return command.value
|
return command.value
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
|||||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize
|
||||||
import com.google.common.annotations.VisibleForTesting
|
import com.google.common.annotations.VisibleForTesting
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
import java.math.BigInteger
|
||||||
import java.time.DayOfWeek
|
import java.time.DayOfWeek
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
@ -34,6 +35,19 @@ import java.util.*
|
|||||||
* @param T the type of the token, for example [Currency].
|
* @param T the type of the token, for example [Currency].
|
||||||
*/
|
*/
|
||||||
data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
|
data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Build an amount from a decimal representation. For example, with an input of "12.34" GBP,
|
||||||
|
* returns an amount with a quantity of "1234".
|
||||||
|
*
|
||||||
|
* @see Amount<Currency>.toDecimal
|
||||||
|
*/
|
||||||
|
fun fromDecimal(quantity: BigDecimal, currency: Currency) : Amount<Currency> {
|
||||||
|
val longQuantity = quantity.movePointRight(currency.defaultFractionDigits).toLong()
|
||||||
|
return Amount(longQuantity, currency)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Negative amounts are of course a vital part of any ledger, but negative values are only valid in certain
|
// Negative amounts are of course a vital part of any ledger, but negative values are only valid in certain
|
||||||
// contexts: you cannot send a negative amount of cash, but you can (sometimes) have a negative balance.
|
// contexts: you cannot send a negative amount of cash, but you can (sometimes) have a negative balance.
|
||||||
@ -41,7 +55,12 @@ data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
|
|||||||
require(quantity >= 0) { "Negative amounts are not allowed: $quantity" }
|
require(quantity >= 0) { "Negative amounts are not allowed: $quantity" }
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(amount: BigDecimal, currency: T) : this(amount.toLong(), currency)
|
/**
|
||||||
|
* Construct the amount using the given decimal value as quantity. Any fractional part
|
||||||
|
* is discarded. To convert and use the fractional part, see [fromDecimal].
|
||||||
|
*/
|
||||||
|
constructor(quantity: BigDecimal, token: T) : this(quantity.toLong(), token)
|
||||||
|
constructor(quantity: BigInteger, token: T) : this(quantity.toLong(), token)
|
||||||
|
|
||||||
operator fun plus(other: Amount<T>): Amount<T> {
|
operator fun plus(other: Amount<T>): Amount<T> {
|
||||||
checkCurrency(other)
|
checkCurrency(other)
|
||||||
@ -70,6 +89,14 @@ data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a currency [Amount] to a decimal representation. For example, with an amount with a quantity
|
||||||
|
* of "1234" GBP, returns "12.34".
|
||||||
|
*
|
||||||
|
* @see Amount.Companion.fromDecimal
|
||||||
|
*/
|
||||||
|
fun Amount<Currency>.toDecimal() : BigDecimal = BigDecimal(quantity).movePointLeft(token.defaultFractionDigits)
|
||||||
|
|
||||||
fun <T> Iterable<Amount<T>>.sumOrNull() = if (!iterator().hasNext()) null else sumOrThrow()
|
fun <T> Iterable<Amount<T>>.sumOrNull() = if (!iterator().hasNext()) null else sumOrThrow()
|
||||||
fun <T> Iterable<Amount<T>>.sumOrThrow() = reduce { left, right -> left + right }
|
fun <T> Iterable<Amount<T>>.sumOrThrow() = reduce { left, right -> left + right }
|
||||||
fun <T> Iterable<Amount<T>>.sumOrZero(currency: T) = if (iterator().hasNext()) sumOrThrow() else Amount(0, currency)
|
fun <T> Iterable<Amount<T>>.sumOrZero(currency: T) = if (iterator().hasNext()) sumOrThrow() else Amount(0, currency)
|
||||||
|
@ -114,7 +114,17 @@ interface ContractState {
|
|||||||
* list should just contain the owner.
|
* list should just contain the owner.
|
||||||
*/
|
*/
|
||||||
val participants: List<CompositeKey>
|
val participants: List<CompositeKey>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper for [ContractState] containing additional platform-level state information.
|
||||||
|
* This is the definitive state that is stored on the ledger and used in transaction outputs.
|
||||||
|
*/
|
||||||
|
data class TransactionState<out T : ContractState> @JvmOverloads constructor(
|
||||||
|
/** The custom contract state */
|
||||||
|
val data: T,
|
||||||
|
/** Identity of the notary that ensures the state is not used as an input to a transaction more than once */
|
||||||
|
val notary: Party,
|
||||||
/**
|
/**
|
||||||
* All contract states may be _encumbered_ by up to one other state.
|
* All contract states may be _encumbered_ by up to one other state.
|
||||||
*
|
*
|
||||||
@ -126,30 +136,19 @@ interface ContractState {
|
|||||||
*
|
*
|
||||||
* The encumbered state refers to another by index, and the referred encumbrance state
|
* The encumbered state refers to another by index, and the referred encumbrance state
|
||||||
* is an output state in a particular position on the same transaction that created the encumbered state. An alternative
|
* is an output state in a particular position on the same transaction that created the encumbered state. An alternative
|
||||||
* implementation would be encumber by reference to a StateRef., which would allow the specification of encumbrance
|
* implementation would be encumbering by reference to a [StateRef], which would allow the specification of encumbrance
|
||||||
* by a state created in a prior transaction.
|
* by a state created in a prior transaction.
|
||||||
*
|
*
|
||||||
* Note that an encumbered state that is being consumed must have its encumbrance consumed in the same transaction,
|
* Note that an encumbered state that is being consumed must have its encumbrance consumed in the same transaction,
|
||||||
* otherwise the transaction is not valid.
|
* otherwise the transaction is not valid.
|
||||||
*/
|
*/
|
||||||
val encumbrance: Int? get() = null
|
val encumbrance: Int? = null) {
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A wrapper for [ContractState] containing additional platform-level state information.
|
|
||||||
* This is the definitive state that is stored on the ledger and used in transaction outputs.
|
|
||||||
*/
|
|
||||||
data class TransactionState<out T : ContractState>(
|
|
||||||
/** The custom contract state */
|
|
||||||
val data: T,
|
|
||||||
/** Identity of the notary that ensures the state is not used as an input to a transaction more than once */
|
|
||||||
val notary: Party) {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies the underlying state, replacing the notary field with the new value.
|
* Copies the underlying state, replacing the notary field with the new value.
|
||||||
* To replace the notary, we need an approval (signature) from _all_ participants of the [ContractState].
|
* To replace the notary, we need an approval (signature) from _all_ participants of the [ContractState].
|
||||||
*/
|
*/
|
||||||
fun withNotary(newNotary: Party) = TransactionState(this.data, newNotary)
|
fun withNotary(newNotary: Party) = TransactionState(this.data, newNotary, encumbrance)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Wraps the [ContractState] in a [TransactionState] object */
|
/** Wraps the [ContractState] in a [TransactionState] object */
|
||||||
|
@ -19,6 +19,8 @@ sealed class TransactionType {
|
|||||||
*/
|
*/
|
||||||
fun verify(tx: LedgerTransaction) {
|
fun verify(tx: LedgerTransaction) {
|
||||||
require(tx.notary != null || tx.timestamp == null) { "Transactions with timestamps must be notarised." }
|
require(tx.notary != null || tx.timestamp == null) { "Transactions with timestamps must be notarised." }
|
||||||
|
val duplicates = detectDuplicateInputs(tx)
|
||||||
|
if (duplicates.isNotEmpty()) throw TransactionVerificationException.DuplicateInputStates(tx, duplicates)
|
||||||
val missing = verifySigners(tx)
|
val missing = verifySigners(tx)
|
||||||
if (missing.isNotEmpty()) throw TransactionVerificationException.SignersMissing(tx, missing.toList())
|
if (missing.isNotEmpty()) throw TransactionVerificationException.SignersMissing(tx, missing.toList())
|
||||||
verifyTransaction(tx)
|
verifyTransaction(tx)
|
||||||
@ -35,6 +37,19 @@ sealed class TransactionType {
|
|||||||
return missing
|
return missing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Check that the inputs are unique. */
|
||||||
|
private fun detectDuplicateInputs(tx: LedgerTransaction): Set<StateRef> {
|
||||||
|
var seenInputs = emptySet<StateRef>()
|
||||||
|
var duplicates = emptySet<StateRef>()
|
||||||
|
tx.inputs.forEach { state ->
|
||||||
|
if (seenInputs.contains(state.ref)) {
|
||||||
|
duplicates += state.ref
|
||||||
|
}
|
||||||
|
seenInputs += state.ref
|
||||||
|
}
|
||||||
|
return duplicates
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the list of public keys that that require signatures for the transaction type.
|
* Return the list of public keys that that require signatures for the transaction type.
|
||||||
* Note: the notary key is checked separately for all transactions and need not be included.
|
* Note: the notary key is checked separately for all transactions and need not be included.
|
||||||
@ -74,14 +89,14 @@ sealed class TransactionType {
|
|||||||
|
|
||||||
private fun verifyEncumbrances(tx: LedgerTransaction) {
|
private fun verifyEncumbrances(tx: LedgerTransaction) {
|
||||||
// Validate that all encumbrances exist within the set of input states.
|
// Validate that all encumbrances exist within the set of input states.
|
||||||
val encumberedInputs = tx.inputs.filter { it.state.data.encumbrance != null }
|
val encumberedInputs = tx.inputs.filter { it.state.encumbrance != null }
|
||||||
encumberedInputs.forEach { encumberedInput ->
|
encumberedInputs.forEach { encumberedInput ->
|
||||||
val encumbranceStateExists = tx.inputs.any {
|
val encumbranceStateExists = tx.inputs.any {
|
||||||
it.ref.txhash == encumberedInput.ref.txhash && it.ref.index == encumberedInput.state.data.encumbrance
|
it.ref.txhash == encumberedInput.ref.txhash && it.ref.index == encumberedInput.state.encumbrance
|
||||||
}
|
}
|
||||||
if (!encumbranceStateExists) {
|
if (!encumbranceStateExists) {
|
||||||
throw TransactionVerificationException.TransactionMissingEncumbranceException(
|
throw TransactionVerificationException.TransactionMissingEncumbranceException(
|
||||||
tx, encumberedInput.state.data.encumbrance!!,
|
tx, encumberedInput.state.encumbrance!!,
|
||||||
TransactionVerificationException.Direction.INPUT
|
TransactionVerificationException.Direction.INPUT
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -90,7 +105,7 @@ sealed class TransactionType {
|
|||||||
// Check that, in the outputs, an encumbered state does not refer to itself as the encumbrance,
|
// Check that, in the outputs, an encumbered state does not refer to itself as the encumbrance,
|
||||||
// and that the number of outputs can contain the encumbrance.
|
// and that the number of outputs can contain the encumbrance.
|
||||||
for ((i, output) in tx.outputs.withIndex()) {
|
for ((i, output) in tx.outputs.withIndex()) {
|
||||||
val encumbranceIndex = output.data.encumbrance ?: continue
|
val encumbranceIndex = output.encumbrance ?: continue
|
||||||
if (encumbranceIndex == i || encumbranceIndex >= tx.outputs.size) {
|
if (encumbranceIndex == i || encumbranceIndex >= tx.outputs.size) {
|
||||||
throw TransactionVerificationException.TransactionMissingEncumbranceException(
|
throw TransactionVerificationException.TransactionMissingEncumbranceException(
|
||||||
tx, encumbranceIndex,
|
tx, encumbranceIndex,
|
||||||
|
@ -97,6 +97,9 @@ sealed class TransactionVerificationException(val tx: LedgerTransaction, cause:
|
|||||||
class SignersMissing(tx: LedgerTransaction, val missing: List<CompositeKey>) : TransactionVerificationException(tx, null) {
|
class SignersMissing(tx: LedgerTransaction, val missing: List<CompositeKey>) : TransactionVerificationException(tx, null) {
|
||||||
override fun toString() = "Signers missing: ${missing.joinToString()}"
|
override fun toString() = "Signers missing: ${missing.joinToString()}"
|
||||||
}
|
}
|
||||||
|
class DuplicateInputStates(tx: LedgerTransaction, val duplicates: Set<StateRef>) : TransactionVerificationException(tx, null) {
|
||||||
|
override fun toString() = "Duplicate inputs: ${duplicates.joinToString()}"
|
||||||
|
}
|
||||||
|
|
||||||
class InvalidNotaryChange(tx: LedgerTransaction) : TransactionVerificationException(tx, null)
|
class InvalidNotaryChange(tx: LedgerTransaction) : TransactionVerificationException(tx, null)
|
||||||
class NotaryChangeInWrongTransactionType(tx: LedgerTransaction, val outputNotary: Party) : TransactionVerificationException(tx, null) {
|
class NotaryChangeInWrongTransactionType(tx: LedgerTransaction, val outputNotary: Party) : TransactionVerificationException(tx, null) {
|
||||||
|
@ -1,39 +1,10 @@
|
|||||||
package net.corda.core.contracts.clauses
|
package net.corda.core.contracts.clauses
|
||||||
|
|
||||||
import net.corda.core.contracts.AuthenticatedObject
|
|
||||||
import net.corda.core.contracts.CommandData
|
import net.corda.core.contracts.CommandData
|
||||||
import net.corda.core.contracts.ContractState
|
import net.corda.core.contracts.ContractState
|
||||||
import net.corda.core.contracts.TransactionForContract
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compose a number of clauses, such that all of the clauses must run for verification to pass.
|
* Compose a number of clauses, such that all of the clauses must run for verification to pass.
|
||||||
*/
|
*/
|
||||||
// TODO: Rename to AllOf
|
@Deprecated("Use AllOf")
|
||||||
class AllComposition<S : ContractState, C : CommandData, K : Any>(firstClause: Clause<S, C, K>, vararg remainingClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
|
class AllComposition<S : ContractState, C : CommandData, K : Any>(firstClause: Clause<S, C, K>, vararg remainingClauses: Clause<S, C, K>) : AllOf<S, C, K>(firstClause, *remainingClauses)
|
||||||
override val clauses = ArrayList<Clause<S, C, K>>()
|
|
||||||
|
|
||||||
init {
|
|
||||||
clauses.add(firstClause)
|
|
||||||
clauses.addAll(remainingClauses)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>> {
|
|
||||||
clauses.forEach { clause ->
|
|
||||||
check(clause.matches(commands)) { "Failed to match clause ${clause}" }
|
|
||||||
}
|
|
||||||
return clauses
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun verify(tx: TransactionForContract,
|
|
||||||
inputs: List<S>,
|
|
||||||
outputs: List<S>,
|
|
||||||
commands: List<AuthenticatedObject<C>>,
|
|
||||||
groupingKey: K?): Set<C> {
|
|
||||||
return matchedClauses(commands).flatMapTo(HashSet<C>()) { clause ->
|
|
||||||
clause.verify(tx, inputs, outputs, commands, groupingKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString() = "All: $clauses.toList()"
|
|
||||||
}
|
|
@ -0,0 +1,38 @@
|
|||||||
|
package net.corda.core.contracts.clauses
|
||||||
|
|
||||||
|
import net.corda.core.contracts.AuthenticatedObject
|
||||||
|
import net.corda.core.contracts.CommandData
|
||||||
|
import net.corda.core.contracts.ContractState
|
||||||
|
import net.corda.core.contracts.TransactionForContract
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose a number of clauses, such that all of the clauses must run for verification to pass.
|
||||||
|
*/
|
||||||
|
open class AllOf<S : ContractState, C : CommandData, K : Any>(firstClause: Clause<S, C, K>, vararg remainingClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
|
||||||
|
override val clauses = ArrayList<Clause<S, C, K>>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
clauses.add(firstClause)
|
||||||
|
clauses.addAll(remainingClauses)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>> {
|
||||||
|
clauses.forEach { clause ->
|
||||||
|
check(clause.matches(commands)) { "Failed to match clause ${clause}" }
|
||||||
|
}
|
||||||
|
return clauses
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun verify(tx: TransactionForContract,
|
||||||
|
inputs: List<S>,
|
||||||
|
outputs: List<S>,
|
||||||
|
commands: List<AuthenticatedObject<C>>,
|
||||||
|
groupingKey: K?): Set<C> {
|
||||||
|
return matchedClauses(commands).flatMapTo(HashSet<C>()) { clause ->
|
||||||
|
clause.verify(tx, inputs, outputs, commands, groupingKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString() = "All: $clauses.toList()"
|
||||||
|
}
|
@ -1,25 +1,10 @@
|
|||||||
package net.corda.core.contracts.clauses
|
package net.corda.core.contracts.clauses
|
||||||
|
|
||||||
import net.corda.core.contracts.AuthenticatedObject
|
|
||||||
import net.corda.core.contracts.CommandData
|
import net.corda.core.contracts.CommandData
|
||||||
import net.corda.core.contracts.ContractState
|
import net.corda.core.contracts.ContractState
|
||||||
import net.corda.core.contracts.TransactionForContract
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compose a number of clauses, such that any number of the clauses can run.
|
* Compose a number of clauses, such that any number of the clauses can run.
|
||||||
*/
|
*/
|
||||||
// TODO: Rename to AnyOf
|
@Deprecated("Use AnyOf instead, although note that any of requires at least one matched clause")
|
||||||
class AnyComposition<in S : ContractState, C : CommandData, in K : Any>(vararg rawClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
|
class AnyComposition<in S : ContractState, C : CommandData, in K : Any>(vararg rawClauses: Clause<S, C, K>) : AnyOf<S, C, K>(*rawClauses)
|
||||||
override val clauses: List<Clause<S, C, K>> = rawClauses.asList()
|
|
||||||
|
|
||||||
override fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>> = clauses.filter { it.matches(commands) }
|
|
||||||
|
|
||||||
override fun verify(tx: TransactionForContract, inputs: List<S>, outputs: List<S>, commands: List<AuthenticatedObject<C>>, groupingKey: K?): Set<C> {
|
|
||||||
return matchedClauses(commands).flatMapTo(HashSet<C>()) { clause ->
|
|
||||||
clause.verify(tx, inputs, outputs, commands, groupingKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String = "Or: ${clauses.toList()}"
|
|
||||||
}
|
|
@ -0,0 +1,28 @@
|
|||||||
|
package net.corda.core.contracts.clauses
|
||||||
|
|
||||||
|
import net.corda.core.contracts.AuthenticatedObject
|
||||||
|
import net.corda.core.contracts.CommandData
|
||||||
|
import net.corda.core.contracts.ContractState
|
||||||
|
import net.corda.core.contracts.TransactionForContract
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose a number of clauses, such that one or more of the clauses can run.
|
||||||
|
*/
|
||||||
|
open class AnyOf<in S : ContractState, C : CommandData, in K : Any>(vararg rawClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
|
||||||
|
override val clauses: List<Clause<S, C, K>> = rawClauses.toList()
|
||||||
|
|
||||||
|
override fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>> {
|
||||||
|
val matched = clauses.filter { it.matches(commands) }
|
||||||
|
require(matched.isNotEmpty()) { "At least one clause must match" }
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun verify(tx: TransactionForContract, inputs: List<S>, outputs: List<S>, commands: List<AuthenticatedObject<C>>, groupingKey: K?): Set<C> {
|
||||||
|
return matchedClauses(commands).flatMapTo(HashSet<C>()) { clause ->
|
||||||
|
clause.verify(tx, inputs, outputs, commands, groupingKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = "Any: ${clauses.toList()}"
|
||||||
|
}
|
@ -26,7 +26,11 @@ abstract class Clause<in S : ContractState, C : CommandData, in K : Any> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the subclauses which will be verified as a result of verifying this clause.
|
* Determine the subclauses which will be verified as a result of verifying this clause.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if the given commands do not result in a valid execution (for example no match
|
||||||
|
* with [FirstOf]).
|
||||||
*/
|
*/
|
||||||
|
@Throws(IllegalStateException::class)
|
||||||
open fun getExecutionPath(commands: List<AuthenticatedObject<C>>): List<Clause<*, *, *>>
|
open fun getExecutionPath(commands: List<AuthenticatedObject<C>>): List<Clause<*, *, *>>
|
||||||
= listOf(this)
|
= listOf(this)
|
||||||
|
|
||||||
|
@ -14,6 +14,12 @@ abstract class CompositeClause<in S : ContractState, C : CommandData, in K : Any
|
|||||||
override fun getExecutionPath(commands: List<AuthenticatedObject<C>>): List<Clause<*, *, *>>
|
override fun getExecutionPath(commands: List<AuthenticatedObject<C>>): List<Clause<*, *, *>>
|
||||||
= matchedClauses(commands).flatMap { it.getExecutionPath(commands) }
|
= matchedClauses(commands).flatMap { it.getExecutionPath(commands) }
|
||||||
|
|
||||||
/** Determine which clauses are matched by the supplied commands */
|
/**
|
||||||
|
* Determine which clauses are matched by the supplied commands.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if the given commands do not result in a valid execution (for example no match
|
||||||
|
* with [FirstOf]).
|
||||||
|
*/
|
||||||
|
@Throws(IllegalStateException::class)
|
||||||
abstract fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>>
|
abstract fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>>
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import java.util.*
|
|||||||
/**
|
/**
|
||||||
* Compose a number of clauses, such that the first match is run, and it errors if none is run.
|
* Compose a number of clauses, such that the first match is run, and it errors if none is run.
|
||||||
*/
|
*/
|
||||||
// TODO: Rename to FirstOf
|
@Deprecated("Use FirstOf instead")
|
||||||
class FirstComposition<S : ContractState, C : CommandData, K : Any>(val firstClause: Clause<S, C, K>, vararg remainingClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
|
class FirstComposition<S : ContractState, C : CommandData, K : Any>(val firstClause: Clause<S, C, K>, vararg remainingClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
|
||||||
companion object {
|
companion object {
|
||||||
val logger = loggerFor<FirstComposition<*, *, *>>()
|
val logger = loggerFor<FirstComposition<*, *, *>>()
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
package net.corda.core.contracts.clauses
|
||||||
|
|
||||||
|
import net.corda.core.contracts.AuthenticatedObject
|
||||||
|
import net.corda.core.contracts.CommandData
|
||||||
|
import net.corda.core.contracts.ContractState
|
||||||
|
import net.corda.core.contracts.TransactionForContract
|
||||||
|
import net.corda.core.utilities.loggerFor
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose a number of clauses, such that the first match is run, and it errors if none is run.
|
||||||
|
*/
|
||||||
|
class FirstOf<S : ContractState, C : CommandData, K : Any>(val firstClause: Clause<S, C, K>, vararg remainingClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
|
||||||
|
companion object {
|
||||||
|
val logger = loggerFor<FirstOf<*, *, *>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val clauses = ArrayList<Clause<S, C, K>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the single matched clause from the set this composes, based on the given commands. This is provided as
|
||||||
|
* helper method for internal use, rather than using the exposed [matchedClauses] function which unnecessarily
|
||||||
|
* wraps the clause in a list.
|
||||||
|
*/
|
||||||
|
private fun matchedClause(commands: List<AuthenticatedObject<C>>): Clause<S, C, K> {
|
||||||
|
return clauses.firstOrNull { it.matches(commands) } ?: throw IllegalStateException("No delegate clause matched in first composition")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun matchedClauses(commands: List<AuthenticatedObject<C>>) = listOf(matchedClause(commands))
|
||||||
|
|
||||||
|
init {
|
||||||
|
clauses.add(firstClause)
|
||||||
|
clauses.addAll(remainingClauses)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun verify(tx: TransactionForContract, inputs: List<S>, outputs: List<S>, commands: List<AuthenticatedObject<C>>, groupingKey: K?): Set<C> {
|
||||||
|
return matchedClause(commands).verify(tx, inputs, outputs, commands, groupingKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString() = "First: ${clauses.toList()}"
|
||||||
|
}
|
@ -1,185 +0,0 @@
|
|||||||
package net.corda.core.crypto
|
|
||||||
|
|
||||||
import sun.security.util.HostnameChecker
|
|
||||||
import java.net.InetAddress
|
|
||||||
import java.net.Socket
|
|
||||||
import java.net.UnknownHostException
|
|
||||||
import java.security.KeyStore
|
|
||||||
import java.security.Provider
|
|
||||||
import java.security.Security
|
|
||||||
import java.security.cert.CertificateException
|
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import javax.net.ssl.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call this to change the default verification algorithm and this use the WhitelistTrustManager
|
|
||||||
* implementation. This is a work around to the fact that ArtemisMQ and probably many other libraries
|
|
||||||
* don't correctly configure the SSLParameters with setEndpointIdentificationAlgorithm and thus don't check
|
|
||||||
* that the certificate matches with the DNS entry requested. This exposes us to man in the middle attacks.
|
|
||||||
* The issue has been raised with ArtemisMQ: https://issues.apache.org/jira/browse/ARTEMIS-656
|
|
||||||
*/
|
|
||||||
fun registerWhitelistTrustManager() {
|
|
||||||
if (Security.getProvider("WhitelistTrustManager") == null) {
|
|
||||||
WhitelistTrustManagerProvider.register()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forcibly change the TrustManagerFactory defaultAlgorithm to be us
|
|
||||||
// This will apply to all code using TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
|
||||||
// Which includes the standard HTTPS implementation and most other SSL code
|
|
||||||
// TrustManagerFactory.getInstance(WhitelistTrustManagerProvider.originalTrustProviderAlgorithm)) will
|
|
||||||
// allow access to the original implementation which is normally "PKIX"
|
|
||||||
Security.setProperty("ssl.TrustManagerFactory.algorithm", "whitelistTrustManager")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom Security Provider that forces the TrustManagerFactory to be our custom one.
|
|
||||||
* Also holds the identity of the original TrustManager algorithm so
|
|
||||||
* that we can delegate most of the checking to the proper Java code. We simply add some more checks.
|
|
||||||
*
|
|
||||||
* The whitelist automatically includes the local server DNS name and IP address
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
object WhitelistTrustManagerProvider : Provider("WhitelistTrustManager",
|
|
||||||
1.0,
|
|
||||||
"Provider for custom trust manager that always validates certificate names") {
|
|
||||||
|
|
||||||
val originalTrustProviderAlgorithm = Security.getProperty("ssl.TrustManagerFactory.algorithm")
|
|
||||||
|
|
||||||
private val _whitelist = ConcurrentHashMap.newKeySet<String>()
|
|
||||||
val whitelist: Set<String> get() = _whitelist.toSet() // The acceptable IP and DNS names for clients and servers.
|
|
||||||
|
|
||||||
init {
|
|
||||||
// Add ourselves to whitelist as currently we have to connect to a local ArtemisMQ broker
|
|
||||||
val host = InetAddress.getLocalHost()
|
|
||||||
addWhitelistEntry(host.hostName)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Security provider registration function for WhitelistTrustManagerProvider
|
|
||||||
*/
|
|
||||||
fun register() {
|
|
||||||
Security.addProvider(WhitelistTrustManagerProvider)
|
|
||||||
|
|
||||||
// Register our custom TrustManagerFactorySpi
|
|
||||||
put("TrustManagerFactory.whitelistTrustManager", "net.corda.core.crypto.WhitelistTrustManagerSpi")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an extra name to the whitelist if not already present
|
|
||||||
* If this is a new entry it will internally request a DNS lookup which may block the calling thread.
|
|
||||||
*/
|
|
||||||
fun addWhitelistEntry(serverName: String) {
|
|
||||||
if (!_whitelist.contains(serverName)) { // Safe as we never delete from the set
|
|
||||||
addWhitelistEntries(listOf(serverName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a list of servers to the whitelist and also adds their fully resolved name/ip address after DNS lookup
|
|
||||||
* If the server name is not an actual DNS name this is silently ignored.
|
|
||||||
* The DNS request may block the calling thread.
|
|
||||||
*/
|
|
||||||
fun addWhitelistEntries(serverNames: List<String>) {
|
|
||||||
_whitelist.addAll(serverNames)
|
|
||||||
for (name in serverNames) {
|
|
||||||
try {
|
|
||||||
val addresses = InetAddress.getAllByName(name).toList()
|
|
||||||
_whitelist.addAll(addresses.map { y -> y.canonicalHostName })
|
|
||||||
_whitelist.addAll(addresses.map { y -> y.hostAddress })
|
|
||||||
} catch (ex: UnknownHostException) {
|
|
||||||
// Ignore if the server name is not resolvable e.g. for wildcard addresses, or addresses that can only be resolved externally
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registered TrustManagerFactorySpi
|
|
||||||
*/
|
|
||||||
class WhitelistTrustManagerSpi : TrustManagerFactorySpi() {
|
|
||||||
// Get the original implementation to delegate to (can't use Kotlin delegation on abstract classes unfortunately).
|
|
||||||
val originalProvider = TrustManagerFactory.getInstance(WhitelistTrustManagerProvider.originalTrustProviderAlgorithm)
|
|
||||||
|
|
||||||
override fun engineInit(keyStore: KeyStore?) {
|
|
||||||
originalProvider.init(keyStore)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun engineInit(managerFactoryParameters: ManagerFactoryParameters?) {
|
|
||||||
originalProvider.init(managerFactoryParameters)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun engineGetTrustManagers(): Array<out TrustManager> {
|
|
||||||
val parent = originalProvider.trustManagers.first() as X509ExtendedTrustManager
|
|
||||||
// Wrap original provider in ours and return
|
|
||||||
return arrayOf(WhitelistTrustManager(parent))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Our TrustManager extension takes the standard certificate checker and first delegates all the
|
|
||||||
* chain checking to that. If everything is well formed we then simply add a check against our whitelist
|
|
||||||
*/
|
|
||||||
class WhitelistTrustManager(val originalProvider: X509ExtendedTrustManager) : X509ExtendedTrustManager() {
|
|
||||||
// Use same Helper class as standard HTTPS library validator
|
|
||||||
val checker = HostnameChecker.getInstance(HostnameChecker.TYPE_TLS)
|
|
||||||
|
|
||||||
private fun checkIdentity(hostname: String?, cert: X509Certificate) {
|
|
||||||
// Based on standard code in sun.security.ssl.X509TrustManagerImpl.checkIdentity
|
|
||||||
// if IPv6 strip off the "[]"
|
|
||||||
if ((hostname != null) && hostname.startsWith("[") && hostname.endsWith("]")) {
|
|
||||||
checker.match(hostname.substring(1, hostname.length - 1), cert)
|
|
||||||
} else {
|
|
||||||
checker.match(hostname, cert)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* scan whitelist and confirm the certificate matches at least one entry
|
|
||||||
*/
|
|
||||||
private fun checkWhitelist(cert: X509Certificate) {
|
|
||||||
for (whiteListEntry in WhitelistTrustManagerProvider.whitelist) {
|
|
||||||
try {
|
|
||||||
checkIdentity(whiteListEntry, cert)
|
|
||||||
return // if we get here without throwing we had a match
|
|
||||||
} catch(ex: CertificateException) {
|
|
||||||
// Ignore and check the next entry until we find a match, or exhaust the whitelist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw CertificateException("Certificate not on whitelist ${cert.subjectDN}")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String, socket: Socket?) {
|
|
||||||
originalProvider.checkClientTrusted(chain, authType, socket)
|
|
||||||
checkWhitelist(chain[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String, engine: SSLEngine?) {
|
|
||||||
originalProvider.checkClientTrusted(chain, authType, engine)
|
|
||||||
checkWhitelist(chain[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String) {
|
|
||||||
originalProvider.checkClientTrusted(chain, authType)
|
|
||||||
checkWhitelist(chain[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String, socket: Socket?) {
|
|
||||||
originalProvider.checkServerTrusted(chain, authType, socket)
|
|
||||||
checkWhitelist(chain[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String, engine: SSLEngine?) {
|
|
||||||
originalProvider.checkServerTrusted(chain, authType, engine)
|
|
||||||
checkWhitelist(chain[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String) {
|
|
||||||
originalProvider.checkServerTrusted(chain, authType)
|
|
||||||
checkWhitelist(chain[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAcceptedIssuers(): Array<out X509Certificate> {
|
|
||||||
return originalProvider.acceptedIssuers
|
|
||||||
}
|
|
||||||
}
|
|
@ -26,21 +26,18 @@ import rx.Observable
|
|||||||
* it to the [subFlow] method. It will return the result of that flow when it completes.
|
* it to the [subFlow] method. It will return the result of that flow when it completes.
|
||||||
*/
|
*/
|
||||||
abstract class FlowLogic<out T> {
|
abstract class FlowLogic<out T> {
|
||||||
|
|
||||||
/** Reference to the [Fiber] instance that is the top level controller for the entire flow. */
|
|
||||||
lateinit var fsm: FlowStateMachine<*>
|
|
||||||
|
|
||||||
/** This is where you should log things to. */
|
/** This is where you should log things to. */
|
||||||
val logger: Logger get() = fsm.logger
|
val logger: Logger get() = stateMachine.logger
|
||||||
|
|
||||||
|
/** Returns a wrapped [UUID] object that identifies this state machine run (i.e. subflows have the same identifier as their parents). */
|
||||||
|
val runId: StateMachineRunId get() = stateMachine.id
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides access to big, heavy classes that may be reconstructed from time to time, e.g. across restarts. It is
|
* Provides access to big, heavy classes that may be reconstructed from time to time, e.g. across restarts. It is
|
||||||
* only available once the flow has started, which means it cannnot be accessed in the constructor. Either
|
* only available once the flow has started, which means it cannnot be accessed in the constructor. Either
|
||||||
* access this lazily or from inside [call].
|
* access this lazily or from inside [call].
|
||||||
*/
|
*/
|
||||||
val serviceHub: ServiceHub get() = fsm.serviceHub
|
val serviceHub: ServiceHub get() = stateMachine.serviceHub
|
||||||
|
|
||||||
private var sessionFlow: FlowLogic<*> = this
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the marker [Class] which [party] has used to register the counterparty flow that is to execute on the
|
* Return the marker [Class] which [party] has used to register the counterparty flow that is to execute on the
|
||||||
@ -49,35 +46,73 @@ abstract class FlowLogic<out T> {
|
|||||||
*/
|
*/
|
||||||
open fun getCounterpartyMarker(party: Party): Class<*> = javaClass
|
open fun getCounterpartyMarker(party: Party): Class<*> = javaClass
|
||||||
|
|
||||||
// Kotlin helpers that allow the use of generic types.
|
/**
|
||||||
inline fun <reified T : Any> sendAndReceive(otherParty: Party, payload: Any): UntrustworthyData<T> {
|
* Serializes and queues the given [payload] object for sending to the [otherParty]. Suspends until a response
|
||||||
return sendAndReceive(otherParty, payload, T::class.java)
|
* is received, which must be of the given [R] type.
|
||||||
}
|
*
|
||||||
|
* Remember that when receiving data from other parties the data should not be trusted until it's been thoroughly
|
||||||
// TODO: Move the receiveType param to first position for readability
|
* verified for consistency and that all expectations are satisfied, as a malicious peer may send you subtly
|
||||||
|
* corrupted data in order to exploit your code.
|
||||||
|
*
|
||||||
|
* Note that this function is not just a simple send+receive pair: it is more efficient and more correct to
|
||||||
|
* use this when you expect to do a message swap than do use [send] and then [receive] in turn.
|
||||||
|
*
|
||||||
|
* @returns an [UntrustworthyData] wrapper around the received object.
|
||||||
|
*/
|
||||||
|
inline fun <reified R : Any> sendAndReceive(otherParty: Party, payload: Any) = sendAndReceive(R::class.java, otherParty, payload)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes and queues the given [payload] object for sending to the [otherParty]. Suspends until a response
|
||||||
|
* is received, which must be of the given [receiveType]. Remember that when receiving data from other parties the data
|
||||||
|
* should not be trusted until it's been thoroughly verified for consistency and that all expectations are
|
||||||
|
* satisfied, as a malicious peer may send you subtly corrupted data in order to exploit your code.
|
||||||
|
*
|
||||||
|
* Note that this function is not just a simple send+receive pair: it is more efficient and more correct to
|
||||||
|
* use this when you expect to do a message swap than do use [send] and then [receive] in turn.
|
||||||
|
*
|
||||||
|
* @returns an [UntrustworthyData] wrapper around the received object.
|
||||||
|
*/
|
||||||
@Suspendable
|
@Suspendable
|
||||||
fun <T : Any> sendAndReceive(otherParty: Party, payload: Any, receiveType: Class<T>): UntrustworthyData<T> {
|
open fun <T : Any> sendAndReceive(receiveType: Class<T>, otherParty: Party, payload: Any): UntrustworthyData<T> {
|
||||||
return fsm.sendAndReceive(otherParty, payload, receiveType, sessionFlow)
|
return stateMachine.sendAndReceive(receiveType, otherParty, payload, sessionFlow)
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified T : Any> receive(otherParty: Party): UntrustworthyData<T> = receive(otherParty, T::class.java)
|
|
||||||
|
|
||||||
// TODO: Move the receiveType param to first position for readability
|
|
||||||
|
|
||||||
@Suspendable
|
|
||||||
fun <T : Any> receive(otherParty: Party, receiveType: Class<T>): UntrustworthyData<T> {
|
|
||||||
return fsm.receive(otherParty, receiveType, sessionFlow)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suspendable
|
|
||||||
fun send(otherParty: Party, payload: Any) {
|
|
||||||
fsm.send(otherParty, payload, sessionFlow)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invokes the given subflow by simply passing through this [FlowLogic]s reference to the
|
* Suspends until the specified [otherParty] sends us a message of type [R].
|
||||||
* [FlowStateMachine] and then calling the [call] method.
|
*
|
||||||
|
* Remember that when receiving data from other parties the data should not be trusted until it's been thoroughly
|
||||||
|
* verified for consistency and that all expectations are satisfied, as a malicious peer may send you subtly
|
||||||
|
* corrupted data in order to exploit your code.
|
||||||
|
*/
|
||||||
|
inline fun <reified R : Any> receive(otherParty: Party): UntrustworthyData<R> = receive(R::class.java, otherParty)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suspends until the specified [otherParty] sends us a message of type [receiveType].
|
||||||
|
*
|
||||||
|
* Remember that when receiving data from other parties the data should not be trusted until it's been thoroughly
|
||||||
|
* verified for consistency and that all expectations are satisfied, as a malicious peer may send you subtly
|
||||||
|
* corrupted data in order to exploit your code.
|
||||||
|
*/
|
||||||
|
@Suspendable
|
||||||
|
open fun <T : Any> receive(receiveType: Class<T>, otherParty: Party): UntrustworthyData<T> {
|
||||||
|
return stateMachine.receive(receiveType, otherParty, sessionFlow)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queues the given [payload] for sending to the [otherParty] and continues without suspending.
|
||||||
|
*
|
||||||
|
* Note that the other party may receive the message at some arbitrary later point or not at all: if [otherParty]
|
||||||
|
* is offline then message delivery will be retried until it comes back or until the message is older than the
|
||||||
|
* network's event horizon time.
|
||||||
|
*/
|
||||||
|
@Suspendable
|
||||||
|
open fun send(otherParty: Party, payload: Any) = stateMachine.send(otherParty, payload, sessionFlow)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes the given subflow. This function returns once the subflow completes successfully with the result
|
||||||
|
* returned by that subflows [call] method. If the subflow has a progress tracker, it is attached to the
|
||||||
|
* current step in this flow's progress tracker.
|
||||||
|
*
|
||||||
* @param shareParentSessions In certain situations the need arises to use the same sessions the parent flow has
|
* @param shareParentSessions In certain situations the need arises to use the same sessions the parent flow has
|
||||||
* already established. However this also prevents the subflow from creating new sessions with those parties.
|
* already established. However this also prevents the subflow from creating new sessions with those parties.
|
||||||
* For this reason the default value is false.
|
* For this reason the default value is false.
|
||||||
@ -85,8 +120,9 @@ abstract class FlowLogic<out T> {
|
|||||||
// TODO Rethink the default value for shareParentSessions
|
// TODO Rethink the default value for shareParentSessions
|
||||||
// TODO shareParentSessions is a bit too low-level and perhaps can be expresed in a better way
|
// TODO shareParentSessions is a bit too low-level and perhaps can be expresed in a better way
|
||||||
@Suspendable
|
@Suspendable
|
||||||
fun <R> subFlow(subLogic: FlowLogic<R>, shareParentSessions: Boolean = false): R {
|
@JvmOverloads
|
||||||
subLogic.fsm = fsm
|
open fun <R> subFlow(subLogic: FlowLogic<R>, shareParentSessions: Boolean = false): R {
|
||||||
|
subLogic.stateMachine = stateMachine
|
||||||
maybeWireUpProgressTracking(subLogic)
|
maybeWireUpProgressTracking(subLogic)
|
||||||
if (shareParentSessions) {
|
if (shareParentSessions) {
|
||||||
subLogic.sessionFlow = this
|
subLogic.sessionFlow = this
|
||||||
@ -97,6 +133,52 @@ abstract class FlowLogic<out T> {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override this to provide a [ProgressTracker]. If one is provided and stepped, the framework will do something
|
||||||
|
* helpful with the progress reports. If this flow is invoked as a subflow of another, then the
|
||||||
|
* tracker will be made a child of the current step in the parent. If it's null, this flow doesn't track
|
||||||
|
* progress.
|
||||||
|
*
|
||||||
|
* Note that this has to return a tracker before the flow is invoked. You can't change your mind half way
|
||||||
|
* through.
|
||||||
|
*/
|
||||||
|
open val progressTracker: ProgressTracker? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is where you fill out your business logic. The returned object will usually be ignored, but can be
|
||||||
|
* helpful if this flow is meant to be used as a subflow.
|
||||||
|
*/
|
||||||
|
@Suspendable
|
||||||
|
abstract fun call(): T
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a pair of the current progress step, as a string, and an observable of stringified changes to the
|
||||||
|
* [progressTracker].
|
||||||
|
*
|
||||||
|
* @return Returns null if this flow has no progress tracker.
|
||||||
|
*/
|
||||||
|
fun track(): Pair<String, Observable<String>>? {
|
||||||
|
// TODO this is not threadsafe, needs an atomic get-step-and-subscribe
|
||||||
|
return progressTracker?.let {
|
||||||
|
Pair(it.currentStep.toString(), it.changes.map { it.toString() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private var _stateMachine: FlowStateMachine<*>? = null
|
||||||
|
/**
|
||||||
|
* Internal only. Reference to the [Fiber] instance that is the top level controller for the entire flow. When
|
||||||
|
* inside a flow this is equivalent to [Strand.currentStrand]. This is public only because it must be accessed
|
||||||
|
* across module boundaries.
|
||||||
|
*/
|
||||||
|
var stateMachine: FlowStateMachine<*>
|
||||||
|
get() = _stateMachine ?: throw IllegalStateException("This can only be done after the flow has been started.")
|
||||||
|
set(value) { _stateMachine = value }
|
||||||
|
|
||||||
|
// This points to the outermost flow and is changed when a subflow is invoked.
|
||||||
|
private var sessionFlow: FlowLogic<*> = this
|
||||||
|
|
||||||
private fun maybeWireUpProgressTracking(subLogic: FlowLogic<*>) {
|
private fun maybeWireUpProgressTracking(subLogic: FlowLogic<*>) {
|
||||||
val ours = progressTracker
|
val ours = progressTracker
|
||||||
|
|
||||||
@ -109,27 +191,4 @@ abstract class FlowLogic<out T> {
|
|||||||
ours.setChildProgressTracker(ours.currentStep, theirs)
|
ours.setChildProgressTracker(ours.currentStep, theirs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Override this to provide a [ProgressTracker]. If one is provided and stepped, the framework will do something
|
|
||||||
* helpful with the progress reports. If this flow is invoked as a sub-flow of another, then the
|
|
||||||
* tracker will be made a child of the current step in the parent. If it's null, this flow doesn't track
|
|
||||||
* progress.
|
|
||||||
*
|
|
||||||
* Note that this has to return a tracker before the flow is invoked. You can't change your mind half way
|
|
||||||
* through.
|
|
||||||
*/
|
|
||||||
open val progressTracker: ProgressTracker? = null
|
|
||||||
|
|
||||||
/** This is where you fill out your business logic. */
|
|
||||||
@Suspendable
|
|
||||||
abstract fun call(): T
|
|
||||||
|
|
||||||
// TODO this is not threadsafe, needs an atomic get-step-and-subscribe
|
|
||||||
fun track(): Pair<String, Observable<String>>? {
|
|
||||||
return progressTracker?.let {
|
|
||||||
Pair(it.currentStep.toString(), it.changes.map { it.toString() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,11 @@ import net.corda.core.utilities.UntrustworthyData
|
|||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A unique identifier for a single state machine run, valid across node restarts. Note that a single run always
|
||||||
|
* has at least one flow, but that flow may also invoke sub-flows: they all share the same run id.
|
||||||
|
*/
|
||||||
data class StateMachineRunId private constructor(val uuid: UUID) {
|
data class StateMachineRunId private constructor(val uuid: UUID) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun createRandom(): StateMachineRunId = StateMachineRunId(UUID.randomUUID())
|
fun createRandom(): StateMachineRunId = StateMachineRunId(UUID.randomUUID())
|
||||||
fun wrap(uuid: UUID): StateMachineRunId = StateMachineRunId(uuid)
|
fun wrap(uuid: UUID): StateMachineRunId = StateMachineRunId(uuid)
|
||||||
@ -18,34 +21,24 @@ data class StateMachineRunId private constructor(val uuid: UUID) {
|
|||||||
override fun toString(): String = "[$uuid]"
|
override fun toString(): String = "[$uuid]"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** This is an internal interface that is implemented by code in the node module. You should look at [FlowLogic]. */
|
||||||
* A FlowStateMachine instance is a suspendable fiber that delegates all actual logic to a [FlowLogic] instance.
|
|
||||||
* For any given flow there is only one PSM, even if that flow invokes subflows.
|
|
||||||
*
|
|
||||||
* These classes are created by the [StateMachineManager] when a new flow is started at the topmost level. If
|
|
||||||
* a flow invokes a sub-flow, then it will pass along the PSM to the child. The call method of the topmost
|
|
||||||
* logic element gets to return the value that the entire state machine resolves to.
|
|
||||||
*/
|
|
||||||
interface FlowStateMachine<R> {
|
interface FlowStateMachine<R> {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
fun <T : Any> sendAndReceive(otherParty: Party,
|
fun <T : Any> sendAndReceive(receiveType: Class<T>,
|
||||||
|
otherParty: Party,
|
||||||
payload: Any,
|
payload: Any,
|
||||||
receiveType: Class<T>,
|
|
||||||
sessionFlow: FlowLogic<*>): UntrustworthyData<T>
|
sessionFlow: FlowLogic<*>): UntrustworthyData<T>
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
fun <T : Any> receive(otherParty: Party, receiveType: Class<T>, sessionFlow: FlowLogic<*>): UntrustworthyData<T>
|
fun <T : Any> receive(receiveType: Class<T>, otherParty: Party, sessionFlow: FlowLogic<*>): UntrustworthyData<T>
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>)
|
fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>)
|
||||||
|
|
||||||
val serviceHub: ServiceHub
|
val serviceHub: ServiceHub
|
||||||
val logger: Logger
|
val logger: Logger
|
||||||
|
|
||||||
/** Unique ID for this machine run, valid across restarts */
|
|
||||||
val id: StateMachineRunId
|
val id: StateMachineRunId
|
||||||
/** This future will complete when the call method returns. */
|
|
||||||
val resultFuture: ListenableFuture<R>
|
val resultFuture: ListenableFuture<R>
|
||||||
}
|
}
|
||||||
|
|
||||||
class FlowSessionException(message: String) : Exception(message)
|
class FlowException(message: String) : RuntimeException(message)
|
||||||
|
@ -23,8 +23,12 @@ data class StateMachineInfo(
|
|||||||
)
|
)
|
||||||
|
|
||||||
sealed class StateMachineUpdate(val id: StateMachineRunId) {
|
sealed class StateMachineUpdate(val id: StateMachineRunId) {
|
||||||
class Added(val stateMachineInfo: StateMachineInfo) : StateMachineUpdate(stateMachineInfo.id)
|
class Added(val stateMachineInfo: StateMachineInfo) : StateMachineUpdate(stateMachineInfo.id) {
|
||||||
class Removed(id: StateMachineRunId) : StateMachineUpdate(id)
|
override fun toString() = "Added($id, ${stateMachineInfo.flowLogicClassName})"
|
||||||
|
}
|
||||||
|
class Removed(id: StateMachineRunId) : StateMachineUpdate(id) {
|
||||||
|
override fun toString() = "Removed($id)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -172,5 +176,6 @@ inline fun <T : Any, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startFlow
|
|||||||
data class FlowHandle<A>(
|
data class FlowHandle<A>(
|
||||||
val id: StateMachineRunId,
|
val id: StateMachineRunId,
|
||||||
val progress: Observable<String>,
|
val progress: Observable<String>,
|
||||||
|
// TODO This should be ListenableFuture<A>
|
||||||
val returnValue: Observable<A>
|
val returnValue: Observable<A>
|
||||||
)
|
)
|
||||||
|
@ -4,6 +4,7 @@ import com.google.common.util.concurrent.ListenableFuture
|
|||||||
import com.google.common.util.concurrent.SettableFuture
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
import net.corda.core.catch
|
import net.corda.core.catch
|
||||||
import net.corda.core.node.services.DEFAULT_SESSION_ID
|
import net.corda.core.node.services.DEFAULT_SESSION_ID
|
||||||
|
import net.corda.core.node.services.PartyInfo
|
||||||
import net.corda.core.serialization.DeserializeAsKotlinObjectDef
|
import net.corda.core.serialization.DeserializeAsKotlinObjectDef
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
@ -79,6 +80,9 @@ interface MessagingService {
|
|||||||
*/
|
*/
|
||||||
fun createMessage(topicSession: TopicSession, data: ByteArray, uuid: UUID = UUID.randomUUID()): Message
|
fun createMessage(topicSession: TopicSession, data: ByteArray, uuid: UUID = UUID.randomUUID()): Message
|
||||||
|
|
||||||
|
/** Given information about either a specific node or a service returns its corresponding address */
|
||||||
|
fun getAddressOfParty(partyInfo: PartyInfo): MessageRecipients
|
||||||
|
|
||||||
/** Returns an address that refers to this node. */
|
/** Returns an address that refers to this node. */
|
||||||
val myAddress: SingleMessageRecipient
|
val myAddress: SingleMessageRecipient
|
||||||
}
|
}
|
||||||
@ -127,7 +131,7 @@ inline fun MessagingService.runOnNextMessage(topicSession: TopicSession, crossin
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a [ListenableFuture] of the next message payload ([Message.data]) which is received on the given topic and sessionId.
|
* Returns a [ListenableFuture] of the next message payload ([Message.data]) which is received on the given topic and sessionId.
|
||||||
* The payload is deserilaized to an object of type [M]. Any exceptions thrown will be captured by the future.
|
* The payload is deserialized to an object of type [M]. Any exceptions thrown will be captured by the future.
|
||||||
*/
|
*/
|
||||||
fun <M : Any> MessagingService.onNext(topic: String, sessionId: Long): ListenableFuture<M> {
|
fun <M : Any> MessagingService.onNext(topic: String, sessionId: Long): ListenableFuture<M> {
|
||||||
val messageFuture = SettableFuture.create<M>()
|
val messageFuture = SettableFuture.create<M>()
|
||||||
|
@ -58,6 +58,7 @@ interface ServiceHub {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Will check [logicType] and [args] against a whitelist and if acceptable then construct and initiate the flow.
|
* Will check [logicType] and [args] against a whitelist and if acceptable then construct and initiate the flow.
|
||||||
|
* Note that you must be on the server thread to call this method.
|
||||||
*
|
*
|
||||||
* @throws IllegalFlowLogicException or IllegalArgumentException if there are problems with the [logicType] or [args].
|
* @throws IllegalFlowLogicException or IllegalArgumentException if there are problems with the [logicType] or [args].
|
||||||
*/
|
*/
|
||||||
@ -81,7 +82,6 @@ interface ServiceHub {
|
|||||||
* Typical use is during signing in flows and for unit test signing.
|
* Typical use is during signing in flows and for unit test signing.
|
||||||
*/
|
*/
|
||||||
val notaryIdentityKey: KeyPair get() = this.keyManagementService.toKeyPair(this.myInfo.notaryIdentity.owningKey.keys)
|
val notaryIdentityKey: KeyPair get() = this.keyManagementService.toKeyPair(this.myInfo.notaryIdentity.owningKey.keys)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -5,9 +5,11 @@ import com.google.common.util.concurrent.ListenableFuture
|
|||||||
import net.corda.core.contracts.Contract
|
import net.corda.core.contracts.Contract
|
||||||
import net.corda.core.crypto.CompositeKey
|
import net.corda.core.crypto.CompositeKey
|
||||||
import net.corda.core.crypto.Party
|
import net.corda.core.crypto.Party
|
||||||
|
import net.corda.core.messaging.MessageRecipients
|
||||||
import net.corda.core.messaging.MessagingService
|
import net.corda.core.messaging.MessagingService
|
||||||
import net.corda.core.messaging.SingleMessageRecipient
|
import net.corda.core.messaging.SingleMessageRecipient
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
|
import net.corda.core.node.ServiceEntry
|
||||||
import net.corda.core.randomOrNull
|
import net.corda.core.randomOrNull
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
@ -63,31 +65,27 @@ interface NetworkMapCache {
|
|||||||
/** Look up the node info for a legal name. */
|
/** Look up the node info for a legal name. */
|
||||||
fun getNodeByLegalName(name: String): NodeInfo? = partyNodes.singleOrNull { it.legalIdentity.name == name }
|
fun getNodeByLegalName(name: String): NodeInfo? = partyNodes.singleOrNull { it.legalIdentity.name == name }
|
||||||
|
|
||||||
/** Look up the node info for a composite key. */
|
/**
|
||||||
fun getNodeByCompositeKey(compositeKey: CompositeKey): NodeInfo? {
|
* In general, nodes can advertise multiple identities: a legal identity, and separate identities for each of
|
||||||
|
* the services it provides. In case of a distributed service – run by multiple nodes – each participant advertises
|
||||||
|
* the identity of the *whole group*.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Look up the node info for a specific peer key. */
|
||||||
|
fun getNodeByLegalIdentityKey(compositeKey: CompositeKey): NodeInfo? {
|
||||||
// Although we should never have more than one match, it is theoretically possible. Report an error if it happens.
|
// Although we should never have more than one match, it is theoretically possible. Report an error if it happens.
|
||||||
val candidates = partyNodes.filter {
|
val candidates = partyNodes.filter { it.legalIdentity.owningKey == compositeKey }
|
||||||
(it.legalIdentity.owningKey == compositeKey)
|
|
||||||
|| it.advertisedServices.any { it.identity.owningKey == compositeKey }
|
|
||||||
}
|
|
||||||
check(candidates.size <= 1) { "Found more than one match for key $compositeKey" }
|
check(candidates.size <= 1) { "Found more than one match for key $compositeKey" }
|
||||||
return candidates.singleOrNull()
|
return candidates.singleOrNull()
|
||||||
}
|
}
|
||||||
|
/** Look up all nodes advertising the service owned by [compositeKey] */
|
||||||
/**
|
fun getNodesByAdvertisedServiceIdentityKey(compositeKey: CompositeKey): List<NodeInfo> {
|
||||||
* Given a [party], returns a node advertising it as an identity. If more than one node found the result
|
return partyNodes.filter { it.advertisedServices.any { it.identity.owningKey == compositeKey } }
|
||||||
* is chosen at random.
|
|
||||||
*
|
|
||||||
* In general, nodes can advertise multiple identities: a legal identity, and separate identities for each of
|
|
||||||
* the services it provides. In case of a distributed service – run by multiple nodes – each participant advertises
|
|
||||||
* the identity of the *whole group*. If the provided [party] is a group identity, multiple nodes advertising it
|
|
||||||
* will be found, and this method will return a randomly chosen one. If [party] is an individual (legal) identity,
|
|
||||||
* we currently assume that it will be advertised by one node only, which will be returned as the result.
|
|
||||||
*/
|
|
||||||
fun getRepresentativeNode(party: Party): NodeInfo? {
|
|
||||||
return partyNodes.randomOrNull { it.legalIdentity == party || it.advertisedServices.any { it.identity == party } }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns information about the party, which may be a specific node or a service */
|
||||||
|
fun getPartyInfo(party: Party): PartyInfo?
|
||||||
|
|
||||||
/** Gets a notary identity by the given name. */
|
/** Gets a notary identity by the given name. */
|
||||||
fun getNotary(name: String): Party? {
|
fun getNotary(name: String): Party? {
|
||||||
val notaryNode = notaryNodes.randomOrNull {
|
val notaryNode = notaryNodes.randomOrNull {
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package net.corda.core.node.services
|
||||||
|
|
||||||
|
import net.corda.core.crypto.Party
|
||||||
|
import net.corda.core.node.NodeInfo
|
||||||
|
import net.corda.core.node.ServiceEntry
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds information about a [Party], which may refer to either a specific node or a service.
|
||||||
|
*/
|
||||||
|
sealed class PartyInfo() {
|
||||||
|
abstract val party: Party
|
||||||
|
class Node(val node: NodeInfo) : PartyInfo() {
|
||||||
|
override val party = node.legalIdentity
|
||||||
|
}
|
||||||
|
class Service(val service: ServiceEntry) : PartyInfo() {
|
||||||
|
override val party = service.identity
|
||||||
|
}
|
||||||
|
}
|
@ -11,15 +11,9 @@ sealed class ServiceType(val id: String) {
|
|||||||
//
|
//
|
||||||
// * IDs must start with a lower case letter
|
// * IDs must start with a lower case letter
|
||||||
// * IDs can only contain alphanumeric, full stop and underscore ASCII characters
|
// * IDs can only contain alphanumeric, full stop and underscore ASCII characters
|
||||||
require(id.matches(Regex("[a-z][a-zA-Z0-9._]+")))
|
require(id.matches(Regex("[a-z][a-zA-Z0-9._]+"))) { id }
|
||||||
}
|
|
||||||
|
|
||||||
private class ServiceTypeImpl(baseId: String, subTypeId: String) : ServiceType("$baseId.$subTypeId") {
|
|
||||||
init {
|
|
||||||
// only allowed one level of subtype
|
|
||||||
require(subTypeId.matches(Regex("[a-z]\\w+")))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
private class ServiceTypeImpl(baseId: String, subTypeId: String) : ServiceType("$baseId.$subTypeId")
|
||||||
|
|
||||||
private class ServiceTypeDirect(id: String) : ServiceType(id)
|
private class ServiceTypeDirect(id: String) : ServiceType(id)
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package net.corda.core.node.services
|
package net.corda.core.node.services
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.google.common.util.concurrent.SettableFuture
|
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.CompositeKey
|
import net.corda.core.crypto.CompositeKey
|
||||||
import net.corda.core.crypto.Party
|
import net.corda.core.crypto.Party
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.toStringShort
|
import net.corda.core.crypto.toStringShort
|
||||||
|
import net.corda.core.toFuture
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
@ -38,7 +38,7 @@ val DEFAULT_SESSION_ID = 0L
|
|||||||
* Active means they haven't been consumed yet (or we don't know about it).
|
* Active means they haven't been consumed yet (or we don't know about it).
|
||||||
* Relevant means they contain at least one of our pubkeys.
|
* Relevant means they contain at least one of our pubkeys.
|
||||||
*/
|
*/
|
||||||
class Vault(val states: Iterable<StateAndRef<ContractState>>) {
|
class Vault(val states: List<StateAndRef<ContractState>>) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
inline fun <reified T : ContractState> statesOfType() = states.filter { it.state.data is T } as List<StateAndRef<T>>
|
inline fun <reified T : ContractState> statesOfType() = states.filter { it.state.data is T } as List<StateAndRef<T>>
|
||||||
|
|
||||||
@ -50,7 +50,10 @@ class Vault(val states: Iterable<StateAndRef<ContractState>>) {
|
|||||||
* If the vault observes multiple transactions simultaneously, where some transactions consume the outputs of some of the
|
* If the vault observes multiple transactions simultaneously, where some transactions consume the outputs of some of the
|
||||||
* other transactions observed, then the changes are observed "net" of those.
|
* other transactions observed, then the changes are observed "net" of those.
|
||||||
*/
|
*/
|
||||||
data class Update(val consumed: Set<StateRef>, val produced: Set<StateAndRef<ContractState>>) {
|
data class Update(val consumed: Set<StateAndRef<ContractState>>, val produced: Set<StateAndRef<ContractState>>) {
|
||||||
|
/** Checks whether the update contains a state of the specified type. */
|
||||||
|
inline fun <reified T : ContractState> containsType() = consumed.any { it.state.data is T } || produced.any { it.state.data is T }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combine two updates into a single update with the combined inputs and outputs of the two updates but net
|
* Combine two updates into a single update with the combined inputs and outputs of the two updates but net
|
||||||
* any outputs of the left-hand-side (this) that are consumed by the inputs of the right-hand-side (rhs).
|
* any outputs of the left-hand-side (this) that are consumed by the inputs of the right-hand-side (rhs).
|
||||||
@ -58,12 +61,10 @@ class Vault(val states: Iterable<StateAndRef<ContractState>>) {
|
|||||||
* i.e. the net effect in terms of state live-ness of receiving the combined update is the same as receiving this followed by rhs.
|
* i.e. the net effect in terms of state live-ness of receiving the combined update is the same as receiving this followed by rhs.
|
||||||
*/
|
*/
|
||||||
operator fun plus(rhs: Update): Update {
|
operator fun plus(rhs: Update): Update {
|
||||||
val previouslyProduced = produced.map { it.ref }
|
|
||||||
val previouslyConsumed = consumed
|
|
||||||
val combined = Vault.Update(
|
val combined = Vault.Update(
|
||||||
previouslyConsumed + (rhs.consumed - previouslyProduced),
|
consumed + (rhs.consumed - produced),
|
||||||
// The ordering below matters to preserve ordering of consumed/produced Sets when they are insertion order dependent implementations.
|
// The ordering below matters to preserve ordering of consumed/produced Sets when they are insertion order dependent implementations.
|
||||||
produced.filter { it.ref !in rhs.consumed }.toSet() + rhs.produced)
|
produced.filter { it !in rhs.consumed }.toSet() + rhs.produced)
|
||||||
return combined
|
return combined
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,15 +121,7 @@ interface VaultService {
|
|||||||
* Returns a map of how much cash we have in each currency, ignoring details like issuer. Note: currencies for
|
* Returns a map of how much cash we have in each currency, ignoring details like issuer. Note: currencies for
|
||||||
* which we have no cash evaluate to null (not present in map), not 0.
|
* which we have no cash evaluate to null (not present in map), not 0.
|
||||||
*/
|
*/
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
val cashBalances: Map<Currency, Amount<Currency>>
|
val cashBalances: Map<Currency, Amount<Currency>>
|
||||||
get() = currentVault.states.
|
|
||||||
// Select the states we own which are cash, ignore the rest, take the amounts.
|
|
||||||
mapNotNull { (it.state.data as? FungibleAsset<Currency>)?.amount }.
|
|
||||||
// Turn into a Map<Currency, List<Amount>> like { GBP -> (£100, £500, etc), USD -> ($2000, $50) }
|
|
||||||
groupBy { it.token.product }.
|
|
||||||
// Collapse to Map<Currency, Amount> by summing all the amounts of the same currency together.
|
|
||||||
mapValues { it.value.map { Amount(it.quantity, it.token.product) }.sumOrThrow() }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Atomically get the current vault and a stream of updates. Note that the Observable buffers updates until the
|
* Atomically get the current vault and a stream of updates. Note that the Observable buffers updates until the
|
||||||
@ -158,24 +151,18 @@ interface VaultService {
|
|||||||
* Possibly update the vault by marking as spent states that these transactions consume, and adding any relevant
|
* Possibly update the vault by marking as spent states that these transactions consume, and adding any relevant
|
||||||
* new states that they create. You should only insert transactions that have been successfully verified here!
|
* new states that they create. You should only insert transactions that have been successfully verified here!
|
||||||
*
|
*
|
||||||
* Returns the new vault that resulted from applying the transactions (note: it may quickly become out of date).
|
|
||||||
*
|
|
||||||
* TODO: Consider if there's a good way to enforce the must-be-verified requirement in the type system.
|
* TODO: Consider if there's a good way to enforce the must-be-verified requirement in the type system.
|
||||||
*/
|
*/
|
||||||
fun notifyAll(txns: Iterable<WireTransaction>): Vault
|
fun notifyAll(txns: Iterable<WireTransaction>)
|
||||||
|
|
||||||
/** Same as notifyAll but with a single transaction. */
|
/** Same as notifyAll but with a single transaction. */
|
||||||
fun notify(tx: WireTransaction): Vault = notifyAll(listOf(tx))
|
fun notify(tx: WireTransaction) = notifyAll(listOf(tx))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide a [Future] for when a [StateRef] is consumed, which can be very useful in building tests.
|
* Provide a [Future] for when a [StateRef] is consumed, which can be very useful in building tests.
|
||||||
*/
|
*/
|
||||||
fun whenConsumed(ref: StateRef): ListenableFuture<Vault.Update> {
|
fun whenConsumed(ref: StateRef): ListenableFuture<Vault.Update> {
|
||||||
val future = SettableFuture.create<Vault.Update>()
|
return updates.filter { it.consumed.any { it.ref == ref } }.toFuture()
|
||||||
updates.filter { ref in it.consumed }.first().subscribe {
|
|
||||||
future.set(it)
|
|
||||||
}
|
|
||||||
return future
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -410,10 +410,8 @@ fun createKryo(k: Kryo = Kryo()): Kryo {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Some things where the JRE provides an efficient custom serialisation.
|
register(EdDSAPublicKey::class.java, Ed25519PublicKeySerializer)
|
||||||
val keyPair = generateKeyPair()
|
register(EdDSAPrivateKey::class.java, Ed25519PrivateKeySerializer)
|
||||||
register(keyPair.public.javaClass, Ed25519PublicKeySerializer)
|
|
||||||
register(keyPair.private.javaClass, Ed25519PrivateKeySerializer)
|
|
||||||
register(Instant::class.java, ReferencesAwareJavaSerializer)
|
register(Instant::class.java, ReferencesAwareJavaSerializer)
|
||||||
|
|
||||||
// Using a custom serializer for compactness
|
// Using a custom serializer for compactness
|
||||||
|
@ -159,11 +159,12 @@ open class TransactionBuilder(
|
|||||||
return outputs.size - 1
|
return outputs.size - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addOutputState(state: ContractState, notary: Party) = addOutputState(TransactionState(state, notary))
|
@JvmOverloads
|
||||||
|
fun addOutputState(state: ContractState, notary: Party, encumbrance: Int? = null) = addOutputState(TransactionState(state, notary, encumbrance))
|
||||||
|
|
||||||
/** A default notary must be specified during builder construction to use this method */
|
/** A default notary must be specified during builder construction to use this method */
|
||||||
fun addOutputState(state: ContractState): Int {
|
fun addOutputState(state: ContractState): Int {
|
||||||
checkNotNull(notary) { "Need to specify a Notary for the state, or set a default one on TransactionBuilder initialisation" }
|
checkNotNull(notary) { "Need to specify a notary for the state, or set a default one on TransactionBuilder initialisation" }
|
||||||
return addOutputState(state, notary!!)
|
return addOutputState(state, notary!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,8 +5,9 @@ package net.corda.core.utilities
|
|||||||
*/
|
*/
|
||||||
object Emoji {
|
object Emoji {
|
||||||
// Unfortunately only Apple has a terminal that can do colour emoji AND an emoji font installed by default.
|
// Unfortunately only Apple has a terminal that can do colour emoji AND an emoji font installed by default.
|
||||||
val hasEmojiTerminal by lazy { System.getenv("TERM_PROGRAM") == "Apple_Terminal" }
|
val hasEmojiTerminal by lazy { listOf("Apple_Terminal", "iTerm.app").contains(System.getenv("TERM_PROGRAM")) }
|
||||||
|
|
||||||
|
const val CODE_SANTA_CLAUS = "\ud83c\udf85"
|
||||||
const val CODE_DIAMOND = "\ud83d\udd37"
|
const val CODE_DIAMOND = "\ud83d\udd37"
|
||||||
const val CODE_BAG_OF_CASH = "\ud83d\udcb0"
|
const val CODE_BAG_OF_CASH = "\ud83d\udcb0"
|
||||||
const val CODE_NEWSPAPER = "\ud83d\udcf0"
|
const val CODE_NEWSPAPER = "\ud83d\udcf0"
|
||||||
@ -22,6 +23,7 @@ object Emoji {
|
|||||||
*/
|
*/
|
||||||
val emojiMode = ThreadLocal<Any>()
|
val emojiMode = ThreadLocal<Any>()
|
||||||
|
|
||||||
|
val santaClaus: String get() = if (emojiMode.get() != null) "$CODE_SANTA_CLAUS " else ""
|
||||||
val diamond: String get() = if (emojiMode.get() != null) "$CODE_DIAMOND " else ""
|
val diamond: String get() = if (emojiMode.get() != null) "$CODE_DIAMOND " else ""
|
||||||
val bagOfCash: String get() = if (emojiMode.get() != null) "$CODE_BAG_OF_CASH " else ""
|
val bagOfCash: String get() = if (emojiMode.get() != null) "$CODE_BAG_OF_CASH " else ""
|
||||||
val newspaper: String get() = if (emojiMode.get() != null) "$CODE_NEWSPAPER " else ""
|
val newspaper: String get() = if (emojiMode.get() != null) "$CODE_NEWSPAPER " else ""
|
||||||
|
@ -67,12 +67,12 @@ abstract class AbstractStateReplacementFlow<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract protected fun assembleProposal(stateRef: StateRef, modification: T, stx: SignedTransaction): Proposal<T>
|
abstract protected fun assembleProposal(stateRef: StateRef, modification: T, stx: SignedTransaction): Proposal<T>
|
||||||
abstract protected fun assembleTx(): Pair<SignedTransaction, List<CompositeKey>>
|
abstract protected fun assembleTx(): Pair<SignedTransaction, Iterable<CompositeKey>>
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun collectSignatures(participants: List<CompositeKey>, stx: SignedTransaction): List<DigitalSignature.WithKey> {
|
private fun collectSignatures(participants: Iterable<CompositeKey>, stx: SignedTransaction): List<DigitalSignature.WithKey> {
|
||||||
val parties = participants.map {
|
val parties = participants.map {
|
||||||
val participantNode = serviceHub.networkMapCache.getNodeByCompositeKey(it) ?:
|
val participantNode = serviceHub.networkMapCache.getNodeByLegalIdentityKey(it) ?:
|
||||||
throw IllegalStateException("Participant $it to state $originalState not found on the network")
|
throw IllegalStateException("Participant $it to state $originalState not found on the network")
|
||||||
participantNode.legalIdentity
|
participantNode.legalIdentity
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
package net.corda.flows
|
package net.corda.flows
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.ContractState
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.contracts.StateAndRef
|
|
||||||
import net.corda.core.contracts.StateRef
|
|
||||||
import net.corda.core.contracts.TransactionType
|
|
||||||
import net.corda.core.crypto.CompositeKey
|
import net.corda.core.crypto.CompositeKey
|
||||||
import net.corda.core.crypto.Party
|
import net.corda.core.crypto.Party
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.core.utilities.UntrustworthyData
|
import net.corda.core.utilities.UntrustworthyData
|
||||||
import net.corda.flows.NotaryChangeFlow.Acceptor
|
import net.corda.flows.NotaryChangeFlow.Acceptor
|
||||||
@ -36,17 +34,66 @@ object NotaryChangeFlow : AbstractStateReplacementFlow<Party>() {
|
|||||||
override fun assembleProposal(stateRef: StateRef, modification: Party, stx: SignedTransaction): AbstractStateReplacementFlow.Proposal<Party>
|
override fun assembleProposal(stateRef: StateRef, modification: Party, stx: SignedTransaction): AbstractStateReplacementFlow.Proposal<Party>
|
||||||
= Proposal(stateRef, modification, stx)
|
= Proposal(stateRef, modification, stx)
|
||||||
|
|
||||||
override fun assembleTx(): Pair<SignedTransaction, List<CompositeKey>> {
|
override fun assembleTx(): Pair<SignedTransaction, Iterable<CompositeKey>> {
|
||||||
val state = originalState.state
|
val state = originalState.state
|
||||||
val newState = state.withNotary(modification)
|
val tx = TransactionType.NotaryChange.Builder(originalState.state.notary)
|
||||||
val participants = state.data.participants
|
|
||||||
val tx = TransactionType.NotaryChange.Builder(originalState.state.notary).withItems(originalState, newState)
|
val participants: Iterable<CompositeKey>
|
||||||
|
|
||||||
|
if (state.encumbrance == null) {
|
||||||
|
val modifiedState = TransactionState(state.data, modification)
|
||||||
|
tx.addInputState(originalState)
|
||||||
|
tx.addOutputState(modifiedState)
|
||||||
|
participants = state.data.participants
|
||||||
|
} else {
|
||||||
|
participants = resolveEncumbrances(tx)
|
||||||
|
}
|
||||||
|
|
||||||
val myKey = serviceHub.legalIdentityKey
|
val myKey = serviceHub.legalIdentityKey
|
||||||
tx.signWith(myKey)
|
tx.signWith(myKey)
|
||||||
|
|
||||||
val stx = tx.toSignedTransaction(false)
|
val stx = tx.toSignedTransaction(false)
|
||||||
|
|
||||||
return Pair(stx, participants)
|
return Pair(stx, participants)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the notary change state transitions to the [tx] builder for the [originalState] and its encumbrance
|
||||||
|
* state chain (encumbrance states might be themselves encumbered by other states).
|
||||||
|
*
|
||||||
|
* @return union of all added states' participants
|
||||||
|
*/
|
||||||
|
private fun resolveEncumbrances(tx: TransactionBuilder): Iterable<CompositeKey> {
|
||||||
|
val stateRef = originalState.ref
|
||||||
|
val txId = stateRef.txhash
|
||||||
|
val issuingTx = serviceHub.storageService.validatedTransactions.getTransaction(txId) ?: throw IllegalStateException("Transaction $txId not found")
|
||||||
|
val outputs = issuingTx.tx.outputs
|
||||||
|
|
||||||
|
val participants = mutableSetOf<CompositeKey>()
|
||||||
|
|
||||||
|
var nextStateIndex = stateRef.index
|
||||||
|
var newOutputPosition = tx.outputStates().size
|
||||||
|
while (true) {
|
||||||
|
val nextState = outputs[nextStateIndex]
|
||||||
|
tx.addInputState(StateAndRef(nextState, StateRef(txId, nextStateIndex)))
|
||||||
|
participants.addAll(nextState.data.participants)
|
||||||
|
|
||||||
|
if (nextState.encumbrance == null) {
|
||||||
|
val modifiedState = TransactionState(nextState.data, modification)
|
||||||
|
tx.addOutputState(modifiedState)
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
val modifiedState = TransactionState(nextState.data, modification, newOutputPosition + 1)
|
||||||
|
tx.addOutputState(modifiedState)
|
||||||
|
nextStateIndex = nextState.encumbrance
|
||||||
|
}
|
||||||
|
|
||||||
|
newOutputPosition++
|
||||||
|
}
|
||||||
|
|
||||||
|
return participants
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Acceptor(otherSide: Party,
|
class Acceptor(otherSide: Party,
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package net.corda.flows
|
package net.corda.flows
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.DigitalSignature
|
||||||
|
import net.corda.core.crypto.Party
|
||||||
|
import net.corda.core.crypto.SignedData
|
||||||
|
import net.corda.core.crypto.signWithECDSA
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.node.services.TimestampChecker
|
import net.corda.core.node.services.TimestampChecker
|
||||||
import net.corda.core.node.services.UniquenessException
|
import net.corda.core.node.services.UniquenessException
|
||||||
@ -47,7 +50,7 @@ object NotaryFlow {
|
|||||||
try {
|
try {
|
||||||
stx.verifySignatures(notaryParty.owningKey)
|
stx.verifySignatures(notaryParty.owningKey)
|
||||||
} catch (ex: SignedTransaction.SignaturesMissingException) {
|
} catch (ex: SignedTransaction.SignaturesMissingException) {
|
||||||
throw NotaryException(NotaryError.SignaturesMissing(ex.missing))
|
throw NotaryException(NotaryError.SignaturesMissing(ex))
|
||||||
}
|
}
|
||||||
|
|
||||||
val response = sendAndReceive<Result>(notaryParty, SignRequest(stx))
|
val response = sendAndReceive<Result>(notaryParty, SignRequest(stx))
|
||||||
@ -129,13 +132,22 @@ object NotaryFlow {
|
|||||||
open fun beforeCommit(stx: SignedTransaction) {
|
open fun beforeCommit(stx: SignedTransaction) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A NotaryException is thrown if any of the states have been consumed by a different transaction. Note that
|
||||||
|
* this method does not throw an exception when input states are present multiple times within the transaction.
|
||||||
|
*/
|
||||||
private fun commitInputStates(tx: WireTransaction) {
|
private fun commitInputStates(tx: WireTransaction) {
|
||||||
try {
|
try {
|
||||||
uniquenessProvider.commit(tx.inputs, tx.id, otherSide)
|
uniquenessProvider.commit(tx.inputs, tx.id, otherSide)
|
||||||
} catch (e: UniquenessException) {
|
} catch (e: UniquenessException) {
|
||||||
val conflictData = e.error.serialize()
|
val conflicts = tx.inputs.filterIndexed { i, stateRef ->
|
||||||
val signedConflict = SignedData(conflictData, sign(conflictData.bytes))
|
val consumingTx = e.error.stateHistory[stateRef]
|
||||||
throw NotaryException(NotaryError.Conflict(tx, signedConflict))
|
consumingTx != null && consumingTx != UniquenessProvider.ConsumingTx(tx.id, i, otherSide)
|
||||||
|
}
|
||||||
|
if (conflicts.isNotEmpty()) {
|
||||||
|
// TODO: Create a new UniquenessException that only contains the conflicts filtered above.
|
||||||
|
throw notaryException(tx, e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,6 +155,12 @@ object NotaryFlow {
|
|||||||
val mySigningKey = serviceHub.notaryIdentityKey
|
val mySigningKey = serviceHub.notaryIdentityKey
|
||||||
return mySigningKey.signWithECDSA(bits)
|
return mySigningKey.signWithECDSA(bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun notaryException(tx: WireTransaction, e: UniquenessException): NotaryException {
|
||||||
|
val conflictData = e.error.serialize()
|
||||||
|
val signedConflict = SignedData(conflictData, sign(conflictData.bytes))
|
||||||
|
return NotaryException(NotaryError.Conflict(tx, signedConflict))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SignRequest(val tx: SignedTransaction)
|
data class SignRequest(val tx: SignedTransaction)
|
||||||
@ -166,9 +184,10 @@ sealed class NotaryError {
|
|||||||
/** Thrown if the time specified in the timestamp command is outside the allowed tolerance */
|
/** Thrown if the time specified in the timestamp command is outside the allowed tolerance */
|
||||||
class TimestampInvalid : NotaryError()
|
class TimestampInvalid : NotaryError()
|
||||||
|
|
||||||
class TransactionInvalid : NotaryError()
|
class TransactionInvalid(val msg: String) : NotaryError()
|
||||||
|
class SignaturesInvalid(val msg: String): NotaryError()
|
||||||
|
|
||||||
class SignaturesMissing(val missingSigners: Set<CompositeKey>) : NotaryError() {
|
class SignaturesMissing(val cause: SignedTransaction.SignaturesMissingException) : NotaryError() {
|
||||||
override fun toString() = "Missing signatures from: ${missingSigners.map { it.toBase58String() }}"
|
override fun toString() = cause.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package net.corda.flows
|
package net.corda.flows
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import net.corda.core.messaging.MessagingService
|
import net.corda.core.messaging.*
|
||||||
import net.corda.core.messaging.SingleMessageRecipient
|
|
||||||
import net.corda.core.messaging.onNext
|
|
||||||
import net.corda.core.messaging.send
|
|
||||||
import net.corda.core.node.services.DEFAULT_SESSION_ID
|
import net.corda.core.node.services.DEFAULT_SESSION_ID
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,7 +18,7 @@ interface ServiceRequestMessage {
|
|||||||
*/
|
*/
|
||||||
fun <R : Any> MessagingService.sendRequest(topic: String,
|
fun <R : Any> MessagingService.sendRequest(topic: String,
|
||||||
request: ServiceRequestMessage,
|
request: ServiceRequestMessage,
|
||||||
target: SingleMessageRecipient): ListenableFuture<R> {
|
target: MessageRecipients): ListenableFuture<R> {
|
||||||
val responseFuture = onNext<R>(topic, request.sessionID)
|
val responseFuture = onNext<R>(topic, request.sessionID)
|
||||||
send(topic, DEFAULT_SESSION_ID, request, target)
|
send(topic, DEFAULT_SESSION_ID, request, target)
|
||||||
return responseFuture
|
return responseFuture
|
||||||
|
@ -29,8 +29,8 @@ class ValidatingNotaryFlow(otherSide: Party,
|
|||||||
wtx.toLedgerTransaction(serviceHub).verify()
|
wtx.toLedgerTransaction(serviceHub).verify()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
when (e) {
|
when (e) {
|
||||||
is TransactionVerificationException,
|
is TransactionVerificationException -> NotaryException(NotaryError.TransactionInvalid(e.toString()))
|
||||||
is SignatureException -> throw NotaryException(NotaryError.TransactionInvalid())
|
is SignatureException -> throw NotaryException(NotaryError.SignaturesInvalid(e.toString()))
|
||||||
else -> throw e
|
else -> throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -40,7 +40,7 @@ class ValidatingNotaryFlow(otherSide: Party,
|
|||||||
try {
|
try {
|
||||||
stx.verifySignatures(serviceHub.myInfo.notaryIdentity.owningKey)
|
stx.verifySignatures(serviceHub.myInfo.notaryIdentity.owningKey)
|
||||||
} catch(e: SignedTransaction.SignaturesMissingException) {
|
} catch(e: SignedTransaction.SignaturesMissingException) {
|
||||||
throw NotaryException(NotaryError.SignaturesMissing(e.missing))
|
throw NotaryException(NotaryError.SignaturesMissing(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
47
core/src/test/kotlin/net/corda/core/UtilsTest.kt
Normal file
47
core/src/test/kotlin/net/corda/core/UtilsTest.kt
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package net.corda.core
|
||||||
|
|
||||||
|
import org.assertj.core.api.Assertions.*
|
||||||
|
import org.junit.Test
|
||||||
|
import rx.subjects.PublishSubject
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class UtilsTest {
|
||||||
|
@Test
|
||||||
|
fun `toFuture - single item observable`() {
|
||||||
|
val subject = PublishSubject.create<String>()
|
||||||
|
val future = subject.toFuture()
|
||||||
|
subject.onNext("Hello")
|
||||||
|
assertThat(future.getOrThrow()).isEqualTo("Hello")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `toFuture - empty obserable`() {
|
||||||
|
val subject = PublishSubject.create<String>()
|
||||||
|
val future = subject.toFuture()
|
||||||
|
subject.onCompleted()
|
||||||
|
assertThatExceptionOfType(NoSuchElementException::class.java).isThrownBy {
|
||||||
|
future.getOrThrow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `toFuture - more than one item observable`() {
|
||||||
|
val subject = PublishSubject.create<String>()
|
||||||
|
val future = subject.toFuture()
|
||||||
|
subject.onNext("Hello")
|
||||||
|
subject.onNext("World")
|
||||||
|
subject.onCompleted()
|
||||||
|
assertThat(future.getOrThrow()).isEqualTo("Hello")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `toFuture - erroring observable`() {
|
||||||
|
val subject = PublishSubject.create<String>()
|
||||||
|
val future = subject.toFuture()
|
||||||
|
val exception = Exception("Error")
|
||||||
|
subject.onError(exception)
|
||||||
|
assertThatThrownBy {
|
||||||
|
future.getOrThrow()
|
||||||
|
}.isSameAs(exception)
|
||||||
|
}
|
||||||
|
}
|
26
core/src/test/kotlin/net/corda/core/contracts/AmountTests.kt
Normal file
26
core/src/test/kotlin/net/corda/core/contracts/AmountTests.kt
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package net.corda.core.contracts
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests of the [Amount] class.
|
||||||
|
*/
|
||||||
|
class AmountTests {
|
||||||
|
@Test
|
||||||
|
fun basicCurrency() {
|
||||||
|
val expected = 1000L
|
||||||
|
val amount = Amount(expected, GBP)
|
||||||
|
assertEquals(expected, amount.quantity)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun decimalConversion() {
|
||||||
|
val quantity = 1234L
|
||||||
|
val amount = Amount(quantity, GBP)
|
||||||
|
val expected = BigDecimal("12.34")
|
||||||
|
assertEquals(expected, amount.toDecimal())
|
||||||
|
assertEquals(amount, Amount.fromDecimal(amount.toDecimal(), amount.token))
|
||||||
|
}
|
||||||
|
}
|
@ -12,32 +12,28 @@ import org.junit.Test
|
|||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
|
|
||||||
val TEST_TIMELOCK_ID = TransactionEncumbranceTests.TestTimeLock()
|
val TEST_TIMELOCK_ID = TransactionEncumbranceTests.DummyTimeLock()
|
||||||
|
|
||||||
class TransactionEncumbranceTests {
|
class TransactionEncumbranceTests {
|
||||||
val defaultIssuer = MEGA_CORP.ref(1)
|
val defaultIssuer = MEGA_CORP.ref(1)
|
||||||
val encumberedState = Cash.State(
|
|
||||||
amount = 1000.DOLLARS `issued by` defaultIssuer,
|
val state = Cash.State(
|
||||||
owner = DUMMY_PUBKEY_1,
|
|
||||||
encumbrance = 1
|
|
||||||
)
|
|
||||||
val unencumberedState = Cash.State(
|
|
||||||
amount = 1000.DOLLARS `issued by` defaultIssuer,
|
amount = 1000.DOLLARS `issued by` defaultIssuer,
|
||||||
owner = DUMMY_PUBKEY_1
|
owner = DUMMY_PUBKEY_1
|
||||||
)
|
)
|
||||||
val stateWithNewOwner = encumberedState.copy(owner = DUMMY_PUBKEY_2)
|
val stateWithNewOwner = state.copy(owner = DUMMY_PUBKEY_2)
|
||||||
|
|
||||||
val FOUR_PM = Instant.parse("2015-04-17T16:00:00.00Z")
|
val FOUR_PM = Instant.parse("2015-04-17T16:00:00.00Z")
|
||||||
val FIVE_PM = FOUR_PM.plus(1, ChronoUnit.HOURS)
|
val FIVE_PM = FOUR_PM.plus(1, ChronoUnit.HOURS)
|
||||||
val FIVE_PM_TIMELOCK = TestTimeLock.State(FIVE_PM)
|
val timeLock = DummyTimeLock.State(FIVE_PM)
|
||||||
|
|
||||||
|
class DummyTimeLock : Contract {
|
||||||
class TestTimeLock : Contract {
|
override val legalContractReference = SecureHash.sha256("DummyTimeLock")
|
||||||
override val legalContractReference = SecureHash.sha256("TestTimeLock")
|
|
||||||
override fun verify(tx: TransactionForContract) {
|
override fun verify(tx: TransactionForContract) {
|
||||||
|
val timeLockInput = tx.inputs.filterIsInstance<State>().singleOrNull() ?: return
|
||||||
val time = tx.timestamp?.before ?: throw IllegalArgumentException("Transactions containing time-locks must be timestamped")
|
val time = tx.timestamp?.before ?: throw IllegalArgumentException("Transactions containing time-locks must be timestamped")
|
||||||
requireThat {
|
requireThat {
|
||||||
"the time specified in the time-lock has passed" by
|
"the time specified in the time-lock has passed" by (time >= timeLockInput.validFrom)
|
||||||
(time >= tx.inputs.filterIsInstance<TestTimeLock.State>().single().validFrom)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,110 +46,110 @@ class TransactionEncumbranceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun trivial() {
|
fun `state can be encumbered`() {
|
||||||
// A transaction containing an input state that is encumbered must fail if the encumbrance has not been presented
|
ledger {
|
||||||
// on the input states.
|
|
||||||
transaction {
|
transaction {
|
||||||
input { encumberedState }
|
input { state }
|
||||||
output { unencumberedState }
|
output(encumbrance = 1) { stateWithNewOwner }
|
||||||
|
output("5pm time-lock") { timeLock }
|
||||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
this `fails with` "Missing required encumbrance 1 in INPUT"
|
verifies()
|
||||||
}
|
}
|
||||||
// An encumbered state must not be encumbered by itself.
|
|
||||||
transaction {
|
|
||||||
input { unencumberedState }
|
|
||||||
input { unencumberedState }
|
|
||||||
output { unencumberedState }
|
|
||||||
// The encumbered state refers to an encumbrance in position 1, so what follows is wrong.
|
|
||||||
output { encumberedState }
|
|
||||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
|
||||||
this `fails with` "Missing required encumbrance 1 in OUTPUT"
|
|
||||||
}
|
}
|
||||||
// An encumbered state must not reference an index greater than the size of the output states.
|
|
||||||
// In this test, the output encumbered state refers to an encumbrance in position 1, but there is only one output.
|
|
||||||
transaction {
|
|
||||||
input { unencumberedState }
|
|
||||||
// The encumbered state refers to an encumbrance in position 1, so there should be at least 2 outputs.
|
|
||||||
output { encumberedState }
|
|
||||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
|
||||||
this `fails with` "Missing required encumbrance 1 in OUTPUT"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testEncumbranceEffects() {
|
fun `state can transition if encumbrance rules are met`() {
|
||||||
// This test fails because the encumbered state is pointing to the ordinary cash state as the encumbrance,
|
|
||||||
// instead of the timelock by mistake, so when we try and use it the transaction fails as we didn't include the
|
|
||||||
// encumbrance cash state.
|
|
||||||
ledger {
|
ledger {
|
||||||
unverifiedTransaction {
|
unverifiedTransaction {
|
||||||
output("state encumbered by 5pm time-lock") { encumberedState }
|
output("state encumbered by 5pm time-lock") { state }
|
||||||
output { unencumberedState }
|
output("5pm time-lock") { timeLock }
|
||||||
output("5pm time-lock") { FIVE_PM_TIMELOCK }
|
|
||||||
}
|
|
||||||
transaction {
|
|
||||||
input("state encumbered by 5pm time-lock")
|
|
||||||
input("5pm time-lock")
|
|
||||||
output { stateWithNewOwner }
|
|
||||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
|
||||||
timestamp(FIVE_PM)
|
|
||||||
this `fails with` "Missing required encumbrance 1 in INPUT"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// A transaction containing an input state that is encumbered must fail if the encumbrance is not in the correct
|
|
||||||
// transaction. In this test, the intended encumbrance is presented alongside the encumbered state for consumption,
|
|
||||||
// although the encumbered state always refers to the encumbrance produced in the same transaction, and the in this case
|
|
||||||
// the encumbrance was created in a separate transaction.
|
|
||||||
ledger {
|
|
||||||
unverifiedTransaction {
|
|
||||||
output("state encumbered by 5pm time-lock") { encumberedState }
|
|
||||||
output { unencumberedState }
|
|
||||||
}
|
|
||||||
unverifiedTransaction {
|
|
||||||
output("5pm time-lock") { FIVE_PM_TIMELOCK }
|
|
||||||
}
|
|
||||||
transaction {
|
|
||||||
input("state encumbered by 5pm time-lock")
|
|
||||||
input("5pm time-lock")
|
|
||||||
output { stateWithNewOwner }
|
|
||||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
|
||||||
timestamp(FIVE_PM)
|
|
||||||
this `fails with` "Missing required encumbrance 1 in INPUT"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// A transaction with an input state that is encumbered must succeed if the rules of the encumbrance are met.
|
|
||||||
ledger {
|
|
||||||
unverifiedTransaction {
|
|
||||||
output("state encumbered by 5pm time-lock") { encumberedState }
|
|
||||||
output("5pm time-lock") { FIVE_PM_TIMELOCK }
|
|
||||||
}
|
}
|
||||||
// Un-encumber the output if the time of the transaction is later than the timelock.
|
// Un-encumber the output if the time of the transaction is later than the timelock.
|
||||||
transaction {
|
transaction {
|
||||||
input("state encumbered by 5pm time-lock")
|
input("state encumbered by 5pm time-lock")
|
||||||
input("5pm time-lock")
|
input("5pm time-lock")
|
||||||
output { unencumberedState }
|
output { stateWithNewOwner }
|
||||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
timestamp(FIVE_PM)
|
timestamp(FIVE_PM)
|
||||||
this.verifies()
|
verifies()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// A transaction with an input state that is encumbered must fail if the rules of the encumbrance are not met.
|
}
|
||||||
// In this test, the time-lock encumbrance is being processed in a transaction before the time allowed.
|
|
||||||
|
@Test
|
||||||
|
fun `state cannot transition if the encumbrance contract fails to verify`() {
|
||||||
ledger {
|
ledger {
|
||||||
unverifiedTransaction {
|
unverifiedTransaction {
|
||||||
output("state encumbered by 5pm time-lock") { encumberedState }
|
output("state encumbered by 5pm time-lock") { state }
|
||||||
output("5pm time-lock") { FIVE_PM_TIMELOCK }
|
output("5pm time-lock") { timeLock }
|
||||||
}
|
}
|
||||||
// The time of the transaction is earlier than the time specified in the encumbering timelock.
|
// The time of the transaction is earlier than the time specified in the encumbering timelock.
|
||||||
transaction {
|
transaction {
|
||||||
input("state encumbered by 5pm time-lock")
|
input("state encumbered by 5pm time-lock")
|
||||||
input("5pm time-lock")
|
input("5pm time-lock")
|
||||||
output { unencumberedState }
|
output { state }
|
||||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
timestamp(FOUR_PM)
|
timestamp(FOUR_PM)
|
||||||
this `fails with` "the time specified in the time-lock has passed"
|
this `fails with` "the time specified in the time-lock has passed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `state must be consumed along with its encumbrance`() {
|
||||||
|
ledger {
|
||||||
|
unverifiedTransaction {
|
||||||
|
output("state encumbered by 5pm time-lock", encumbrance = 1) { state }
|
||||||
|
output("5pm time-lock") { timeLock }
|
||||||
|
}
|
||||||
|
transaction {
|
||||||
|
input("state encumbered by 5pm time-lock")
|
||||||
|
output { stateWithNewOwner }
|
||||||
|
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
|
timestamp(FIVE_PM)
|
||||||
|
this `fails with` "Missing required encumbrance 1 in INPUT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `state cannot be encumbered by itself`() {
|
||||||
|
transaction {
|
||||||
|
input { state }
|
||||||
|
output(encumbrance = 0) { stateWithNewOwner }
|
||||||
|
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
|
this `fails with` "Missing required encumbrance 0 in OUTPUT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `encumbrance state index must be valid`() {
|
||||||
|
transaction {
|
||||||
|
input { state }
|
||||||
|
output(encumbrance = 2) { stateWithNewOwner }
|
||||||
|
output { timeLock }
|
||||||
|
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
|
this `fails with` "Missing required encumbrance 2 in OUTPUT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `correct encumbrance state must be provided`() {
|
||||||
|
ledger {
|
||||||
|
unverifiedTransaction {
|
||||||
|
output("state encumbered by some other state", encumbrance = 1) { state }
|
||||||
|
output("some other state") { state }
|
||||||
|
output("5pm time-lock") { timeLock }
|
||||||
|
}
|
||||||
|
transaction {
|
||||||
|
input("state encumbered by some other state")
|
||||||
|
input("5pm time-lock")
|
||||||
|
output { stateWithNewOwner }
|
||||||
|
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
|
timestamp(FIVE_PM)
|
||||||
|
this `fails with` "Missing required encumbrance 1 in INPUT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,6 +82,33 @@ class TransactionTests {
|
|||||||
transaction.type.verify(transaction)
|
transaction.type.verify(transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `transaction verification fails for duplicate inputs`() {
|
||||||
|
val baseOutState = TransactionState(DummyContract.SingleOwnerState(0, ALICE_PUBKEY), DUMMY_NOTARY)
|
||||||
|
val stateRef = StateRef(SecureHash.randomSHA256(), 0)
|
||||||
|
val stateAndRef = StateAndRef(baseOutState, stateRef)
|
||||||
|
val inputs = listOf(stateAndRef, stateAndRef)
|
||||||
|
val outputs = listOf(baseOutState)
|
||||||
|
val commands = emptyList<AuthenticatedObject<CommandData>>()
|
||||||
|
val attachments = emptyList<Attachment>()
|
||||||
|
val id = SecureHash.randomSHA256()
|
||||||
|
val signers = listOf(DUMMY_NOTARY_KEY.public.composite)
|
||||||
|
val timestamp: Timestamp? = null
|
||||||
|
val transaction: LedgerTransaction = LedgerTransaction(
|
||||||
|
inputs,
|
||||||
|
outputs,
|
||||||
|
commands,
|
||||||
|
attachments,
|
||||||
|
id,
|
||||||
|
DUMMY_NOTARY,
|
||||||
|
signers,
|
||||||
|
timestamp,
|
||||||
|
TransactionType.General()
|
||||||
|
)
|
||||||
|
|
||||||
|
assertFailsWith<TransactionVerificationException.DuplicateInputStates> { transaction.type.verify(transaction) }
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `general transactions cannot change notary`() {
|
fun `general transactions cannot change notary`() {
|
||||||
val notary: Party = DUMMY_NOTARY
|
val notary: Party = DUMMY_NOTARY
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
package net.corda.core.contracts.clauses
|
|
||||||
|
|
||||||
import net.corda.core.contracts.AuthenticatedObject
|
|
||||||
import net.corda.core.contracts.CommandData
|
|
||||||
import net.corda.core.contracts.TransactionForContract
|
|
||||||
import net.corda.core.crypto.SecureHash
|
|
||||||
import org.junit.Test
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertFailsWith
|
|
||||||
|
|
||||||
class AllCompositionTests {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun minimal() {
|
|
||||||
val counter = AtomicInteger(0)
|
|
||||||
val clause = AllComposition(matchedClause(counter), matchedClause(counter))
|
|
||||||
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
|
|
||||||
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
|
|
||||||
|
|
||||||
// Check that we've run the verify() function of two clauses
|
|
||||||
assertEquals(2, counter.get())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `not all match`() {
|
|
||||||
val clause = AllComposition(matchedClause(), unmatchedClause())
|
|
||||||
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
|
|
||||||
assertFailsWith<IllegalStateException> { verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>()) }
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,31 @@
|
|||||||
|
package net.corda.core.contracts.clauses
|
||||||
|
|
||||||
|
import net.corda.core.contracts.AuthenticatedObject
|
||||||
|
import net.corda.core.contracts.CommandData
|
||||||
|
import net.corda.core.contracts.TransactionForContract
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import org.junit.Test
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
|
||||||
|
class AllOfTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun minimal() {
|
||||||
|
val counter = AtomicInteger(0)
|
||||||
|
val clause = AllOf(matchedClause(counter), matchedClause(counter))
|
||||||
|
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
|
||||||
|
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
|
||||||
|
|
||||||
|
// Check that we've run the verify() function of two clauses
|
||||||
|
assertEquals(2, counter.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `not all match`() {
|
||||||
|
val clause = AllOf(matchedClause(), unmatchedClause())
|
||||||
|
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
|
||||||
|
assertFailsWith<IllegalStateException> { verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>()) }
|
||||||
|
}
|
||||||
|
}
|
@ -1,44 +0,0 @@
|
|||||||
package net.corda.core.contracts.clauses
|
|
||||||
|
|
||||||
import net.corda.core.contracts.AuthenticatedObject
|
|
||||||
import net.corda.core.contracts.CommandData
|
|
||||||
import net.corda.core.contracts.TransactionForContract
|
|
||||||
import net.corda.core.crypto.SecureHash
|
|
||||||
import org.junit.Test
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
class AnyCompositionTests {
|
|
||||||
@Test
|
|
||||||
fun minimal() {
|
|
||||||
val counter = AtomicInteger(0)
|
|
||||||
val clause = AnyComposition(matchedClause(counter), matchedClause(counter))
|
|
||||||
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
|
|
||||||
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
|
|
||||||
|
|
||||||
// Check that we've run the verify() function of two clauses
|
|
||||||
assertEquals(2, counter.get())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `not all match`() {
|
|
||||||
val counter = AtomicInteger(0)
|
|
||||||
val clause = AnyComposition(matchedClause(counter), unmatchedClause(counter))
|
|
||||||
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
|
|
||||||
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
|
|
||||||
|
|
||||||
// Check that we've run the verify() function of one clause
|
|
||||||
assertEquals(1, counter.get())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `none match`() {
|
|
||||||
val counter = AtomicInteger(0)
|
|
||||||
val clause = AnyComposition(unmatchedClause(counter), unmatchedClause(counter))
|
|
||||||
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
|
|
||||||
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
|
|
||||||
|
|
||||||
// Check that we've run the verify() function of neither clause
|
|
||||||
assertEquals(0, counter.get())
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,44 @@
|
|||||||
|
package net.corda.core.contracts.clauses
|
||||||
|
|
||||||
|
import net.corda.core.contracts.AuthenticatedObject
|
||||||
|
import net.corda.core.contracts.CommandData
|
||||||
|
import net.corda.core.contracts.TransactionForContract
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import org.junit.Test
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
|
||||||
|
class AnyOfTests {
|
||||||
|
@Test
|
||||||
|
fun minimal() {
|
||||||
|
val counter = AtomicInteger(0)
|
||||||
|
val clause = AnyOf(matchedClause(counter), matchedClause(counter))
|
||||||
|
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
|
||||||
|
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
|
||||||
|
|
||||||
|
// Check that we've run the verify() function of two clauses
|
||||||
|
assertEquals(2, counter.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `not all match`() {
|
||||||
|
val counter = AtomicInteger(0)
|
||||||
|
val clause = AnyOf(matchedClause(counter), unmatchedClause(counter))
|
||||||
|
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
|
||||||
|
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
|
||||||
|
|
||||||
|
// Check that we've run the verify() function of one clause
|
||||||
|
assertEquals(1, counter.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `none match`() {
|
||||||
|
val counter = AtomicInteger(0)
|
||||||
|
val clause = AnyOf(unmatchedClause(counter), unmatchedClause(counter))
|
||||||
|
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
|
||||||
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
|
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import net.corda.core.contracts.TransactionForContract
|
|||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
internal fun matchedClause(counter: AtomicInteger? = null) = object : Clause<ContractState, CommandData, Unit>() {
|
internal fun matchedClause(counter: AtomicInteger? = null) = object : Clause<ContractState, CommandData, Unit>() {
|
||||||
|
override val requiredCommands: Set<Class<out CommandData>> = emptySet()
|
||||||
override fun verify(tx: TransactionForContract,
|
override fun verify(tx: TransactionForContract,
|
||||||
inputs: List<ContractState>,
|
inputs: List<ContractState>,
|
||||||
outputs: List<ContractState>,
|
outputs: List<ContractState>,
|
||||||
|
@ -1,205 +0,0 @@
|
|||||||
package net.corda.core.crypto
|
|
||||||
|
|
||||||
import org.junit.BeforeClass
|
|
||||||
import org.junit.Test
|
|
||||||
import java.net.Socket
|
|
||||||
import java.security.KeyStore
|
|
||||||
import java.security.cert.CertificateException
|
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
import javax.net.ssl.SSLEngine
|
|
||||||
import javax.net.ssl.TrustManagerFactory
|
|
||||||
import javax.net.ssl.X509ExtendedTrustManager
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertFailsWith
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
// TODO: This suppress is needed due to KT-260, fixed in Kotlin 1.0.4 so remove after upgrading.
|
|
||||||
@Suppress("CAST_NEVER_SUCCEEDS")
|
|
||||||
class WhitelistTrustManagerTest {
|
|
||||||
companion object {
|
|
||||||
@BeforeClass
|
|
||||||
@JvmStatic
|
|
||||||
fun registerTrustManager() {
|
|
||||||
// Validate original factory
|
|
||||||
assertEquals("PKIX", TrustManagerFactory.getDefaultAlgorithm())
|
|
||||||
|
|
||||||
//register for all tests
|
|
||||||
registerWhitelistTrustManager()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getTrustmanagerAndCert(whitelist: String, certificateName: String): Pair<X509ExtendedTrustManager, X509Certificate> {
|
|
||||||
WhitelistTrustManagerProvider.addWhitelistEntry(whitelist)
|
|
||||||
|
|
||||||
val caCertAndKey = X509Utilities.createSelfSignedCACert(certificateName)
|
|
||||||
|
|
||||||
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
|
|
||||||
keyStore.load(null, null)
|
|
||||||
keyStore.setCertificateEntry("cacert", caCertAndKey.certificate)
|
|
||||||
|
|
||||||
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
|
||||||
trustManagerFactory.init(keyStore)
|
|
||||||
|
|
||||||
return Pair(trustManagerFactory.trustManagers.first() as X509ExtendedTrustManager, caCertAndKey.certificate)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getTrustmanagerAndUntrustedChainCert(): Pair<X509ExtendedTrustManager, X509Certificate> {
|
|
||||||
WhitelistTrustManagerProvider.addWhitelistEntry("test.r3corda.com")
|
|
||||||
|
|
||||||
val otherCaCertAndKey = X509Utilities.createSelfSignedCACert("bad root")
|
|
||||||
|
|
||||||
val caCertAndKey = X509Utilities.createSelfSignedCACert("good root")
|
|
||||||
|
|
||||||
val subject = X509Utilities.getDevX509Name("test.r3corda.com")
|
|
||||||
val serverKey = X509Utilities.generateECDSAKeyPairForSSL()
|
|
||||||
val serverCert = X509Utilities.createServerCert(subject,
|
|
||||||
serverKey.public,
|
|
||||||
otherCaCertAndKey,
|
|
||||||
listOf(),
|
|
||||||
listOf())
|
|
||||||
|
|
||||||
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
|
|
||||||
keyStore.load(null, null)
|
|
||||||
keyStore.setCertificateEntry("cacert", caCertAndKey.certificate)
|
|
||||||
|
|
||||||
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
|
||||||
trustManagerFactory.init(keyStore)
|
|
||||||
|
|
||||||
return Pair(trustManagerFactory.trustManagers.first() as X509ExtendedTrustManager, serverCert)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `getDefaultAlgorithm TrustManager is WhitelistTrustManager`() {
|
|
||||||
registerWhitelistTrustManager() // Check double register is safe
|
|
||||||
|
|
||||||
assertEquals("whitelistTrustManager", TrustManagerFactory.getDefaultAlgorithm())
|
|
||||||
|
|
||||||
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
|
||||||
|
|
||||||
trustManagerFactory.init(null as KeyStore?)
|
|
||||||
|
|
||||||
val trustManagers = trustManagerFactory.trustManagers
|
|
||||||
|
|
||||||
assertTrue { trustManagers.all { it is WhitelistTrustManager } }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `check certificate works for whitelisted certificate and specific domain`() {
|
|
||||||
val (trustManager, cert) = getTrustmanagerAndCert("test.r3corda.com", "test.r3corda.com")
|
|
||||||
|
|
||||||
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM)
|
|
||||||
|
|
||||||
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?)
|
|
||||||
|
|
||||||
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?)
|
|
||||||
|
|
||||||
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM)
|
|
||||||
|
|
||||||
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?)
|
|
||||||
|
|
||||||
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `check certificate works for specific certificate and wildcard permitted domain`() {
|
|
||||||
val (trustManager, cert) = getTrustmanagerAndCert("*.r3corda.com", "test.r3corda.com")
|
|
||||||
|
|
||||||
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM)
|
|
||||||
|
|
||||||
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?)
|
|
||||||
|
|
||||||
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?)
|
|
||||||
|
|
||||||
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM)
|
|
||||||
|
|
||||||
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?)
|
|
||||||
|
|
||||||
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `check certificate works for wildcard certificate and non wildcard domain`() {
|
|
||||||
val (trustManager, cert) = getTrustmanagerAndCert("*.r3corda.com", "test.r3corda.com")
|
|
||||||
|
|
||||||
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM)
|
|
||||||
|
|
||||||
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?)
|
|
||||||
|
|
||||||
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?)
|
|
||||||
|
|
||||||
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM)
|
|
||||||
|
|
||||||
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?)
|
|
||||||
|
|
||||||
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `check unknown certificate rejected`() {
|
|
||||||
val (trustManager, cert) = getTrustmanagerAndCert("test.r3corda.com", "test.notr3.com")
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `check unknown wildcard certificate rejected`() {
|
|
||||||
val (trustManager, cert) = getTrustmanagerAndCert("test.r3corda.com", "*.notr3.com")
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `check unknown certificate rejected against mismatched wildcard`() {
|
|
||||||
val (trustManager, cert) = getTrustmanagerAndCert("*.r3corda.com", "test.notr3.com")
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `check certificate signed by untrusted root is still rejected, despite matched name`() {
|
|
||||||
val (trustManager, cert) = getTrustmanagerAndUntrustedChainCert()
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
|
|
||||||
|
|
||||||
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
|
|
||||||
}
|
|
||||||
}
|
|
@ -46,7 +46,7 @@ class VaultUpdateTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `something plus nothing is something`() {
|
fun `something plus nothing is something`() {
|
||||||
val before = Vault.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef2, stateAndRef3))
|
val before = Vault.Update(setOf(stateAndRef0, stateAndRef1), setOf(stateAndRef2, stateAndRef3))
|
||||||
val after = before + Vault.NoUpdate
|
val after = before + Vault.NoUpdate
|
||||||
assertEquals(before, after)
|
assertEquals(before, after)
|
||||||
}
|
}
|
||||||
@ -54,32 +54,32 @@ class VaultUpdateTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `nothing plus something is something`() {
|
fun `nothing plus something is something`() {
|
||||||
val before = Vault.NoUpdate
|
val before = Vault.NoUpdate
|
||||||
val after = before + Vault.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef2, stateAndRef3))
|
val after = before + Vault.Update(setOf(stateAndRef0, stateAndRef1), setOf(stateAndRef2, stateAndRef3))
|
||||||
val expected = Vault.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef2, stateAndRef3))
|
val expected = Vault.Update(setOf(stateAndRef0, stateAndRef1), setOf(stateAndRef2, stateAndRef3))
|
||||||
assertEquals(expected, after)
|
assertEquals(expected, after)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `something plus consume state 0 is something without state 0 output`() {
|
fun `something plus consume state 0 is something without state 0 output`() {
|
||||||
val before = Vault.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1))
|
val before = Vault.Update(setOf(stateAndRef2, stateAndRef3), setOf(stateAndRef0, stateAndRef1))
|
||||||
val after = before + Vault.Update(setOf(stateRef0), setOf())
|
val after = before + Vault.Update(setOf(stateAndRef0), setOf())
|
||||||
val expected = Vault.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef1))
|
val expected = Vault.Update(setOf(stateAndRef2, stateAndRef3), setOf(stateAndRef1))
|
||||||
assertEquals(expected, after)
|
assertEquals(expected, after)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `something plus produce state 4 is something with additional state 4 output`() {
|
fun `something plus produce state 4 is something with additional state 4 output`() {
|
||||||
val before = Vault.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1))
|
val before = Vault.Update(setOf(stateAndRef2, stateAndRef3), setOf(stateAndRef0, stateAndRef1))
|
||||||
val after = before + Vault.Update(setOf(), setOf(stateAndRef4))
|
val after = before + Vault.Update(setOf(), setOf(stateAndRef4))
|
||||||
val expected = Vault.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1, stateAndRef4))
|
val expected = Vault.Update(setOf(stateAndRef2, stateAndRef3), setOf(stateAndRef0, stateAndRef1, stateAndRef4))
|
||||||
assertEquals(expected, after)
|
assertEquals(expected, after)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `something plus consume states 0 and 1, and produce state 4, is something without state 0 and 1 outputs and only state 4 output`() {
|
fun `something plus consume states 0 and 1, and produce state 4, is something without state 0 and 1 outputs and only state 4 output`() {
|
||||||
val before = Vault.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1))
|
val before = Vault.Update(setOf(stateAndRef2, stateAndRef3), setOf(stateAndRef0, stateAndRef1))
|
||||||
val after = before + Vault.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef4))
|
val after = before + Vault.Update(setOf(stateAndRef0, stateAndRef1), setOf(stateAndRef4))
|
||||||
val expected = Vault.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef4))
|
val expected = Vault.Update(setOf(stateAndRef2, stateAndRef3), setOf(stateAndRef4))
|
||||||
assertEquals(expected, after)
|
assertEquals(expected, after)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ class TransactionSerializationTests {
|
|||||||
signedTX.verifySignatures()
|
signedTX.verifySignatures()
|
||||||
|
|
||||||
// Corrupt the data and ensure the signature catches the problem.
|
// Corrupt the data and ensure the signature catches the problem.
|
||||||
signedTX.id.bytes[5] = 0
|
signedTX.id.bytes[5] = signedTX.id.bytes[5].inc()
|
||||||
assertFailsWith(SignatureException::class) {
|
assertFailsWith(SignatureException::class) {
|
||||||
signedTX.verifySignatures()
|
signedTX.verifySignatures()
|
||||||
}
|
}
|
||||||
|
BIN
docs/build/doctrees/CLI-vs-IDE.doctree
vendored
BIN
docs/build/doctrees/CLI-vs-IDE.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/azure-vm.doctree
vendored
Normal file
BIN
docs/build/doctrees/azure-vm.doctree
vendored
Normal file
Binary file not shown.
BIN
docs/build/doctrees/building-the-docs.doctree
vendored
BIN
docs/build/doctrees/building-the-docs.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/clauses.doctree
vendored
BIN
docs/build/doctrees/clauses.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/clientrpc.doctree
vendored
BIN
docs/build/doctrees/clientrpc.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/codestyle.doctree
vendored
BIN
docs/build/doctrees/codestyle.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/consensus.doctree
vendored
BIN
docs/build/doctrees/consensus.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/contract-catalogue.doctree
vendored
BIN
docs/build/doctrees/contract-catalogue.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/contract-irs.doctree
vendored
BIN
docs/build/doctrees/contract-irs.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/corda-configuration-file.doctree
vendored
BIN
docs/build/doctrees/corda-configuration-file.doctree
vendored
Binary file not shown.
Binary file not shown.
BIN
docs/build/doctrees/corda-plugins.doctree
vendored
BIN
docs/build/doctrees/corda-plugins.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/creating-a-cordapp.doctree
vendored
BIN
docs/build/doctrees/creating-a-cordapp.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/data-model.doctree
vendored
BIN
docs/build/doctrees/data-model.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/environment.pickle
vendored
BIN
docs/build/doctrees/environment.pickle
vendored
Binary file not shown.
BIN
docs/build/doctrees/event-scheduling.doctree
vendored
BIN
docs/build/doctrees/event-scheduling.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/flow-state-machines.doctree
vendored
BIN
docs/build/doctrees/flow-state-machines.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/flow-testing.doctree
vendored
BIN
docs/build/doctrees/flow-testing.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/further-notes-on-kotlin.doctree
vendored
BIN
docs/build/doctrees/further-notes-on-kotlin.doctree
vendored
Binary file not shown.
Binary file not shown.
BIN
docs/build/doctrees/getting-set-up.doctree
vendored
BIN
docs/build/doctrees/getting-set-up.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/glossary.doctree
vendored
BIN
docs/build/doctrees/glossary.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/index.doctree
vendored
BIN
docs/build/doctrees/index.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/initial-margin-agreement.doctree
vendored
BIN
docs/build/doctrees/initial-margin-agreement.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/initialmarginagreement.doctree
vendored
BIN
docs/build/doctrees/initialmarginagreement.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/inthebox.doctree
vendored
BIN
docs/build/doctrees/inthebox.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/loadtesting.doctree
vendored
BIN
docs/build/doctrees/loadtesting.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/merkle-trees.doctree
vendored
BIN
docs/build/doctrees/merkle-trees.doctree
vendored
Binary file not shown.
BIN
docs/build/doctrees/messaging.doctree
vendored
BIN
docs/build/doctrees/messaging.doctree
vendored
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user