Merge from Corda master

This commit is contained in:
szymonsztuka 2017-10-16 18:03:07 +01:00
commit db0969ebda
706 changed files with 20091 additions and 11187 deletions

11
.ci/README.md Normal file
View File

@ -0,0 +1,11 @@
# !! DO NOT MODIFY THE API FILE IN THIS DIRECTORY !!
The `api-current.txt` file contains a summary of Corda's current public APIs,
as generated by the `api-scanner` Gradle plugin. (See [here](../gradle-plugins/api-scanner/README.md) for a detailed description of this plugin.) It will be regenerated and the copy in this repository updated by the Release Manager with
each new Corda release. It will not be modified otherwise except under special circumstances that will require extra approval.
Deleting or changing the existing Corda APIs listed in `api-current.txt` may
break developers' CorDapps in the next Corda release! Please remember that we
have committed to API Stability for CorDapps.
# !! DO NOT MODIFY THE API FILE IN THIS DIRECTORY !!

3272
.ci/api-current.txt Normal file

File diff suppressed because it is too large Load Diff

48
.ci/check-api-changes.sh Executable file
View File

@ -0,0 +1,48 @@
#!/bin/bash
echo "Starting API Diff"
APIHOME=$(dirname $0)
apiCurrent=$APIHOME/api-current.txt
if [ ! -f $apiCurrent ]; then
echo "Missing $apiCurrent file - cannot check API diff. Please rebase or add it to this release"
exit -1
fi
# Remove the two header lines from the diff output.
diffContents=`diff -u $apiCurrent $APIHOME/../build/api/api-corda-*.txt | tail --lines=+3`
echo "Diff contents:"
echo "$diffContents"
echo
# A removed line means that an API was either deleted or modified.
removals=$(echo "$diffContents" | grep "^-")
removalCount=`grep -v "^$" <<EOF | wc -l
$removals
EOF
`
echo "Number of API removals/changes: "$removalCount
if [ $removalCount -gt 0 ]; then
echo "$removals"
echo
fi
# Adding new abstract methods could also break the API.
newAbstracts=$(echo "$diffContents" | grep "^+" | grep "\(public\|protected\) abstract")
abstractCount=`grep -v "^$" <<EOF | wc -l
$newAbstracts
EOF
`
echo "Number of new abstract APIs: "$abstractCount
if [ $abstractCount -gt 0 ]; then
echo "$newAbstracts"
echo
fi
badChanges=$(($removalCount + $abstractCount))
echo "Exiting with exit code" $badChanges
exit $badChanges

5
.gitignore vendored
View File

@ -34,10 +34,12 @@ lib/quasar.jar
.idea/shelf .idea/shelf
.idea/dataSources .idea/dataSources
.idea/markdown-navigator .idea/markdown-navigator
.idea/runConfigurations
/gradle-plugins/.idea/ /gradle-plugins/.idea/
# Include the -parameters compiler option by default in IntelliJ required for serialization. # Include the -parameters compiler option by default in IntelliJ required for serialization.
!.idea/compiler.xml !.idea/compiler.xml
!.idea/codeStyleSettings.xml
# if you remove the above rule, at least ignore the following: # if you remove the above rule, at least ignore the following:
@ -88,6 +90,9 @@ docs/virtualenv/
# bft-smart # bft-smart
**/config/currentView **/config/currentView
# Node Explorer
/tools/explorer/conf/CordaExplorer.properties
# vim # vim
*.swp *.swp
*.swn *.swn

19
.idea/codeStyleSettings.xml generated Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectCodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value>
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="java.util" withSubpackages="false" static="false" />
<package name="kotlinx.android.synthetic" withSubpackages="true" static="false" />
<package name="tornadofx" withSubpackages="false" static="false" />
</value>
</option>
</JetCodeStyleSettings>
</value>
</option>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</component>
</project>

9
.idea/compiler.xml generated
View File

@ -2,6 +2,8 @@
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8"> <bytecodeTargetLevel target="1.8">
<module name="api-scanner_main" target="1.8" />
<module name="api-scanner_test" target="1.8" />
<module name="attachment-demo_integrationTest" target="1.8" /> <module name="attachment-demo_integrationTest" target="1.8" />
<module name="attachment-demo_main" target="1.8" /> <module name="attachment-demo_main" target="1.8" />
<module name="attachment-demo_test" target="1.8" /> <module name="attachment-demo_test" target="1.8" />
@ -19,6 +21,8 @@
<module name="corda-webserver_integrationTest" target="1.8" /> <module name="corda-webserver_integrationTest" target="1.8" />
<module name="corda-webserver_main" target="1.8" /> <module name="corda-webserver_main" target="1.8" />
<module name="corda-webserver_test" target="1.8" /> <module name="corda-webserver_test" target="1.8" />
<module name="cordapp_main" target="1.8" />
<module name="cordapp_test" target="1.8" />
<module name="cordform-common_main" target="1.8" /> <module name="cordform-common_main" target="1.8" />
<module name="cordform-common_test" target="1.8" /> <module name="cordform-common_test" target="1.8" />
<module name="cordformation_main" target="1.8" /> <module name="cordformation_main" target="1.8" />
@ -42,8 +46,11 @@
<module name="explorer-capsule_test" target="1.6" /> <module name="explorer-capsule_test" target="1.6" />
<module name="explorer_main" target="1.8" /> <module name="explorer_main" target="1.8" />
<module name="explorer_test" target="1.8" /> <module name="explorer_test" target="1.8" />
<module name="finance_integrationTest" target="1.8" />
<module name="finance_main" target="1.8" /> <module name="finance_main" target="1.8" />
<module name="finance_test" target="1.8" /> <module name="finance_test" target="1.8" />
<module name="gradle-plugins-cordform-common_main" target="1.8" />
<module name="gradle-plugins-cordform-common_test" target="1.8" />
<module name="graphs_main" target="1.8" /> <module name="graphs_main" target="1.8" />
<module name="graphs_test" target="1.8" /> <module name="graphs_test" target="1.8" />
<module name="intellij-plugin_main" target="1.8" /> <module name="intellij-plugin_main" target="1.8" />
@ -58,6 +65,8 @@
<module name="jfx_integrationTest" target="1.8" /> <module name="jfx_integrationTest" target="1.8" />
<module name="jfx_main" target="1.8" /> <module name="jfx_main" target="1.8" />
<module name="jfx_test" target="1.8" /> <module name="jfx_test" target="1.8" />
<module name="kryo-hook_main" target="1.8" />
<module name="kryo-hook_test" target="1.8" />
<module name="loadtest_main" target="1.8" /> <module name="loadtest_main" target="1.8" />
<module name="loadtest_test" target="1.8" /> <module name="loadtest_test" target="1.8" />
<module name="mock_main" target="1.8" /> <module name="mock_main" target="1.8" />

3
.idea/scopes/Corda_API.xml generated Normal file
View File

@ -0,0 +1,3 @@
<component name="DependencyValidationManager">
<scope name="Corda API" pattern="src[core_main]:*..*&amp;&amp;!src[core_main]:net.corda.core.internal..*||src[rpc_main]:net.corda.client.rpc.*||src[jackson_main]:net.corda.client.jackson.*" />
</component>

View File

@ -1,10 +1,23 @@
![Corda](https://www.corda.net/wp-content/uploads/2016/11/fg005_corda_b.png) ![Corda](https://www.corda.net/wp-content/uploads/2016/11/fg005_corda_b.png)
<a href="https://ci-master.corda.r3cev.com/viewType.html?buildTypeId=CordaEnterprise_CordaBuild&tab=buildTypeStatusDiv"><img src="https://ci.corda.r3cev.com/app/rest/builds/buildType:Corda_CordaBuild/statusIcon"/></a>
# Corda Enterprise # Corda Enterprise
Corda Enterprise is R3's closed source patch set on top of Corda Open Source. It adds features and improvements that we Corda Enterprise is R3's closed source patch set on top of Corda Open Source. It adds features and improvements that we
plan to charge for. plan to charge for.
Corda is a decentralised database system in which nodes trust each other as little as possible.
## Features
* Smart contracts that can be written in Java and other JVM languages
* Flow framework to manage communication and negotiation between participants
* Peer-to-peer network of nodes
* "Notary" infrastructure to validate uniqueness and sequencing of transactions without global broadcast
* Enables the development and deployment of distributed apps called CorDapps
* Written in [Kotlin](https://kotlinlang.org), targeting the JVM
## Extra features ## Extra features
* Doorman * Doorman
@ -12,3 +25,36 @@ plan to charge for.
* Flow triage screen in Explorer * Flow triage screen in Explorer
* No stupid jokes at startup * No stupid jokes at startup
* SGX * SGX
## Getting started
1. Read the [Getting Started](https://docs.corda.net/getting-set-up.html) documentation
2. Run the [Example CorDapp](https://docs.corda.net/tutorial-cordapp.html)
3. Read about Corda's [Key Concepts](https://docs.corda.net/key-concepts.html)
3. Follow the [Hello, World! tutorial](https://docs.corda.net/hello-world-index.html)
## Useful links
* [Project Website](https://corda.net)
* [Documentation](https://docs.corda.net)
* [Slack Channel](https://slack.corda.net/)
* [Stack Overflow tag](https://stackoverflow.com/questions/tagged/corda)
* [Forum](https://discourse.corda.net)
* [Meetups](https://www.meetup.com/pro/corda/)
* [Training Courses](https://www.corda.net/corda-training/)
## Contributing
Please read [here](./CONTRIBUTING.md).
## License
[Apache 2.0](./LICENSE)
## 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.

View File

@ -40,7 +40,7 @@ buildscript {
ext.jopt_simple_version = '5.0.2' ext.jopt_simple_version = '5.0.2'
ext.jansi_version = '1.14' ext.jansi_version = '1.14'
ext.hibernate_version = '5.2.6.Final' ext.hibernate_version = '5.2.6.Final'
ext.h2_version = '1.4.194' ext.h2_version = '1.4.194' // Update docs if renamed or removed.
ext.rxjava_version = '1.2.4' ext.rxjava_version = '1.2.4'
ext.dokka_version = '0.9.14' ext.dokka_version = '0.9.14'
ext.eddsa_version = '0.2.0' ext.eddsa_version = '0.2.0'
@ -59,7 +59,9 @@ 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' classpath "net.corda.plugins:cordapp:$gradle_plugins_version"
classpath "net.corda.plugins:api-scanner:$gradle_plugins_version"
classpath 'com.github.ben-manes:gradle-versions-plugin:0.15.0'
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}" classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}"
classpath "org.ajoberstar:grgit:1.1.0" classpath "org.ajoberstar:grgit:1.1.0"
@ -115,12 +117,14 @@ allprojects {
} }
} }
tasks.withType(Jar) { // Includes War and Ear tasks.withType(Jar) { task ->
// Includes War and Ear
manifest { manifest {
attributes('Corda-Release-Version': corda_release_version) attributes('Corda-Release-Version': corda_release_version)
attributes('Corda-Platform-Version': corda_platform_version) attributes('Corda-Platform-Version': corda_platform_version)
attributes('Corda-Revision': corda_revision) attributes('Corda-Revision': corda_revision)
attributes('Corda-Vendor': 'Corda Enterprise Edition') attributes('Corda-Vendor': 'Corda Enterprise Edition')
attributes('Automatic-Module-Name': "net.corda.${task.project.name.replaceAll('-', '.')}")
} }
} }
@ -175,27 +179,31 @@ repositories {
} }
} }
// TODO: Corda root project currently produces a dummy cordapp when it shouldn't.
// Required for building out the fat JAR. // Required for building out the fat JAR.
dependencies { dependencies {
cordaCompile project(':node') compile project(':node')
compile "com.google.guava:guava:$guava_version" compile "com.google.guava:guava:$guava_version"
// Set to corda compile to ensure it exists now deploy nodes no longer relies on build // Set to corda compile to ensure it exists now deploy nodes no longer relies on build
cordaCompile project(path: ":node:capsule", configuration: 'runtimeArtifacts') compile project(path: ":node:capsule", configuration: 'runtimeArtifacts')
cordaCompile project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts') compile project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts')
// For the buildCordappDependenciesJar task // For the buildCordappDependenciesJar task
cordaRuntime project(':client:jfx') runtime project(':client:jfx')
cordaRuntime project(':client:mock') runtime project(':client:mock')
cordaRuntime project(':client:rpc') runtime project(':client:rpc')
cordaRuntime project(':core') runtime project(':core')
cordaRuntime project(':confidential-identities') runtime project(':confidential-identities')
cordaRuntime project(':finance') runtime project(':finance')
cordaRuntime project(':webserver') runtime project(':webserver')
testCompile project(':test-utils') testCompile project(':test-utils')
} }
jar {
// Prevent the root project from building an unwanted dummy CorDapp.
enabled = false
}
task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) { task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) {
dependsOn = subprojects.test dependsOn = subprojects.test
additionalSourceDirs = files(subprojects.sourceSets.main.allSource.srcDirs) additionalSourceDirs = files(subprojects.sourceSets.main.allSource.srcDirs)
@ -223,16 +231,14 @@ tasks.withType(Test) {
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
directory "./build/nodes" directory "./build/nodes"
networkMap "O=Controller,OU=corda,L=London,C=GB"
node { node {
name "O=Controller,OU=corda,L=London,C=GB" name "O=Controller,OU=corda,L=London,C=GB"
advertisedServices = ["corda.notary.validating"] notary = [validating : true]
p2pPort 10002 p2pPort 10002
cordapps = [] cordapps = []
} }
node { node {
name "O=Bank A,OU=corda,L=London,C=GB" name "O=Bank A,OU=corda,L=London,C=GB"
advertisedServices = []
p2pPort 10012 p2pPort 10012
rpcPort 10013 rpcPort 10013
webPort 10014 webPort 10014
@ -240,7 +246,6 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
} }
node { node {
name "O=Bank B,OU=corda,L=London,C=GB" name "O=Bank B,OU=corda,L=London,C=GB"
advertisedServices = []
p2pPort 10007 p2pPort 10007
rpcPort 10008 rpcPort 10008
webPort 10009 webPort 10009
@ -289,12 +294,20 @@ artifactory {
publish { publish {
contextUrl = 'https://ci-artifactory.corda.r3cev.com/artifactory' contextUrl = 'https://ci-artifactory.corda.r3cev.com/artifactory'
repository { repository {
repoKey = 'corda-releases' repoKey = 'enterprise-dev'
username = 'teamcity' username = 'teamcity'
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD') password = System.getenv('CORDA_ARTIFACTORY_PASSWORD')
} }
defaults { defaults {
publications('corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'cordform-common', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver', 'corda-node-driver', 'corda-confidential-identities') // Root project applies the plugin (for this block) but does not need to be published
if(project != rootProject) {
publications(project.extensions.publish.name())
}
} }
} }
} }
task generateApi(type: net.corda.plugins.GenerateApi){
baseName = "api-corda"
}

View File

@ -28,7 +28,7 @@ class CanonicalizerPlugin implements Plugin<Project> {
output.setMethod(ZipOutputStream.DEFLATED) output.setMethod(ZipOutputStream.DEFLATED)
entries.each { entries.each {
def newEntry = new ZipEntry( it.name ) def newEntry = new ZipEntry(it.name)
newEntry.setLastModifiedTime(zeroTime) newEntry.setLastModifiedTime(zeroTime)
newEntry.setCreationTime(zeroTime) newEntry.setCreationTime(zeroTime)

View File

@ -1,6 +1,7 @@
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'kotlin' apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'net.corda.plugins.api-scanner'
apply plugin: 'com.jfrog.artifactory' apply plugin: 'com.jfrog.artifactory'
dependencies { dependencies {
@ -24,6 +25,9 @@ dependencies {
jar { jar {
baseName 'corda-jackson' baseName 'corda-jackson'
manifest {
attributes 'Automatic-Module-Name': 'net.corda.client.jackson'
}
} }
publish { publish {

View File

@ -17,6 +17,7 @@ import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
@ -28,8 +29,9 @@ import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.parsePublicKeyBase58 import net.corda.core.utilities.base58ToByteArray
import net.corda.core.utilities.toBase58String import net.corda.core.utilities.base64ToByteArray
import net.corda.core.utilities.toBase64
import net.i2p.crypto.eddsa.EdDSAPublicKey import net.i2p.crypto.eddsa.EdDSAPublicKey
import java.math.BigDecimal import java.math.BigDecimal
import java.security.PublicKey import java.security.PublicKey
@ -83,13 +85,9 @@ object JacksonSupport {
addDeserializer(SecureHash::class.java, SecureHashDeserializer()) addDeserializer(SecureHash::class.java, SecureHashDeserializer())
addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer()) addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer())
// For ed25519 pubkeys // Public key types
addSerializer(EdDSAPublicKey::class.java, PublicKeySerializer) addSerializer(PublicKey::class.java, PublicKeySerializer)
addDeserializer(EdDSAPublicKey::class.java, PublicKeyDeserializer) addDeserializer(PublicKey::class.java, PublicKeyDeserializer)
// For composite keys
addSerializer(CompositeKey::class.java, CompositeKeySerializer)
addDeserializer(CompositeKey::class.java, CompositeKeyDeserializer)
// For NodeInfo // For NodeInfo
// TODO this tunnels the Kryo representation as a Base58 encoded string. Replace when RPC supports this. // TODO this tunnels the Kryo representation as a Base58 encoded string. Replace when RPC supports this.
@ -121,12 +119,14 @@ object JacksonSupport {
* match an identity known from the network map. If true, the name is matched more leniently but if the match * match an identity known from the network map. If true, the name is matched more leniently but if the match
* is ambiguous a [JsonParseException] is thrown. * is ambiguous a [JsonParseException] is thrown.
*/ */
@JvmStatic @JvmOverloads @JvmStatic
@JvmOverloads
fun createDefaultMapper(rpc: CordaRPCOps, factory: JsonFactory = JsonFactory(), fun createDefaultMapper(rpc: CordaRPCOps, factory: JsonFactory = JsonFactory(),
fuzzyIdentityMatch: Boolean = false): ObjectMapper = configureMapper(RpcObjectMapper(rpc, factory, fuzzyIdentityMatch)) fuzzyIdentityMatch: Boolean = false): ObjectMapper = configureMapper(RpcObjectMapper(rpc, factory, fuzzyIdentityMatch))
/** For testing or situations where deserialising parties is not required */ /** For testing or situations where deserialising parties is not required */
@JvmStatic @JvmOverloads @JvmStatic
@JvmOverloads
fun createNonRpcMapper(factory: JsonFactory = JsonFactory()): ObjectMapper = configureMapper(NoPartyObjectMapper(factory)) fun createNonRpcMapper(factory: JsonFactory = JsonFactory()): ObjectMapper = configureMapper(NoPartyObjectMapper(factory))
/** /**
@ -136,7 +136,8 @@ object JacksonSupport {
* match an identity known from the network map. If true, the name is matched more leniently but if the match * match an identity known from the network map. If true, the name is matched more leniently but if the match
* is ambiguous a [JsonParseException] is thrown. * is ambiguous a [JsonParseException] is thrown.
*/ */
@JvmStatic @JvmOverloads @JvmStatic
@JvmOverloads
fun createInMemoryMapper(identityService: IdentityService, factory: JsonFactory = JsonFactory(), fun createInMemoryMapper(identityService: IdentityService, factory: JsonFactory = JsonFactory(),
fuzzyIdentityMatch: Boolean = false) = configureMapper(IdentityObjectMapper(identityService, factory, fuzzyIdentityMatch)) fuzzyIdentityMatch: Boolean = false) = configureMapper(IdentityObjectMapper(identityService, factory, fuzzyIdentityMatch))
@ -158,7 +159,7 @@ object JacksonSupport {
object AnonymousPartySerializer : JsonSerializer<AnonymousParty>() { object AnonymousPartySerializer : JsonSerializer<AnonymousParty>() {
override fun serialize(obj: AnonymousParty, generator: JsonGenerator, provider: SerializerProvider) { override fun serialize(obj: AnonymousParty, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.owningKey.toBase58String()) PublicKeySerializer.serialize(obj.owningKey, generator, provider)
} }
} }
@ -168,8 +169,7 @@ object JacksonSupport {
parser.nextToken() parser.nextToken()
} }
// TODO this needs to use some industry identifier(s) instead of these keys val key = PublicKeyDeserializer.deserialize(parser, context)
val key = parsePublicKeyBase58(parser.text)
return AnonymousParty(key) return AnonymousParty(key)
} }
} }
@ -187,19 +187,24 @@ object JacksonSupport {
} }
val mapper = parser.codec as PartyObjectMapper val mapper = parser.codec as PartyObjectMapper
// TODO: We should probably have a better specified way of identifying X.500 names vs keys // The comma character is invalid in base64, and required as a separator for X.500 names. As Corda
// Base58 keys never include an equals character, while X.500 names always will, so we use that to determine // X.500 names all involve at least three attributes (organisation, locality, country), they must
// how to parse the content // include a comma. As such we can use it as a distinguisher between the two types.
return if (parser.text.contains("=")) { return if (parser.text.contains(",")) {
val principal = CordaX500Name.parse(parser.text) val principal = CordaX500Name.parse(parser.text)
mapper.wellKnownPartyFromX500Name(principal) ?: throw JsonParseException(parser, "Could not find a Party with name $principal") mapper.wellKnownPartyFromX500Name(principal) ?: throw JsonParseException(parser, "Could not find a Party with name $principal")
} else { } else {
val nameMatches = mapper.partiesFromName(parser.text) val nameMatches = mapper.partiesFromName(parser.text)
if (nameMatches.isEmpty()) { if (nameMatches.isEmpty()) {
val derBytes = try {
parser.text.base64ToByteArray()
} catch (e: AddressFormatException) {
throw JsonParseException(parser, "Could not find a matching party for '${parser.text}' and is not a base64 encoded public key: " + e.message)
}
val key = try { val key = try {
parsePublicKeyBase58(parser.text) Crypto.decodePublicKey(derBytes)
} catch (e: Exception) { } catch (e: Exception) {
throw JsonParseException(parser, "Could not find a matching party for '${parser.text}' and is not a base58 encoded public key") throw JsonParseException(parser, "Could not find a matching party for '${parser.text}' and is not a valid public key: " + e.message)
} }
mapper.partyFromKey(key) ?: throw JsonParseException(parser, "Could not find a Party with key ${key.toStringShort()}") mapper.partyFromKey(key) ?: throw JsonParseException(parser, "Could not find a Party with key ${key.toStringShort()}")
} else if (nameMatches.size == 1) { } else if (nameMatches.size == 1) {
@ -225,7 +230,7 @@ object JacksonSupport {
return try { return try {
CordaX500Name.parse(parser.text) CordaX500Name.parse(parser.text)
} catch(ex: IllegalArgumentException) { } catch (ex: IllegalArgumentException) {
throw JsonParseException(parser, "Invalid Corda X.500 name ${parser.text}: ${ex.message}", ex) throw JsonParseException(parser, "Invalid Corda X.500 name ${parser.text}: ${ex.message}", ex)
} }
} }
@ -265,47 +270,30 @@ object JacksonSupport {
parser.nextToken() parser.nextToken()
} }
try { try {
@Suppress("UNCHECKED_CAST") return uncheckedCast(SecureHash.parse(parser.text))
return SecureHash.parse(parser.text) as T
} catch (e: Exception) { } catch (e: Exception) {
throw JsonParseException(parser, "Invalid hash ${parser.text}: ${e.message}") throw JsonParseException(parser, "Invalid hash ${parser.text}: ${e.message}")
} }
} }
} }
object PublicKeySerializer : JsonSerializer<EdDSAPublicKey>() { object PublicKeySerializer : JsonSerializer<PublicKey>() {
override fun serialize(obj: EdDSAPublicKey, generator: JsonGenerator, provider: SerializerProvider) { override fun serialize(obj: PublicKey, generator: JsonGenerator, provider: SerializerProvider) {
check(obj.params == Crypto.EDDSA_ED25519_SHA512.algSpec) generator.writeString(obj.encoded.toBase64())
generator.writeString(obj.toBase58String())
} }
} }
object PublicKeyDeserializer : JsonDeserializer<EdDSAPublicKey>() { object PublicKeyDeserializer : JsonDeserializer<PublicKey>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): EdDSAPublicKey { override fun deserialize(parser: JsonParser, context: DeserializationContext): PublicKey {
return try { return try {
parsePublicKeyBase58(parser.text) as EdDSAPublicKey val derBytes = parser.text.base64ToByteArray()
Crypto.decodePublicKey(derBytes)
} catch (e: Exception) { } catch (e: Exception) {
throw JsonParseException(parser, "Invalid public key ${parser.text}: ${e.message}") throw JsonParseException(parser, "Invalid public key ${parser.text}: ${e.message}")
} }
} }
} }
object CompositeKeySerializer : JsonSerializer<CompositeKey>() {
override fun serialize(obj: CompositeKey, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.toBase58String())
}
}
object CompositeKeyDeserializer : JsonDeserializer<CompositeKey>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): CompositeKey {
return try {
parsePublicKeyBase58(parser.text) as CompositeKey
} catch (e: Exception) {
throw JsonParseException(parser, "Invalid composite key ${parser.text}: ${e.message}")
}
}
}
object AmountSerializer : JsonSerializer<Amount<*>>() { object AmountSerializer : JsonSerializer<Amount<*>>() {
override fun serialize(value: Amount<*>, gen: JsonGenerator, serializers: SerializerProvider) { override fun serialize(value: Amount<*>, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString(value.toString()) gen.writeString(value.toString())
@ -325,7 +313,7 @@ object JacksonSupport {
// Attempt parsing as a currency token. TODO: This needs thought about how to extend to other token types. // Attempt parsing as a currency token. TODO: This needs thought about how to extend to other token types.
val currency = Currency.getInstance(token) val currency = Currency.getInstance(token)
return Amount(quantity, currency) return Amount(quantity, currency)
} catch(e2: Exception) { } catch (e2: Exception) {
throw JsonParseException(parser, "Invalid amount ${parser.text}", e2) throw JsonParseException(parser, "Invalid amount ${parser.text}", e2)
} }
} }

View File

@ -6,6 +6,7 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.google.common.collect.HashMultimap import com.google.common.collect.HashMultimap
import com.google.common.collect.Multimap import com.google.common.collect.Multimap
import net.corda.client.jackson.StringToMethodCallParser.ParsedMethodCall import net.corda.client.jackson.StringToMethodCallParser.ParsedMethodCall
import net.corda.core.CordaException
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.lang.reflect.Constructor import java.lang.reflect.Constructor
import java.lang.reflect.Method import java.lang.reflect.Method
@ -99,7 +100,7 @@ open class StringToMethodCallParser<in T : Any> @JvmOverloads constructor(
val methodParamNames: Map<String, List<String>> = targetType.declaredMethods.mapNotNull { val methodParamNames: Map<String, List<String>> = targetType.declaredMethods.mapNotNull {
try { try {
it.name to paramNamesFromMethod(it) it.name to paramNamesFromMethod(it)
} catch(e: KotlinReflectionInternalError) { } catch (e: KotlinReflectionInternalError) {
// Kotlin reflection doesn't support every method that can exist on an object (in particular, reified // Kotlin reflection doesn't support every method that can exist on an object (in particular, reified
// inline methods) so we just ignore those here. // inline methods) so we just ignore those here.
null null
@ -146,7 +147,7 @@ open class StringToMethodCallParser<in T : Any> @JvmOverloads constructor(
} }
} }
open class UnparseableCallException(command: String, cause: Throwable? = null) : Exception("Could not parse as a command: $command", cause) { open class UnparseableCallException(command: String, cause: Throwable? = null) : CordaException("Could not parse as a command: $command", cause) {
class UnknownMethod(val methodName: String) : UnparseableCallException("Unknown command name: $methodName") class UnknownMethod(val methodName: String) : UnparseableCallException("Unknown command name: $methodName")
class MissingParameter(methodName: String, val paramName: String, command: String) : UnparseableCallException("Parameter $paramName missing from attempt to invoke $methodName in command: $command") class MissingParameter(methodName: String, val paramName: String, command: String) : UnparseableCallException("Parameter $paramName missing from attempt to invoke $methodName in command: $command")
class TooManyParameters(methodName: String, command: String) : UnparseableCallException("Too many parameters provided for $methodName: $command") class TooManyParameters(methodName: String, command: String) : UnparseableCallException("Too many parameters provided for $methodName: $command")
@ -174,7 +175,7 @@ open class StringToMethodCallParser<in T : Any> @JvmOverloads constructor(
try { try {
val args = parseArguments(name, paramNamesFromMethod(method).zip(method.parameterTypes), argStr) val args = parseArguments(name, paramNamesFromMethod(method).zip(method.parameterTypes), argStr)
return ParsedMethodCall(target, method, args) return ParsedMethodCall(target, method, args)
} catch(e: UnparseableCallException) { } catch (e: UnparseableCallException) {
if (index == methods.size - 1) if (index == methods.size - 1)
throw e throw e
} }
@ -197,7 +198,7 @@ open class StringToMethodCallParser<in T : Any> @JvmOverloads constructor(
val entry = tree[argName] ?: throw UnparseableCallException.MissingParameter(methodNameHint, argName, args) val entry = tree[argName] ?: throw UnparseableCallException.MissingParameter(methodNameHint, argName, args)
try { try {
om.readValue(entry.traverse(om), argType) om.readValue(entry.traverse(om), argType)
} catch(e: Exception) { } catch (e: Exception) {
throw UnparseableCallException.FailedParse(e) throw UnparseableCallException.FailedParse(e)
} }
} }
@ -211,16 +212,17 @@ open class StringToMethodCallParser<in T : Any> @JvmOverloads constructor(
} }
/** Returns a string-to-string map of commands to a string describing available parameter types. */ /** Returns a string-to-string map of commands to a string describing available parameter types. */
val availableCommands: Map<String, String> get() { val availableCommands: Map<String, String>
return methodMap.entries().map { entry -> get() {
val (name, args) = entry // TODO: Kotlin 1.1 return methodMap.entries().map { entry ->
val argStr = if (args.parameterCount == 0) "" else { val (name, args) = entry // TODO: Kotlin 1.1
val paramNames = methodParamNames[name]!! val argStr = if (args.parameterCount == 0) "" else {
val typeNames = args.parameters.map { it.type.simpleName } val paramNames = methodParamNames[name]!!
val paramTypes = paramNames.zip(typeNames) val typeNames = args.parameters.map { it.type.simpleName }
paramTypes.map { "${it.first}: ${it.second}" }.joinToString(", ") val paramTypes = paramNames.zip(typeNames)
} paramTypes.map { "${it.first}: ${it.second}" }.joinToString(", ")
Pair(name, argStr) }
}.toMap() Pair(name, argStr)
} }.toMap()
}
} }

View File

@ -14,15 +14,16 @@ import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.MINI_CORP import net.corda.testing.MINI_CORP
import net.corda.testing.TestDependencyInjectionBase import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.i2p.crypto.eddsa.EdDSAPublicKey
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import java.math.BigInteger
import java.security.PublicKey
import java.util.* import java.util.*
import kotlin.reflect.jvm.jvmName
import kotlin.test.assertEquals import kotlin.test.assertEquals
class JacksonSupportTest : TestDependencyInjectionBase() { class JacksonSupportTest : TestDependencyInjectionBase() {
companion object { companion object {
private val SEED = BigInteger.valueOf(20170922L)
val mapper = JacksonSupport.createNonRpcMapper() val mapper = JacksonSupport.createNonRpcMapper()
} }
@ -37,15 +38,34 @@ class JacksonSupportTest : TestDependencyInjectionBase() {
} }
@Test @Test
fun publicKeySerializingWorks() { fun `should serialize Composite keys`() {
val publicKey = generateKeyPair().public val expected = "\"MIHAMBUGE2mtoq+J1bjir/ONk6yd5pab0FoDgaYAMIGiAgECMIGcMDIDLQAwKjAFBgMrZXADIQAgIX1QlJRgaLlD0ttLlJF5kNqT/7P7QwCvrWc9+/248gIBATAyAy0AMCowBQYDK2VwAyEAqS0JPGlzdviBZjB9FaNY+w6cVs3/CQ2A5EimE9Lyng4CAQEwMgMtADAqMAUGAytlcAMhALq4GG0gBQZIlaKE6ucooZsuoKUbH4MtGSmA6cwj136+AgEB\""
val innerKeys = (1..3).map { i ->
Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, SEED.plus(BigInteger.valueOf(i.toLong()))).public
}
// Build a 2 of 3 composite key
val publicKey = CompositeKey.Builder().let {
innerKeys.forEach { key -> it.addKey(key, 1) }
it.build(2)
}
val serialized = mapper.writeValueAsString(publicKey) val serialized = mapper.writeValueAsString(publicKey)
val parsedKey = mapper.readValue(serialized, EdDSAPublicKey::class.java) assertEquals(expected, serialized)
val parsedKey = mapper.readValue(serialized, PublicKey::class.java)
assertEquals(publicKey, parsedKey) assertEquals(publicKey, parsedKey)
} }
private class Dummy(val notional: Amount<Currency>) private class Dummy(val notional: Amount<Currency>)
@Test
fun `should serialize EdDSA keys`() {
val expected = "\"MCowBQYDK2VwAyEACFTgLk1NOqYXAfxLoR7ctSbZcl9KMXu58Mq31Kv1Dwk=\""
val publicKey = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, SEED).public
val serialized = mapper.writeValueAsString(publicKey)
assertEquals(expected, serialized)
val parsedKey = mapper.readValue(serialized, PublicKey::class.java)
assertEquals(publicKey, parsedKey)
}
@Test @Test
fun readAmount() { fun readAmount() {
val oldJson = """ val oldJson = """
@ -72,10 +92,10 @@ class JacksonSupportTest : TestDependencyInjectionBase() {
fun writeTransaction() { fun writeTransaction() {
val attachmentRef = SecureHash.randomSHA256() val attachmentRef = SecureHash.randomSHA256()
whenever(cordappProvider.getContractAttachmentID(DummyContract.PROGRAM_ID)) whenever(cordappProvider.getContractAttachmentID(DummyContract.PROGRAM_ID))
.thenReturn(attachmentRef) .thenReturn(attachmentRef)
fun makeDummyTx(): SignedTransaction { fun makeDummyTx(): SignedTransaction {
val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1)) val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1))
.toWireTransaction(services) .toWireTransaction(services)
val signatures = TransactionSignature( val signatures = TransactionSignature(
ByteArray(1), ByteArray(1),
ALICE_PUBKEY, ALICE_PUBKEY,

View File

@ -57,8 +57,11 @@ task integrationTest(type: Test) {
jar { jar {
baseName 'corda-jfx' baseName 'corda-jfx'
manifest {
attributes 'Automatic-Module-Name': 'net.corda.client.jfx'
}
} }
publish { publish {
name jar.baseName name jar.baseName
} }

View File

@ -27,8 +27,6 @@ import net.corda.finance.flows.CashExitFlow
import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.FlowPermissions.Companion.startFlowPermission import net.corda.node.services.FlowPermissions.Companion.startFlowPermission
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.nodeapi.User import net.corda.nodeapi.User
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
@ -59,7 +57,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
startFlowPermission<CashExitFlow>()) startFlowPermission<CashExitFlow>())
) )
val aliceNodeFuture = startNode(providedName = ALICE.name, rpcUsers = listOf(cashUser)) val aliceNodeFuture = startNode(providedName = ALICE.name, rpcUsers = listOf(cashUser))
val notaryHandle = startNode(providedName = DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type))).getOrThrow() val notaryHandle = startNotaryNode(DUMMY_NOTARY.name, validating = false).getOrThrow()
val aliceNodeHandle = aliceNodeFuture.getOrThrow() val aliceNodeHandle = aliceNodeFuture.getOrThrow()
aliceNode = aliceNodeHandle.nodeInfo aliceNode = aliceNodeHandle.nodeInfo
newNode = { nodeName -> startNode(providedName = nodeName).getOrThrow().nodeInfo } newNode = { nodeName -> startNode(providedName = nodeName).getOrThrow().nodeInfo }
@ -71,7 +69,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
vaultUpdates = monitor.vaultUpdates.bufferUntilSubscribed() vaultUpdates = monitor.vaultUpdates.bufferUntilSubscribed()
networkMapUpdates = monitor.networkMap.bufferUntilSubscribed() networkMapUpdates = monitor.networkMap.bufferUntilSubscribed()
monitor.register(aliceNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password, initialiseSerialization = false) monitor.register(aliceNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password)
rpc = monitor.proxyObservable.value!! rpc = monitor.proxyObservable.value!!
notaryParty = notaryHandle.nodeInfo.legalIdentities[1] notaryParty = notaryHandle.nodeInfo.legalIdentities[1]
@ -79,7 +77,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
bobNode = bobNodeHandle.nodeInfo bobNode = bobNodeHandle.nodeInfo
val monitorBob = NodeMonitorModel() val monitorBob = NodeMonitorModel()
stateMachineUpdatesBob = monitorBob.stateMachineUpdates.bufferUntilSubscribed() stateMachineUpdatesBob = monitorBob.stateMachineUpdates.bufferUntilSubscribed()
monitorBob.register(bobNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password, initialiseSerialization = false) monitorBob.register(bobNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password)
rpcBob = monitorBob.proxyObservable.value!! rpcBob = monitorBob.proxyObservable.value!!
runTest() runTest()
} }

View File

@ -6,6 +6,7 @@ import net.corda.client.jfx.utils.fold
import net.corda.client.jfx.utils.map import net.corda.client.jfx.utils.map
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.internal.uncheckedCast
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.finance.contracts.asset.Cash import net.corda.finance.contracts.asset.Cash
import rx.Observable import rx.Observable
@ -37,10 +38,9 @@ class ContractStateModel {
companion object { companion object {
private fun Collection<StateAndRef<ContractState>>.filterCashStateAndRefs(): List<StateAndRef<Cash.State>> { private fun Collection<StateAndRef<ContractState>>.filterCashStateAndRefs(): List<StateAndRef<Cash.State>> {
return this.map { stateAndRef -> return this.map { stateAndRef ->
@Suppress("UNCHECKED_CAST")
if (stateAndRef.state.data is Cash.State) { if (stateAndRef.state.data is Cash.State) {
// Kotlin doesn't unify here for some reason // Kotlin doesn't unify here for some reason
stateAndRef as StateAndRef<Cash.State> uncheckedCast(stateAndRef)
} else { } else {
null null
} }

View File

@ -4,6 +4,7 @@ import javafx.beans.property.ObjectProperty
import javafx.beans.value.ObservableValue import javafx.beans.value.ObservableValue
import javafx.beans.value.WritableValue import javafx.beans.value.WritableValue
import javafx.collections.ObservableList import javafx.collections.ObservableList
import net.corda.core.internal.uncheckedCast
import org.reactfx.EventSink import org.reactfx.EventSink
import org.reactfx.EventStream import org.reactfx.EventStream
import rx.Observable import rx.Observable
@ -78,9 +79,7 @@ object Models {
if (model.javaClass != klass.java) { if (model.javaClass != klass.java) {
throw IllegalStateException("Model stored as ${klass.qualifiedName} has type ${model.javaClass}") throw IllegalStateException("Model stored as ${klass.qualifiedName} has type ${model.javaClass}")
} }
return uncheckedCast(model)
@Suppress("UNCHECKED_CAST")
return model as M
} }
inline fun <reified M : Any> get(origin: KClass<*>): M = get(M::class, origin) inline fun <reified M : Any> get(origin: KClass<*>): M = get(M::class, origin)

View File

@ -1,4 +1,5 @@
@file:JvmName("ModelsUtils") @file:JvmName("ModelsUtils")
package net.corda.client.jfx.model package net.corda.client.jfx.model
import javafx.beans.property.ObjectProperty import javafx.beans.property.ObjectProperty

View File

@ -38,11 +38,14 @@ class NetworkIdentityModel {
}) })
val notaries: ObservableList<Party> = networkIdentities.map { val notaries: ObservableList<Party> = networkIdentities.map {
it.legalIdentitiesAndCerts.find { it.name.commonName?.let { ServiceType.parse(it).isNotary() } ?: false } it.legalIdentitiesAndCerts.find { it.name.commonName?.let { ServiceType.parse(it).isNotary() } == true }
}.map { it?.party }.filterNotNull() }.map { it?.party }.filterNotNull()
val notaryNodes: ObservableList<NodeInfo> = notaries.map { rpcProxy.value?.nodeInfoFromParty(it) }.filterNotNull() val notaryNodes: ObservableList<NodeInfo> = notaries.map { rpcProxy.value?.nodeInfoFromParty(it) }.filterNotNull()
val parties: ObservableList<NodeInfo> = networkIdentities.filtered { it.legalIdentities.all { it !in notaries } } val parties: ObservableList<NodeInfo> = networkIdentities
.filtered { it.legalIdentities.all { it !in notaries } }
// TODO: REMOVE THIS HACK WHEN NETWORK MAP REDESIGN WORK IS COMPLETED.
.filtered { it.legalIdentities.all { it.name.organisation != "Network Map Service" } }
val myIdentity = rpcProxy.map { it?.nodeInfo()?.legalIdentitiesAndCerts?.first()?.party } val myIdentity = rpcProxy.map { it?.nodeInfo()?.legalIdentitiesAndCerts?.first()?.party }
fun partyFromPublicKey(publicKey: PublicKey): ObservableValue<NodeInfo?> = identityCache[publicKey] fun partyFromPublicKey(publicKey: PublicKey): ObservableValue<NodeInfo?> = identityCache[publicKey]

View File

@ -55,13 +55,12 @@ class NodeMonitorModel {
* 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: NetworkHostAndPort, username: String, password: String, initialiseSerialization: Boolean = true) { fun register(nodeHostAndPort: NetworkHostAndPort, username: String, password: String) {
val client = CordaRPCClient( val client = CordaRPCClient(
hostAndPort = nodeHostAndPort, nodeHostAndPort,
configuration = CordaRPCClientConfiguration.default.copy( CordaRPCClientConfiguration.DEFAULT.copy(
connectionMaxRetryInterval = 10.seconds connectionMaxRetryInterval = 10.seconds
), )
initialiseSerialization = initialiseSerialization
) )
val connection = client.start(username, password) val connection = client.start(username, password)
val proxy = connection.proxy val proxy = connection.proxy

View File

@ -253,14 +253,15 @@ class ConcatenatedList<A>(sourceList: ObservableList<ObservableList<A>>) : Trans
} }
} }
override val size: Int get() { override val size: Int
recalculateOffsets() get() {
if (nestedIndexOffsets.size > 0) { recalculateOffsets()
return nestedIndexOffsets.last() if (nestedIndexOffsets.size > 0) {
} else { return nestedIndexOffsets.last()
return 0 } else {
return 0
}
} }
}
override fun getSourceIndex(index: Int): Int { override fun getSourceIndex(index: Int): Int {
throw UnsupportedOperationException("Source index not supported in concatenation") throw UnsupportedOperationException("Source index not supported in concatenation")

View File

@ -1,4 +1,5 @@
@file:JvmName("ObservableFold") @file:JvmName("ObservableFold")
package net.corda.client.jfx.utils package net.corda.client.jfx.utils
import javafx.application.Platform import javafx.application.Platform

View File

@ -1,4 +1,5 @@
@file:JvmName("ObservableUtilities") @file:JvmName("ObservableUtilities")
package net.corda.client.jfx.utils package net.corda.client.jfx.utils
import javafx.application.Platform import javafx.application.Platform
@ -14,6 +15,7 @@ import javafx.collections.ObservableMap
import javafx.collections.transformation.FilteredList import javafx.collections.transformation.FilteredList
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.internal.uncheckedCast
import net.corda.core.messaging.DataFeed import net.corda.core.messaging.DataFeed
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import org.fxmisc.easybind.EasyBind import org.fxmisc.easybind.EasyBind
@ -92,8 +94,7 @@ fun <A, B> ObservableValue<out A>.bind(function: (A) -> ObservableValue<B>): Obs
* propagate variance constraints and type inference fails. * propagate variance constraints and type inference fails.
*/ */
fun <A, B> ObservableValue<out A>.bindOut(function: (A) -> ObservableValue<out B>): ObservableValue<out B> = fun <A, B> ObservableValue<out A>.bindOut(function: (A) -> ObservableValue<out B>): ObservableValue<out B> =
@Suppress("UNCHECKED_CAST") EasyBind.monadic(this).flatMap(uncheckedCast(function))
EasyBind.monadic(this).flatMap(function as (A) -> ObservableValue<B>)
/** /**
* enum class FilterCriterion { HEIGHT, NAME } * enum class FilterCriterion { HEIGHT, NAME }
@ -105,8 +106,7 @@ fun <A, B> ObservableValue<out A>.bindOut(function: (A) -> ObservableValue<out B
*/ */
fun <A> ObservableList<out A>.filter(predicate: ObservableValue<(A) -> Boolean>): ObservableList<A> { fun <A> ObservableList<out A>.filter(predicate: ObservableValue<(A) -> Boolean>): ObservableList<A> {
// We cast here to enforce variance, FilteredList should be covariant // We cast here to enforce variance, FilteredList should be covariant
@Suppress("UNCHECKED_CAST") return FilteredList<A>(uncheckedCast(this)).apply {
return FilteredList<A>(this as ObservableList<A>).apply {
predicateProperty().bind(predicate.map { predicateFunction -> predicateProperty().bind(predicate.map { predicateFunction ->
Predicate<A> { predicateFunction(it) } Predicate<A> { predicateFunction(it) }
}) })
@ -120,13 +120,11 @@ fun <A> ObservableList<out A>.filter(predicate: ObservableValue<(A) -> Boolean>)
*/ */
fun <A> ObservableList<out A?>.filterNotNull(): ObservableList<A> { fun <A> ObservableList<out A?>.filterNotNull(): ObservableList<A> {
//TODO This is a tactical work round for an issue with SAM conversion (https://youtrack.jetbrains.com/issue/ALL-1552) so that the M10 explorer works. //TODO This is a tactical work round for an issue with SAM conversion (https://youtrack.jetbrains.com/issue/ALL-1552) so that the M10 explorer works.
@Suppress("UNCHECKED_CAST") return uncheckedCast(uncheckedCast<Any, ObservableList<A?>>(this).filtered(object : Predicate<A?> {
return (this as ObservableList<A?>).filtered(object : Predicate<A?> {
override fun test(t: A?): Boolean { override fun test(t: A?): Boolean {
return t != null return t != null
} }
}) as ObservableList<A> }))
} }
/** /**

View File

@ -50,18 +50,19 @@ open class ReadOnlyBackedObservableMapBase<K, A, B> : ObservableMap<K, A> {
override fun isEmpty() = backingMap.isEmpty() override fun isEmpty() = backingMap.isEmpty()
override val entries: MutableSet<MutableMap.MutableEntry<K, A>> get() = backingMap.entries.fold(mutableSetOf()) { set, entry -> override val entries: MutableSet<MutableMap.MutableEntry<K, A>>
set.add(object : MutableMap.MutableEntry<K, A> { get() = backingMap.entries.fold(mutableSetOf()) { set, entry ->
override var value: A = entry.value.first set.add(object : MutableMap.MutableEntry<K, A> {
override val key = entry.key override var value: A = entry.value.first
override fun setValue(newValue: A): A { override val key = entry.key
val old = value override fun setValue(newValue: A): A {
value = newValue val old = value
return old value = newValue
} return old
}) }
set })
} set
}
override val keys: MutableSet<K> get() = backingMap.keys override val keys: MutableSet<K> get() = backingMap.keys
override val values: MutableCollection<A> get() = ArrayList(backingMap.values.map { it.first }) override val values: MutableCollection<A> get() = ArrayList(backingMap.values.map { it.first })

View File

@ -21,8 +21,11 @@ dependencies {
jar { jar {
baseName 'corda-mock' baseName 'corda-mock'
manifest {
attributes 'Automatic-Module-Name': 'net.corda.client.mock'
}
} }
publish { publish {
name jar.baseName name jar.baseName
} }

View File

@ -50,7 +50,7 @@ open class EventGenerator(val parties: List<Party>, val currencies: List<Currenc
* [Generator]s for incoming/outgoing events of starting different cash flows. It invokes flows that throw exceptions * [Generator]s for incoming/outgoing events of starting different cash flows. It invokes flows that throw exceptions
* for use in explorer flow triage. Exceptions are of kind spending/exiting too much cash. * for use in explorer flow triage. Exceptions are of kind spending/exiting too much cash.
*/ */
class ErrorFlowsEventGenerator(parties: List<Party>, currencies: List<Currency>, notary: Party): EventGenerator(parties, currencies, notary) { class ErrorFlowsEventGenerator(parties: List<Party>, currencies: List<Currency>, notary: Party) : EventGenerator(parties, currencies, notary) {
enum class IssuerEvents { enum class IssuerEvents {
NORMAL_EXIT, NORMAL_EXIT,
EXIT_ERROR EXIT_ERROR
@ -62,7 +62,7 @@ class ErrorFlowsEventGenerator(parties: List<Party>, currencies: List<Currency>,
when (errorType) { when (errorType) {
IssuerEvents.NORMAL_EXIT -> { IssuerEvents.NORMAL_EXIT -> {
println("Normal exit") println("Normal exit")
if (currencyMap[ccy]!! <= amount) addToMap(ccy, -amount) if (currencyMap[ccy]!! <= amount) addToMap(ccy, -amount)
ExitRequest(Amount(amount, ccy), issueRef) // It may fail at the beginning, but we don't care. ExitRequest(Amount(amount, ccy), issueRef) // It may fail at the beginning, but we don't care.
} }
IssuerEvents.EXIT_ERROR -> { IssuerEvents.EXIT_ERROR -> {

View File

@ -1,6 +1,7 @@
package net.corda.client.mock package net.corda.client.mock
import net.corda.client.mock.Generator.Companion.choice import net.corda.client.mock.Generator.Companion.choice
import net.corda.core.internal.uncheckedCast
import net.corda.core.utilities.Try import net.corda.core.utilities.Try
import java.util.* import java.util.*
@ -115,14 +116,13 @@ class Generator<out A>(val generate: (SplittableRandom) -> Try<A>) {
fun <A> frequency(vararg generators: Pair<Double, Generator<A>>) = frequency(generators.toList()) fun <A> frequency(vararg generators: Pair<Double, Generator<A>>) = frequency(generators.toList())
fun <A> sequence(generators: List<Generator<A>>) = Generator { fun <A> sequence(generators: List<Generator<A>>) = Generator<List<A>> {
val result = mutableListOf<A>() val result = mutableListOf<A>()
for (generator in generators) { for (generator in generators) {
val element = generator.generate(it) val element = generator.generate(it)
@Suppress("UNCHECKED_CAST")
when (element) { when (element) {
is Try.Success -> result.add(element.value) is Try.Success -> result.add(element.value)
is Try.Failure -> return@Generator element as Try<List<A>> is Try.Failure -> return@Generator uncheckedCast(element)
} }
} }
Try.Success(result) Try.Success(result)
@ -175,7 +175,7 @@ class Generator<out A>(val generate: (SplittableRandom) -> Try<A>) {
} }
fun <A> replicatePoisson(meanSize: Double, generator: Generator<A>, atLeastOne: Boolean = false) = Generator { fun <A> replicatePoisson(meanSize: Double, generator: Generator<A>, atLeastOne: Boolean = false) = Generator<List<A>> {
val chance = (meanSize - 1) / meanSize val chance = (meanSize - 1) / meanSize
val result = mutableListOf<A>() val result = mutableListOf<A>()
var finish = false var finish = false
@ -191,8 +191,7 @@ class Generator<out A>(val generate: (SplittableRandom) -> Try<A>) {
} }
} }
if (res is Try.Failure) { if (res is Try.Failure) {
@Suppress("UNCHECKED_CAST") return@Generator uncheckedCast(res)
return@Generator res as Try<List<A>>
} }
} }
Try.Success(result) Try.Success(result)

View File

@ -1,4 +1,5 @@
@file:JvmName("Generators") @file:JvmName("Generators")
package net.corda.client.mock package net.corda.client.mock
import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount

View File

@ -1,6 +1,7 @@
apply plugin: 'kotlin' apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.quasar-utils' apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'net.corda.plugins.api-scanner'
apply plugin: 'com.jfrog.artifactory' apply plugin: 'com.jfrog.artifactory'
description 'Corda client RPC modules' description 'Corda client RPC modules'
@ -89,6 +90,9 @@ task smokeTest(type: Test) {
jar { jar {
baseName 'corda-rpc' baseName 'corda-rpc'
manifest {
attributes 'Automatic-Module-Name': 'net.corda.client.rpc'
}
} }
publish { publish {

View File

@ -1,6 +1,5 @@
package net.corda.client.rpc; package net.corda.client.rpc;
import net.corda.client.rpc.internal.RPCClient;
import net.corda.core.concurrent.CordaFuture; import net.corda.core.concurrent.CordaFuture;
import net.corda.core.contracts.Amount; import net.corda.core.contracts.Amount;
import net.corda.core.messaging.CordaRPCOps; import net.corda.core.messaging.CordaRPCOps;
@ -9,11 +8,9 @@ import net.corda.core.utilities.OpaqueBytes;
import net.corda.finance.flows.AbstractCashFlow; import net.corda.finance.flows.AbstractCashFlow;
import net.corda.finance.flows.CashIssueFlow; import net.corda.finance.flows.CashIssueFlow;
import net.corda.finance.flows.CashPaymentFlow; import net.corda.finance.flows.CashPaymentFlow;
import net.corda.finance.schemas.*; import net.corda.finance.schemas.CashSchemaV1;
import net.corda.node.internal.Node; import net.corda.node.internal.Node;
import net.corda.node.internal.StartedNode; import net.corda.node.internal.StartedNode;
import net.corda.node.services.transactions.ValidatingNotaryService;
import net.corda.nodeapi.internal.ServiceInfo;
import net.corda.nodeapi.User; import net.corda.nodeapi.User;
import net.corda.testing.CoreTestUtils; import net.corda.testing.CoreTestUtils;
import net.corda.testing.node.NodeBasedTest; import net.corda.testing.node.NodeBasedTest;
@ -25,24 +22,26 @@ import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import static java.util.Collections.*; import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static kotlin.test.AssertionsKt.assertEquals; import static kotlin.test.AssertionsKt.assertEquals;
import static net.corda.client.rpc.CordaRPCClientConfiguration.getDefault;
import static net.corda.finance.Currencies.DOLLARS; import static net.corda.finance.Currencies.DOLLARS;
import static net.corda.finance.contracts.GetBalances.getCashBalance; import static net.corda.finance.contracts.GetBalances.getCashBalance;
import static net.corda.node.services.FlowPermissions.startFlowPermission; import static net.corda.node.services.FlowPermissions.startFlowPermission;
import static net.corda.testing.CoreTestUtils.*;
import static net.corda.testing.TestConstants.getALICE; import static net.corda.testing.TestConstants.getALICE;
public class CordaRPCJavaClientTest extends NodeBasedTest { public class CordaRPCJavaClientTest extends NodeBasedTest {
public CordaRPCJavaClientTest() {
super(Arrays.asList("net.corda.finance.contracts", CashSchemaV1.class.getPackage().getName()));
}
private List<String> perms = Arrays.asList(startFlowPermission(CashPaymentFlow.class), startFlowPermission(CashIssueFlow.class)); private List<String> perms = Arrays.asList(startFlowPermission(CashPaymentFlow.class), startFlowPermission(CashIssueFlow.class));
private Set<String> permSet = new HashSet<>(perms); private Set<String> permSet = new HashSet<>(perms);
private User rpcUser = new User("user1", "test", permSet); private User rpcUser = new User("user1", "test", permSet);
private StartedNode<Node> node; private StartedNode<Node> node;
private CordaRPCClient client; private CordaRPCClient client;
private RPCClient.RPCConnection<CordaRPCOps> connection = null; private RPCConnection<CordaRPCOps> connection = null;
private CordaRPCOps rpcProxy; private CordaRPCOps rpcProxy;
private void login(String username, String password) { private void login(String username, String password) {
@ -52,18 +51,14 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
@Before @Before
public void setUp() throws ExecutionException, InterruptedException { public void setUp() throws ExecutionException, InterruptedException {
setCordappPackages("net.corda.finance.contracts"); CordaFuture<StartedNode<Node>> nodeFuture = startNotaryNode(getALICE().getName(), singletonList(rpcUser), true);
Set<ServiceInfo> services = new HashSet<>(singletonList(new ServiceInfo(ValidatingNotaryService.Companion.getType(), null)));
CordaFuture<StartedNode<Node>> nodeFuture = startNode(getALICE().getName(), 1, services, singletonList(rpcUser), emptyMap());
node = nodeFuture.get(); node = nodeFuture.get();
node.getInternals().registerCustomSchemas(Collections.singleton(CashSchemaV1.INSTANCE)); client = new CordaRPCClient(requireNonNull(node.getInternals().getConfiguration().getRpcAddress()));
client = new CordaRPCClient(requireNonNull(node.getInternals().getConfiguration().getRpcAddress()), getDefault(), false);
} }
@After @After
public void done() throws IOException { public void done() throws IOException {
connection.close(); connection.close();
unsetCordappPackages();
} }
@Test @Test

View File

@ -0,0 +1,90 @@
package net.corda.client.rpc
import co.paralleluniverse.fibers.Suspendable
import com.esotericsoftware.kryo.KryoException
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.messaging.startFlow
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.unwrap
import net.corda.node.internal.Node
import net.corda.node.internal.StartedNode
import net.corda.nodeapi.User
import net.corda.testing.*
import net.corda.testing.node.NodeBasedTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
@CordaSerializable
data class Packet(val x: () -> Long)
class BlacklistKotlinClosureTest : NodeBasedTest(listOf("net.corda.client.rpc")) {
companion object {
@Suppress("UNUSED") val logger = loggerFor<BlacklistKotlinClosureTest>()
const val EVIL: Long = 666
}
@StartableByRPC
@InitiatingFlow
class FlowC(private val remoteParty: Party, private val data: Packet) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val session = initiateFlow(remoteParty)
val x = session.sendAndReceive<Packet>(data).unwrap { x -> x }
logger.info("FlowC: ${x.x()}")
}
}
@InitiatedBy(FlowC::class)
class RemoteFlowC(private val session: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val packet = session.receive<Packet>().unwrap { x -> x }
logger.info("RemoteFlowC: ${packet.x() + 1}")
session.send(Packet({ packet.x() + 1 }))
}
}
@JvmField
@Rule
val expectedEx: ExpectedException = ExpectedException.none()
private val rpcUser = User("user1", "test", permissions = setOf("ALL"))
private lateinit var aliceNode: StartedNode<Node>
private lateinit var bobNode: StartedNode<Node>
private lateinit var aliceClient: CordaRPCClient
private var connection: CordaRPCConnection? = null
private fun login(username: String, password: String) {
connection = aliceClient.start(username, password)
}
@Before
fun setUp() {
aliceNode = startNode(ALICE.name, rpcUsers = listOf(rpcUser)).getOrThrow()
bobNode = startNode(BOB.name, rpcUsers = listOf(rpcUser)).getOrThrow()
bobNode.registerInitiatedFlow(RemoteFlowC::class.java)
aliceClient = CordaRPCClient(aliceNode.internals.configuration.rpcAddress!!)
}
@After
fun done() {
connection?.close()
bobNode.internals.stop()
aliceNode.internals.stop()
}
@Test
fun `closure sent via RPC`() {
login(rpcUser.username, rpcUser.password)
val proxy = connection!!.proxy
expectedEx.expect(KryoException::class.java)
expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization")
proxy.startFlow(::FlowC, bobNode.info.chooseIdentity(), Packet{ EVIL }).returnValue.getOrThrow()
}
}

View File

@ -2,6 +2,7 @@ package net.corda.client.rpc
import net.corda.core.crypto.random63BitValue import net.corda.core.crypto.random63BitValue
import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowInitiator
import net.corda.core.internal.packageName
import net.corda.core.messaging.FlowProgressHandle import net.corda.core.messaging.FlowProgressHandle
import net.corda.core.messaging.StateMachineUpdate import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
@ -19,14 +20,10 @@ import net.corda.finance.schemas.CashSchemaV1
import net.corda.node.internal.Node import net.corda.node.internal.Node
import net.corda.node.internal.StartedNode import net.corda.node.internal.StartedNode
import net.corda.node.services.FlowPermissions.Companion.startFlowPermission import net.corda.node.services.FlowPermissions.Companion.startFlowPermission
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.node.services.transactions.ValidatingNotaryService
import net.corda.nodeapi.User import net.corda.nodeapi.User
import net.corda.testing.ALICE import net.corda.testing.ALICE
import net.corda.testing.chooseIdentity import net.corda.testing.chooseIdentity
import net.corda.testing.node.NodeBasedTest import net.corda.testing.node.NodeBasedTest
import net.corda.testing.setCordappPackages
import net.corda.testing.unsetCordappPackages
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.After
@ -36,7 +33,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
class CordaRPCClientTest : NodeBasedTest() { class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", CashSchemaV1::class.packageName)) {
private val rpcUser = User("user1", "test", permissions = setOf( private val rpcUser = User("user1", "test", permissions = setOf(
startFlowPermission<CashIssueFlow>(), startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>() startFlowPermission<CashPaymentFlow>()
@ -51,16 +48,13 @@ class CordaRPCClientTest : NodeBasedTest() {
@Before @Before
fun setUp() { fun setUp() {
setCordappPackages("net.corda.finance.contracts") node = startNotaryNode(ALICE.name, rpcUsers = listOf(rpcUser)).getOrThrow()
node = startNode(ALICE.name, rpcUsers = listOf(rpcUser), advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type))).getOrThrow() client = CordaRPCClient(node.internals.configuration.rpcAddress!!)
node.internals.registerCustomSchemas(setOf(CashSchemaV1))
client = CordaRPCClient(node.internals.configuration.rpcAddress!!, initialiseSerialization = false)
} }
@After @After
fun done() { fun done() {
connection?.close() connection?.close()
unsetCordappPackages()
} }
@Test @Test

View File

@ -7,6 +7,7 @@ import net.corda.core.internal.concurrent.fork
import net.corda.core.internal.concurrent.transpose import net.corda.core.internal.concurrent.transpose
import net.corda.core.messaging.RPCOps import net.corda.core.messaging.RPCOps
import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.serialize
import net.corda.core.utilities.* import net.corda.core.utilities.*
import net.corda.node.services.messaging.RPCServerConfiguration import net.corda.node.services.messaging.RPCServerConfiguration
import net.corda.nodeapi.RPCApi import net.corda.nodeapi.RPCApi
@ -75,10 +76,12 @@ class RPCStabilityTests {
rpcDriver { rpcDriver {
Try.on { startRpcClient<RPCOps>(NetworkHostAndPort("localhost", 9999)).get() } Try.on { startRpcClient<RPCOps>(NetworkHostAndPort("localhost", 9999)).get() }
val server = startRpcServer<RPCOps>(ops = DummyOps) val server = startRpcServer<RPCOps>(ops = DummyOps)
Try.on { startRpcClient<RPCOps>( Try.on {
server.get().broker.hostAndPort!!, startRpcClient<RPCOps>(
configuration = RPCClientConfiguration.default.copy(minimumServerProtocolVersion = 1) server.get().broker.hostAndPort!!,
).get() } configuration = RPCClientConfiguration.default.copy(minimumServerProtocolVersion = 1)
).get()
}
} }
} }
repeat(5) { repeat(5) {
@ -172,7 +175,7 @@ class RPCStabilityTests {
} }
} }
interface LeakObservableOps: RPCOps { interface LeakObservableOps : RPCOps {
fun leakObservable(): Observable<Nothing> fun leakObservable(): Observable<Nothing>
} }
@ -248,6 +251,7 @@ class RPCStabilityTests {
val trackSubscriberCountObservable = UnicastSubject.create<Unit>().share(). val trackSubscriberCountObservable = UnicastSubject.create<Unit>().share().
doOnSubscribe { subscriberCount.incrementAndGet() }. doOnSubscribe { subscriberCount.incrementAndGet() }.
doOnUnsubscribe { subscriberCount.decrementAndGet() } doOnUnsubscribe { subscriberCount.decrementAndGet() }
override fun subscribe(): Observable<Unit> { override fun subscribe(): Observable<Unit> {
return trackSubscriberCountObservable return trackSubscriberCountObservable
} }
@ -260,7 +264,7 @@ class RPCStabilityTests {
).get() ).get()
val numberOfClients = 4 val numberOfClients = 4
val clients = (1 .. numberOfClients).map { val clients = (1..numberOfClients).map {
startRandomRpcClient<TrackSubscriberOps>(server.broker.hostAndPort!!) startRandomRpcClient<TrackSubscriberOps>(server.broker.hostAndPort!!)
}.transpose().get() }.transpose().get()
@ -271,7 +275,7 @@ class RPCStabilityTests {
clients[0].destroyForcibly() clients[0].destroyForcibly()
pollUntilClientNumber(server, numberOfClients - 1) pollUntilClientNumber(server, numberOfClients - 1)
// Kill the rest // Kill the rest
(1 .. numberOfClients - 1).forEach { (1..numberOfClients - 1).forEach {
clients[it].destroyForcibly() clients[it].destroyForcibly()
} }
pollUntilClientNumber(server, 0) pollUntilClientNumber(server, 0)
@ -283,6 +287,7 @@ class RPCStabilityTests {
interface SlowConsumerRPCOps : RPCOps { interface SlowConsumerRPCOps : RPCOps {
fun streamAtInterval(interval: Duration, size: Int): Observable<ByteArray> fun streamAtInterval(interval: Duration, size: Int): Observable<ByteArray>
} }
class SlowConsumerRPCOpsImpl : SlowConsumerRPCOps { class SlowConsumerRPCOpsImpl : SlowConsumerRPCOps {
override val protocolVersion = 0 override val protocolVersion = 0
@ -291,6 +296,7 @@ class RPCStabilityTests {
return Observable.interval(interval.toMillis(), TimeUnit.MILLISECONDS).map { chunk } return Observable.interval(interval.toMillis(), TimeUnit.MILLISECONDS).map { chunk }
} }
} }
@Test @Test
fun `slow consumers are kicked`() { fun `slow consumers are kicked`() {
rpcDriver { rpcDriver {
@ -315,9 +321,9 @@ class RPCStabilityTests {
clientAddress = SimpleString(myQueue), clientAddress = SimpleString(myQueue),
id = RPCApi.RpcRequestId(random63BitValue()), id = RPCApi.RpcRequestId(random63BitValue()),
methodName = SlowConsumerRPCOps::streamAtInterval.name, methodName = SlowConsumerRPCOps::streamAtInterval.name,
arguments = listOf(10.millis, 123456) serialisedArguments = listOf(10.millis, 123456).serialize(context = SerializationDefaults.RPC_SERVER_CONTEXT).bytes
) )
request.writeToClientMessage(SerializationDefaults.RPC_SERVER_CONTEXT, message) request.writeToClientMessage(message)
producer.send(message) producer.send(message)
session.commit() session.commit()

View File

@ -10,42 +10,68 @@ import net.corda.nodeapi.ConnectionDirection
import net.corda.nodeapi.internal.serialization.KRYO_RPC_CLIENT_CONTEXT import net.corda.nodeapi.internal.serialization.KRYO_RPC_CLIENT_CONTEXT
import java.time.Duration import java.time.Duration
/** @see RPCClient.RPCConnection */ /**
class CordaRPCConnection internal constructor( * This class is essentially just a wrapper for an RPCConnection<CordaRPCOps> and can be treated identically.
connection: RPCClient.RPCConnection<CordaRPCOps> *
) : RPCClient.RPCConnection<CordaRPCOps> by connection * @see RPCConnection
*/
class CordaRPCConnection internal constructor(connection: RPCConnection<CordaRPCOps>) : RPCConnection<CordaRPCOps> by connection
/** @see RPCClientConfiguration */ /**
data class CordaRPCClientConfiguration( * Can be used to configure the RPC client connection.
val connectionMaxRetryInterval: Duration *
) { * @property connectionMaxRetryInterval How much time to wait between connection retries if the server goes down. This
* time will be reached via exponential backoff.
*/
data class CordaRPCClientConfiguration(val connectionMaxRetryInterval: Duration) {
internal fun toRpcClientConfiguration(): RPCClientConfiguration { internal fun toRpcClientConfiguration(): RPCClientConfiguration {
return RPCClientConfiguration.default.copy( return RPCClientConfiguration.default.copy(
connectionMaxRetryInterval = connectionMaxRetryInterval connectionMaxRetryInterval = connectionMaxRetryInterval
) )
} }
companion object { companion object {
@JvmStatic /**
val default = CordaRPCClientConfiguration( * Returns the default configuration we recommend you use.
connectionMaxRetryInterval = RPCClientConfiguration.default.connectionMaxRetryInterval */
) @JvmField
val DEFAULT = CordaRPCClientConfiguration(connectionMaxRetryInterval = RPCClientConfiguration.default.connectionMaxRetryInterval)
} }
} }
/** @see RPCClient */ /**
//TODO Add SSL support * An RPC client connects to the specified server and allows you to make calls to the server that perform various
class CordaRPCClient( * useful tasks. Please see the Client RPC section of docs.corda.net to learn more about how this API works. A brief
* description is provided here.
*
* Calling [start] returns an [RPCConnection] containing a proxy that lets you invoke RPCs on the server. Calls on
* it block, and if the server throws an exception then it will be rethrown on the client. Proxies are thread safe and
* may be used to invoke multiple RPCs in parallel.
*
* RPC sends and receives are logged on the net.corda.rpc logger.
*
* The [CordaRPCOps] defines what client RPCs are available. If an RPC returns an [rx.Observable] anywhere in the object
* graph returned then the server-side observable is transparently forwarded to the client side here.
* *You are expected to use it*. The server will begin sending messages immediately that will be buffered on the
* client, you are expected to drain by subscribing to the returned observer. You can opt-out of this by simply
* calling the [net.corda.client.rpc.notUsed] method on it.
*
* You don't have to explicitly close the observable if you actually subscribe to it: it will close itself and free up
* the server-side resources either when the client or JVM itself is shutdown, or when there are no more subscribers to
* it. Once all the subscribers to a returned observable are unsubscribed or the observable completes successfully or
* with an error, the observable is closed and you can't then re-subscribe again: you'll have to re-request a fresh
* observable with another RPC.
*
* @param hostAndPort The network address to connect to.
* @param configuration An optional configuration used to tweak client behaviour.
*/
class CordaRPCClient @JvmOverloads constructor(
hostAndPort: NetworkHostAndPort, hostAndPort: NetworkHostAndPort,
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default, configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT
initialiseSerialization: Boolean = true
) { ) {
init { init {
// Init serialization. It's plausible there are multiple clients in a single JVM, so be tolerant of
// others having registered first.
// TODO: allow clients to have serialization factory etc injected and align with RPC protocol version? // TODO: allow clients to have serialization factory etc injected and align with RPC protocol version?
if (initialiseSerialization) { KryoClientSerializationScheme.initialiseSerialization()
KryoClientSerializationScheme.initialiseSerialization()
}
} }
private val rpcClient = RPCClient<CordaRPCOps>( private val rpcClient = RPCClient<CordaRPCOps>(
@ -54,10 +80,24 @@ class CordaRPCClient(
KRYO_RPC_CLIENT_CONTEXT KRYO_RPC_CLIENT_CONTEXT
) )
/**
* Logs in to the target server and returns an active connection. The returned connection is a [java.io.Closeable]
* and can be used with a try-with-resources statement. If you don't use that, you should use the
* [RPCConnection.notifyServerAndClose] or [RPCConnection.forceClose] methods to dispose of the connection object
* when done.
*
* @param username The username to authenticate with.
* @param password The password to authenticate with.
* @throws RPCException if the server version is too low or if the server isn't reachable within a reasonable timeout.
*/
fun start(username: String, password: String): CordaRPCConnection { fun start(username: String, password: String): CordaRPCConnection {
return CordaRPCConnection(rpcClient.start(CordaRPCOps::class.java, username, password)) return CordaRPCConnection(rpcClient.start(CordaRPCOps::class.java, username, password))
} }
/**
* A helper for Kotlin users that simply closes the connection after the block has executed. Be careful not to
* over-use this, as setting up and closing connections takes time.
*/
inline fun <A> use(username: String, password: String, block: (CordaRPCConnection) -> A): A { inline fun <A> use(username: String, password: String, block: (CordaRPCConnection) -> A): A {
return start(username, password).use(block) return start(username, password).use(block)
} }

View File

@ -1,10 +1,10 @@
package net.corda.client.rpc package net.corda.client.rpc
import net.corda.core.CordaRuntimeException
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
/** /**
* Thrown to indicate that the calling user does not have permission for something they have requested (for example * Thrown to indicate that the calling user does not have permission for something they have requested (for example
* calling a method). * calling a method).
*/ */
@CordaSerializable class PermissionException(msg: String) : CordaRuntimeException(msg)
class PermissionException(msg: String) : RuntimeException(msg)

View File

@ -0,0 +1,38 @@
package net.corda.client.rpc
import net.corda.core.messaging.RPCOps
import java.io.Closeable
/**
* Holds a [proxy] object implementing [I] that forwards requests to the RPC server. The server version can be queried
* via this interface.
*
* [Closeable.close] may be used to shut down the connection and release associated resources. It is an
* alias for [notifyServerAndClose].
*/
interface RPCConnection<out I : RPCOps> : Closeable {
/**
* Holds a synthetic class that automatically forwards method calls to the server, and returns the response.
*/
val proxy: I
/** The RPC protocol version reported by the server. */
val serverProtocolVersion: Int
/**
* Closes this client gracefully by sending a notification to the server, so it can immediately clean up resources.
* If the server is not available this method may block for a short period until it's clear the server is not
* coming back.
*/
fun notifyServerAndClose()
/**
* Closes this client without notifying the server.
*
* The server will eventually clear out the RPC message queue and disconnect subscribed observers,
* but this may take longer than desired, so to conserve resources you should normally use [notifyServerAndClose].
* This method is helpful when the node may be shutting down or have already shut down and you don't want to
* block waiting for it to come back, which typically happens in integration tests and demos rather than production.
*/
fun forceClose()
}

View File

@ -5,6 +5,7 @@ import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SerializationDefaults
import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.ByteSequence
import net.corda.nodeapi.internal.serialization.* import net.corda.nodeapi.internal.serialization.*
import java.util.concurrent.atomic.AtomicBoolean
class KryoClientSerializationScheme : AbstractKryoSerializationScheme() { class KryoClientSerializationScheme : AbstractKryoSerializationScheme() {
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
@ -23,7 +24,9 @@ class KryoClientSerializationScheme : AbstractKryoSerializationScheme() {
override fun rpcServerKryoPool(context: SerializationContext): KryoPool = throw UnsupportedOperationException() override fun rpcServerKryoPool(context: SerializationContext): KryoPool = throw UnsupportedOperationException()
companion object { companion object {
val isInitialised = AtomicBoolean(false)
fun initialiseSerialization() { fun initialiseSerialization() {
if (!isInitialised.compareAndSet(false, true)) return
try { try {
SerializationDefaults.SERIALIZATION_FACTORY = SerializationFactoryImpl().apply { SerializationDefaults.SERIALIZATION_FACTORY = SerializationFactoryImpl().apply {
registerScheme(KryoClientSerializationScheme()) registerScheme(KryoClientSerializationScheme())
@ -31,10 +34,14 @@ class KryoClientSerializationScheme : AbstractKryoSerializationScheme() {
} }
SerializationDefaults.P2P_CONTEXT = KRYO_P2P_CONTEXT SerializationDefaults.P2P_CONTEXT = KRYO_P2P_CONTEXT
SerializationDefaults.RPC_CLIENT_CONTEXT = KRYO_RPC_CLIENT_CONTEXT SerializationDefaults.RPC_CLIENT_CONTEXT = KRYO_RPC_CLIENT_CONTEXT
} catch(e: IllegalStateException) { } catch (e: IllegalStateException) {
// Check that it's registered as we expect // Check that it's registered as we expect
check(SerializationDefaults.SERIALIZATION_FACTORY is SerializationFactoryImpl) { "RPC client encountered conflicting configuration of serialization subsystem." } val factory = SerializationDefaults.SERIALIZATION_FACTORY
check((SerializationDefaults.SERIALIZATION_FACTORY as SerializationFactoryImpl).alreadyRegisteredSchemes.any { it is KryoClientSerializationScheme }) { "RPC client encountered conflicting configuration of serialization subsystem." } val checkedFactory = factory as? SerializationFactoryImpl
?: throw IllegalStateException("RPC client encountered conflicting configuration of serialization subsystem: $factory")
check(checkedFactory.alreadyRegisteredSchemes.any { it is KryoClientSerializationScheme }) {
"RPC client encountered conflicting configuration of serialization subsystem."
}
} }
} }
} }

View File

@ -1,8 +1,10 @@
package net.corda.client.rpc.internal package net.corda.client.rpc.internal
import net.corda.client.rpc.RPCConnection
import net.corda.client.rpc.RPCException import net.corda.client.rpc.RPCException
import net.corda.core.crypto.random63BitValue import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.logElapsedTime import net.corda.core.internal.logElapsedTime
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.RPCOps import net.corda.core.messaging.RPCOps
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SerializationDefaults
@ -17,7 +19,6 @@ import net.corda.nodeapi.config.SSLConfiguration
import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.TransportConfiguration import org.apache.activemq.artemis.api.core.TransportConfiguration
import org.apache.activemq.artemis.api.core.client.ActiveMQClient import org.apache.activemq.artemis.api.core.client.ActiveMQClient
import java.io.Closeable
import java.lang.reflect.Proxy import java.lang.reflect.Proxy
import java.time.Duration import java.time.Duration
@ -79,12 +80,6 @@ data class RPCClientConfiguration(
} }
} }
/**
* An RPC client that may be used to create connections to an RPC server.
*
* @param transport The Artemis transport to use to connect to the server.
* @param rpcConfiguration Configuration used to tweak client behaviour.
*/
class RPCClient<I : RPCOps>( class RPCClient<I : RPCOps>(
val transport: TransportConfiguration, val transport: TransportConfiguration,
val rpcConfiguration: RPCClientConfiguration = RPCClientConfiguration.default, val rpcConfiguration: RPCClientConfiguration = RPCClientConfiguration.default,
@ -101,54 +96,6 @@ class RPCClient<I : RPCOps>(
private val log = loggerFor<RPCClient<*>>() private val log = loggerFor<RPCClient<*>>()
} }
/**
* Holds a proxy object implementing [I] that forwards requests to the RPC server.
*
* [Closeable.close] may be used to shut down the connection and release associated resources.
*/
interface RPCConnection<out I : RPCOps> : Closeable {
val proxy: I
/** The RPC protocol version reported by the server */
val serverProtocolVersion: Int
/**
* Closes this client without notifying the server.
* The server will eventually clear out the RPC message queue and disconnect subscribed observers,
* but this may take longer than desired, so to conserve resources you should normally use [notifyServerAndClose].
* This method is helpful when the node may be shutting down or
* have already shut down and you don't want to block waiting for it to come back.
*/
fun forceClose()
/**
* Closes this client gracefully by sending a notification to the server, so it can immediately clean up resources.
* If the server is not available this method may block for a short period until it's clear the server is not coming back.
*/
fun notifyServerAndClose()
}
/**
* Returns an [RPCConnection] containing a proxy that lets you invoke RPCs on the server. Calls on it block, and if
* the server throws an exception then it will be rethrown on the client. Proxies are thread safe and may be used to
* invoke multiple RPCs in parallel.
*
* RPC sends and receives are logged on the net.corda.rpc logger.
*
* The [RPCOps] defines what client RPCs are available. If an RPC returns an [Observable] anywhere in the object
* graph returned then the server-side observable is transparently forwarded to the client side here.
* *You are expected to use it*. The server will begin sending messages immediately that will be buffered on the
* client, you are expected to drain by subscribing to the returned observer. You can opt-out of this by simply
* calling the [net.corda.client.rpc.notUsed] method on it. You don't have to explicitly close the observable if you actually
* subscribe to it: it will close itself and free up the server-side resources either when the client or JVM itself
* is shutdown, or when there are no more subscribers to it. Once all the subscribers to a returned observable are
* unsubscribed or the observable completes successfully or with an error, the observable is closed and you can't
* then re-subscribe again: you'll have to re-request a fresh observable with another RPC.
*
* @param rpcOpsClass The [Class] of the RPC interface.
* @param username The username to authenticate with.
* @param password The password to authenticate with.
* @throws RPCException if the server version is too low or if the server isn't reachable within the given time.
*/
fun start( fun start(
rpcOpsClass: Class<I>, rpcOpsClass: Class<I>,
username: String, username: String,
@ -168,10 +115,7 @@ class RPCClient<I : RPCOps>(
val proxyHandler = RPCClientProxyHandler(rpcConfiguration, username, password, serverLocator, clientAddress, rpcOpsClass, serializationContext) val proxyHandler = RPCClientProxyHandler(rpcConfiguration, username, password, serverLocator, clientAddress, rpcOpsClass, serializationContext)
try { try {
proxyHandler.start() proxyHandler.start()
val ops: I = uncheckedCast(Proxy.newProxyInstance(rpcOpsClass.classLoader, arrayOf(rpcOpsClass), proxyHandler))
@Suppress("UNCHECKED_CAST")
val ops = Proxy.newProxyInstance(rpcOpsClass.classLoader, arrayOf(rpcOpsClass), proxyHandler) as I
val serverProtocolVersion = ops.protocolVersion val serverProtocolVersion = ops.protocolVersion
if (serverProtocolVersion < rpcConfiguration.minimumServerProtocolVersion) { if (serverProtocolVersion < rpcConfiguration.minimumServerProtocolVersion) {
throw RPCException("Requested minimum protocol version (${rpcConfiguration.minimumServerProtocolVersion}) is higher" + throw RPCException("Requested minimum protocol version (${rpcConfiguration.minimumServerProtocolVersion}) is higher" +

View File

@ -19,6 +19,7 @@ import net.corda.core.internal.LifeCycle
import net.corda.core.internal.ThreadBox import net.corda.core.internal.ThreadBox
import net.corda.core.messaging.RPCOps import net.corda.core.messaging.RPCOps
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.serialize
import net.corda.core.utilities.Try import net.corda.core.utilities.Try
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
@ -78,6 +79,7 @@ class RPCClientProxyHandler(
STARTED, STARTED,
FINISHED FINISHED
} }
private val lifeCycle = LifeCycle(State.UNSTARTED) private val lifeCycle = LifeCycle(State.UNSTARTED)
private companion object { private companion object {
@ -208,11 +210,12 @@ class RPCClientProxyHandler(
val rpcId = RPCApi.RpcRequestId(random63BitValue()) val rpcId = RPCApi.RpcRequestId(random63BitValue())
callSiteMap?.set(rpcId.toLong, Throwable("<Call site of root RPC '${method.name}'>")) callSiteMap?.set(rpcId.toLong, Throwable("<Call site of root RPC '${method.name}'>"))
try { try {
val request = RPCApi.ClientToServer.RpcRequest(clientAddress, rpcId, method.name, arguments?.toList() ?: emptyList()) val serialisedArguments = (arguments?.toList() ?: emptyList()).serialize(context = serializationContextWithObservableContext)
val request = RPCApi.ClientToServer.RpcRequest(clientAddress, rpcId, method.name, serialisedArguments.bytes)
val replyFuture = SettableFuture.create<Any>() val replyFuture = SettableFuture.create<Any>()
sessionAndProducerPool.run { sessionAndProducerPool.run {
val message = it.session.createMessage(false) val message = it.session.createMessage(false)
request.writeToClientMessage(serializationContextWithObservableContext, message) request.writeToClientMessage(message)
log.debug { log.debug {
val argumentsString = arguments?.joinToString() ?: "" val argumentsString = arguments?.joinToString() ?: ""

View File

@ -6,7 +6,6 @@ import net.corda.core.identity.CordaX500Name;
import net.corda.core.identity.Party; import net.corda.core.identity.Party;
import net.corda.core.messaging.CordaRPCOps; import net.corda.core.messaging.CordaRPCOps;
import net.corda.core.messaging.FlowHandle; import net.corda.core.messaging.FlowHandle;
import net.corda.core.node.NodeInfo;
import net.corda.core.utilities.OpaqueBytes; import net.corda.core.utilities.OpaqueBytes;
import net.corda.finance.flows.AbstractCashFlow; import net.corda.finance.flows.AbstractCashFlow;
import net.corda.finance.flows.CashIssueFlow; import net.corda.finance.flows.CashIssueFlow;
@ -41,7 +40,6 @@ public class StandaloneCordaRPCJavaClientTest {
private NodeProcess notary; private NodeProcess notary;
private CordaRPCOps rpcProxy; private CordaRPCOps rpcProxy;
private CordaRPCConnection connection; private CordaRPCConnection connection;
private NodeInfo notaryNode;
private Party notaryNodeIdentity; private Party notaryNodeIdentity;
private NodeConfig notaryConfig = new NodeConfig( private NodeConfig notaryConfig = new NodeConfig(
@ -49,8 +47,8 @@ public class StandaloneCordaRPCJavaClientTest {
port.getAndIncrement(), port.getAndIncrement(),
port.getAndIncrement(), port.getAndIncrement(),
port.getAndIncrement(), port.getAndIncrement(),
Collections.singletonList("corda.notary.validating"), true,
Arrays.asList(rpcUser), Collections.singletonList(rpcUser),
null null
); );
@ -61,7 +59,6 @@ public class StandaloneCordaRPCJavaClientTest {
notary = factory.create(notaryConfig); notary = factory.create(notaryConfig);
connection = notary.connect(); connection = notary.connect();
rpcProxy = connection.getProxy(); rpcProxy = connection.getProxy();
notaryNode = fetchNotaryIdentity();
notaryNodeIdentity = rpcProxy.nodeInfo().getLegalIdentities().get(0); notaryNodeIdentity = rpcProxy.nodeInfo().getLegalIdentities().get(0);
} }
@ -70,16 +67,16 @@ public class StandaloneCordaRPCJavaClientTest {
try { try {
connection.close(); connection.close();
} finally { } finally {
if(notary != null) { if (notary != null) {
notary.close(); notary.close();
} }
} }
} }
private void copyFinanceCordapp() { private void copyFinanceCordapp() {
Path pluginsDir = (factory.baseDirectory(notaryConfig).resolve("plugins")); Path cordappsDir = (factory.baseDirectory(notaryConfig).resolve("cordapps"));
try { try {
Files.createDirectories(pluginsDir); Files.createDirectories(cordappsDir);
} catch (IOException ex) { } catch (IOException ex) {
fail("Failed to create directories"); fail("Failed to create directories");
} }
@ -87,7 +84,7 @@ public class StandaloneCordaRPCJavaClientTest {
paths.forEach(file -> { paths.forEach(file -> {
if (file.toString().contains("corda-finance")) { if (file.toString().contains("corda-finance")) {
try { try {
Files.copy(file, pluginsDir.resolve(file.getFileName())); Files.copy(file, cordappsDir.resolve(file.getFileName()));
} catch (IOException ex) { } catch (IOException ex) {
fail("Failed to copy finance jar"); fail("Failed to copy finance jar");
} }
@ -98,11 +95,6 @@ public class StandaloneCordaRPCJavaClientTest {
} }
} }
private NodeInfo fetchNotaryIdentity() {
List<NodeInfo> nodeDataSnapshot = rpcProxy.networkMapSnapshot();
return nodeDataSnapshot.get(0);
}
@Test @Test
public void testCashBalances() throws NoSuchFieldException, ExecutionException, InterruptedException { public void testCashBalances() throws NoSuchFieldException, ExecutionException, InterruptedException {
Amount<Currency> dollars123 = new Amount<>(123, Currency.getInstance("USD")); Amount<Currency> dollars123 = new Amount<>(123, Currency.getInstance("USD"));

View File

@ -64,7 +64,7 @@ class StandaloneCordaRPClientTest {
p2pPort = port.andIncrement, p2pPort = port.andIncrement,
rpcPort = port.andIncrement, rpcPort = port.andIncrement,
webPort = port.andIncrement, webPort = port.andIncrement,
extraServices = listOf("corda.notary.validating"), isNotary = true,
users = listOf(user) users = listOf(user)
) )
@ -89,12 +89,12 @@ class StandaloneCordaRPClientTest {
} }
private fun copyFinanceCordapp() { private fun copyFinanceCordapp() {
val pluginsDir = (factory.baseDirectory(notaryConfig) / "plugins").createDirectories() val cordappsDir = (factory.baseDirectory(notaryConfig) / "cordapps").createDirectories()
// Find the finance jar file for the smoke tests of this module // Find the finance jar file for the smoke tests of this module
val financeJar = Paths.get("build", "resources", "smokeTest").list { val financeJar = Paths.get("build", "resources", "smokeTest").list {
it.filter { "corda-finance" in it.toString() }.toList().single() it.filter { "corda-finance" in it.toString() }.toList().single()
} }
financeJar.copyToDirectory(pluginsDir) financeJar.copyToDirectory(cordappsDir)
} }
@Test @Test
@ -114,14 +114,14 @@ class StandaloneCordaRPClientTest {
@Test @Test
fun `test starting flow`() { fun `test starting flow`() {
rpcProxy.startFlow(::CashIssueFlow, 127.POUNDS, OpaqueBytes.of(0), notaryNodeIdentity) rpcProxy.startFlow(::CashIssueFlow, 127.POUNDS, OpaqueBytes.of(0), notaryNodeIdentity)
.returnValue.getOrThrow(timeout) .returnValue.getOrThrow(timeout)
} }
@Test @Test
fun `test starting tracked flow`() { fun `test starting tracked flow`() {
var trackCount = 0 var trackCount = 0
val handle = rpcProxy.startTrackedFlow( val handle = rpcProxy.startTrackedFlow(
::CashIssueFlow, 429.DOLLARS, OpaqueBytes.of(0), notaryNodeIdentity ::CashIssueFlow, 429.DOLLARS, OpaqueBytes.of(0), notaryNodeIdentity
) )
val updateLatch = CountDownLatch(1) val updateLatch = CountDownLatch(1)
handle.progress.subscribe { msg -> handle.progress.subscribe { msg ->
@ -156,7 +156,7 @@ class StandaloneCordaRPClientTest {
// Now issue some cash // Now issue some cash
rpcProxy.startFlow(::CashIssueFlow, 513.SWISS_FRANCS, OpaqueBytes.of(0), notaryNodeIdentity) rpcProxy.startFlow(::CashIssueFlow, 513.SWISS_FRANCS, OpaqueBytes.of(0), notaryNodeIdentity)
.returnValue.getOrThrow(timeout) .returnValue.getOrThrow(timeout)
updateLatch.await() updateLatch.await()
assertEquals(1, updateCount.get()) assertEquals(1, updateCount.get())
} }

View File

@ -20,10 +20,13 @@ open class AbstractRPCTest {
} }
companion object { companion object {
@JvmStatic @Parameterized.Parameters(name = "Mode = {0}") @JvmStatic
@Parameterized.Parameters(name = "Mode = {0}")
fun defaultModes() = modes(RPCTestMode.InVm, RPCTestMode.Netty) fun defaultModes() = modes(RPCTestMode.InVm, RPCTestMode.Netty)
fun modes(vararg modes: RPCTestMode) = listOf(*modes).map { arrayOf(it) } fun modes(vararg modes: RPCTestMode) = listOf(*modes).map { arrayOf(it) }
} }
@Parameterized.Parameter @Parameterized.Parameter
lateinit var mode: RPCTestMode lateinit var mode: RPCTestMode

View File

@ -26,9 +26,11 @@ import java.util.concurrent.TimeUnit
@RunWith(Parameterized::class) @RunWith(Parameterized::class)
class RPCPerformanceTests : AbstractRPCTest() { class RPCPerformanceTests : AbstractRPCTest() {
companion object { companion object {
@JvmStatic @Parameterized.Parameters(name = "Mode = {0}") @JvmStatic
@Parameterized.Parameters(name = "Mode = {0}")
fun modes() = modes(RPCTestMode.Netty) fun modes() = modes(RPCTestMode.Netty)
} }
private interface TestOps : RPCOps { private interface TestOps : RPCOps {
fun simpleReply(input: ByteArray, sizeOfReply: Int): ByteArray fun simpleReply(input: ByteArray, sizeOfReply: Int): ByteArray
} }
@ -60,7 +62,7 @@ class RPCPerformanceTests : AbstractRPCTest() {
val executor = Executors.newFixedThreadPool(4) val executor = Executors.newFixedThreadPool(4)
val N = 10000 val N = 10000
val latch = CountDownLatch(N) val latch = CountDownLatch(N)
for (i in 1 .. N) { for (i in 1..N) {
executor.submit { executor.submit {
proxy.ops.simpleReply(ByteArray(1024), 1024) proxy.ops.simpleReply(ByteArray(1024), 1024)
latch.countDown() latch.countDown()
@ -155,10 +157,12 @@ class RPCPerformanceTests : AbstractRPCTest() {
data class BigMessagesResult( data class BigMessagesResult(
val Mbps: Double val Mbps: Double
) )
@Test @Test
fun `big messages`() { fun `big messages`() {
warmup() warmup()
measure(listOf(1)) { clientParallelism -> // TODO this hangs with more parallelism measure(listOf(1)) { clientParallelism ->
// TODO this hangs with more parallelism
rpcDriver { rpcDriver {
val proxy = testProxy( val proxy = testProxy(
RPCClientConfiguration.default, RPCClientConfiguration.default,

View File

@ -79,7 +79,7 @@ class RPCPermissionsTests : AbstractRPCTest() {
} }
@Test @Test
fun `check ALL is implemented the correct way round` () { fun `check ALL is implemented the correct way round`() {
rpcDriver { rpcDriver {
val joeUser = userOf("joe", setOf(DUMMY_FLOW)) val joeUser = userOf("joe", setOf(DUMMY_FLOW))
val proxy = testProxyFor(joeUser) val proxy = testProxyFor(joeUser)

View File

@ -13,6 +13,7 @@ class RepeatingBytesInputStream(val bytesToRepeat: ByteArray, val numberOfBytes:
return bytesToRepeat[(numberOfBytes - bytesLeft) % bytesToRepeat.size].toInt() return bytesToRepeat[(numberOfBytes - bytesLeft) % bytesToRepeat.size].toInt()
} }
} }
override fun read(byteArray: ByteArray, offset: Int, length: Int): Int { override fun read(byteArray: ByteArray, offset: Int, length: Int): Int {
val lastIdx = Math.min(Math.min(offset + length, byteArray.size), offset + bytesLeft) val lastIdx = Math.min(Math.min(offset + length, byteArray.size), offset + bytesLeft)
for (i in offset until lastIdx) { for (i in offset until lastIdx) {

View File

@ -6,15 +6,12 @@ apply plugin: 'kotlin'
apply plugin: CanonicalizerPlugin apply plugin: CanonicalizerPlugin
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: 'com.jfrog.artifactory' apply plugin: 'com.jfrog.artifactory'
description 'Corda Experimental Confidential Identities' description 'Corda Experimental Confidential Identities'
dependencies { dependencies {
// Note the :confidential-identities module is a CorDapp in its own right compile project(':core')
// and CorDapps using :confidential-identities features should use 'cordapp' not 'compile' linkage.
cordaCompile project(':core')
// Quasar, for suspendable fibres. // Quasar, for suspendable fibres.
compileOnly "co.paralleluniverse:quasar-core:$quasar_version:jdk8" compileOnly "co.paralleluniverse:quasar-core:$quasar_version:jdk8"

View File

@ -12,14 +12,12 @@ import net.corda.core.utilities.unwrap
object IdentitySyncFlow { object IdentitySyncFlow {
/** /**
* Flow for ensuring that one or more counterparties to a transaction have the full certificate paths of confidential * Flow for ensuring that our counterparties in a transaction have the full certificate paths for *our* confidential
* identities used in the transaction. This is intended for use as a subflow of another flow, typically between * identities used in states present in the transaction. This is intended for use as a subflow of another flow, typically between
* transaction assembly and signing. An example of where this is useful is where a recipient of a [Cash] state wants * transaction assembly and signing. An example of where this is useful is where a recipient of a [Cash] state wants
* to know that it is being paid by the correct party, and the owner of the state is a confidential identity of that * to know that it is being paid by the correct party, and the owner of the state is a confidential identity of that
* party. This flow would send a copy of the confidential identity path to the recipient, enabling them to verify that * party. This flow would send a copy of the confidential identity path to the recipient, enabling them to verify that
* identity. * identity.
*
* @return a mapping of well known identities to the confidential identities used in the transaction.
*/ */
// TODO: Can this be triggered automatically from [SendTransactionFlow] // TODO: Can this be triggered automatically from [SendTransactionFlow]
class Send(val otherSideSessions: Set<FlowSession>, class Send(val otherSideSessions: Set<FlowSession>,
@ -36,17 +34,10 @@ object IdentitySyncFlow {
@Suspendable @Suspendable
override fun call() { override fun call() {
progressTracker.currentStep = SYNCING_IDENTITIES progressTracker.currentStep = SYNCING_IDENTITIES
val states: List<ContractState> = (tx.inputs.map { serviceHub.loadState(it) }.requireNoNulls().map { it.data } + tx.outputs.map { it.data }) val identityCertificates: Map<AbstractParty, PartyAndCertificate?> = extractOurConfidentialIdentities()
val identities: Set<AbstractParty> = states.flatMap { it.participants }.toSet()
// Filter participants down to the set of those not in the network map (are not well known)
val confidentialIdentities = identities
.filter { serviceHub.networkMapCache.getNodesByLegalIdentityKey(it.owningKey).isEmpty() }
.toList()
val identityCertificates: Map<AbstractParty, PartyAndCertificate?> = identities
.map { Pair(it, serviceHub.identityService.certificateFromKey(it.owningKey)) }.toMap()
otherSideSessions.forEach { otherSideSession -> otherSideSessions.forEach { otherSideSession ->
val requestedIdentities: List<AbstractParty> = otherSideSession.sendAndReceive<List<AbstractParty>>(confidentialIdentities).unwrap { req -> val requestedIdentities: List<AbstractParty> = otherSideSession.sendAndReceive<List<AbstractParty>>(identityCertificates.keys.toList()).unwrap { req ->
require(req.all { it in identityCertificates.keys }) { "${otherSideSession.counterparty} requested a confidential identity not part of transaction: ${tx.id}" } require(req.all { it in identityCertificates.keys }) { "${otherSideSession.counterparty} requested a confidential identity not part of transaction: ${tx.id}" }
req req
} }
@ -61,6 +52,20 @@ object IdentitySyncFlow {
} }
} }
private fun extractOurConfidentialIdentities(): Map<AbstractParty, PartyAndCertificate?> {
val states: List<ContractState> = (tx.inputs.map { serviceHub.loadState(it) }.requireNoNulls().map { it.data } + tx.outputs.map { it.data })
val identities: Set<AbstractParty> = states.flatMap(ContractState::participants).toSet()
// Filter participants down to the set of those not in the network map (are not well known)
val confidentialIdentities = identities
.filter { serviceHub.networkMapCache.getNodesByLegalIdentityKey(it.owningKey).isEmpty() }
.toList()
return confidentialIdentities
.map { Pair(it, serviceHub.identityService.certificateFromKey(it.owningKey)) }
// Filter down to confidential identities of our well known identity
// TODO: Consider if this too restrictive - we perhaps should be checking the name on the signing certificate in the certificate path instead
.filter { it.second?.name == ourIdentity.name }
.toMap()
}
} }
/** /**

View File

@ -1,15 +1,32 @@
package net.corda.confidential package net.corda.confidential
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.DigitalSignature
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.AnonymousParty import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.toX509CertHolder
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap import net.corda.core.utilities.unwrap
import org.bouncycastle.asn1.DERSet
import org.bouncycastle.asn1.pkcs.CertificationRequestInfo
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import java.io.ByteArrayOutputStream
import java.nio.charset.Charset
import java.security.PublicKey
import java.security.SignatureException
import java.security.cert.CertPath
import java.util.*
/** /**
* Very basic flow which generates new confidential identities for parties in a transaction and exchanges the transaction * Very basic flow which generates new confidential identities for parties in a transaction and exchanges the transaction
@ -27,8 +44,32 @@ class SwapIdentitiesFlow(private val otherParty: Party,
object AWAITING_KEY : ProgressTracker.Step("Awaiting key") object AWAITING_KEY : ProgressTracker.Step("Awaiting key")
fun tracker() = ProgressTracker(AWAITING_KEY) fun tracker() = ProgressTracker(AWAITING_KEY)
fun validateAndRegisterIdentity(identityService: IdentityService, otherSide: Party, anonymousOtherSide: PartyAndCertificate): PartyAndCertificate { /**
require(anonymousOtherSide.name == otherSide.name) * Generate the determinstic data blob the confidential identity's key holder signs to indicate they want to
* represent the subject named in the X.509 certificate. Note that this is never actually sent between nodes,
* but only the signature is sent. The blob is built independently on each node and the received signature
* verified against the expected blob, rather than exchanging the blob.
*/
fun buildDataToSign(confidentialIdentity: PartyAndCertificate): ByteArray {
val certOwnerAssert = CertificateOwnershipAssertion(confidentialIdentity.name, confidentialIdentity.owningKey)
return certOwnerAssert.serialize().bytes
}
@Throws(SwapIdentitiesException::class)
fun validateAndRegisterIdentity(identityService: IdentityService,
otherSide: Party,
anonymousOtherSideBytes: PartyAndCertificate,
sigBytes: DigitalSignature): PartyAndCertificate {
val anonymousOtherSide: PartyAndCertificate = anonymousOtherSideBytes
if (anonymousOtherSide.name != otherSide.name) {
throw SwapIdentitiesException("Certificate subject must match counterparty's well known identity.")
}
val signature = DigitalSignature.WithKey(anonymousOtherSide.owningKey, sigBytes.bytes)
try {
signature.verify(buildDataToSign(anonymousOtherSideBytes))
} catch(ex: SignatureException) {
throw SwapIdentitiesException("Signature does not match the expected identity ownership assertion.", ex)
}
// Validate then store their identity so that we can prove the key in the transaction is owned by the // Validate then store their identity so that we can prove the key in the transaction is owned by the
// counterparty. // counterparty.
identityService.verifyAndRegisterIdentity(anonymousOtherSide) identityService.verifyAndRegisterIdentity(anonymousOtherSide)
@ -40,6 +81,7 @@ class SwapIdentitiesFlow(private val otherParty: Party,
override fun call(): LinkedHashMap<Party, AnonymousParty> { override fun call(): LinkedHashMap<Party, AnonymousParty> {
progressTracker.currentStep = AWAITING_KEY progressTracker.currentStep = AWAITING_KEY
val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, revocationEnabled) val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, revocationEnabled)
val serializedIdentity = SerializedBytes<PartyAndCertificate>(legalIdentityAnonymous.serialize().bytes)
// Special case that if we're both parties, a single identity is generated // Special case that if we're both parties, a single identity is generated
val identities = LinkedHashMap<Party, AnonymousParty>() val identities = LinkedHashMap<Party, AnonymousParty>()
@ -47,13 +89,33 @@ class SwapIdentitiesFlow(private val otherParty: Party,
identities.put(otherParty, legalIdentityAnonymous.party.anonymise()) identities.put(otherParty, legalIdentityAnonymous.party.anonymise())
} else { } else {
val otherSession = initiateFlow(otherParty) val otherSession = initiateFlow(otherParty)
val anonymousOtherSide = otherSession.sendAndReceive<PartyAndCertificate>(legalIdentityAnonymous).unwrap { confidentialIdentity -> val data = buildDataToSign(legalIdentityAnonymous)
validateAndRegisterIdentity(serviceHub.identityService, otherSession.counterparty, confidentialIdentity) val ourSig: DigitalSignature.WithKey = serviceHub.keyManagementService.sign(data, legalIdentityAnonymous.owningKey)
} val ourIdentWithSig = IdentityWithSignature(serializedIdentity, ourSig.withoutKey())
val anonymousOtherSide = otherSession.sendAndReceive<IdentityWithSignature>(ourIdentWithSig)
.unwrap { (confidentialIdentityBytes, theirSigBytes) ->
val confidentialIdentity: PartyAndCertificate = confidentialIdentityBytes.bytes.deserialize()
validateAndRegisterIdentity(serviceHub.identityService, otherParty, confidentialIdentity, theirSigBytes)
}
identities.put(ourIdentity, legalIdentityAnonymous.party.anonymise()) identities.put(ourIdentity, legalIdentityAnonymous.party.anonymise())
identities.put(otherSession.counterparty, anonymousOtherSide.party.anonymise()) identities.put(otherParty, anonymousOtherSide.party.anonymise())
} }
return identities return identities
} }
@CordaSerializable
data class IdentityWithSignature(val identity: SerializedBytes<PartyAndCertificate>, val signature: DigitalSignature)
} }
/**
* Data class used only in the context of asserting the owner of the private key for the listed key wants to use it
* to represent the named entity. This is pairs with an X.509 certificate (which asserts the signing identity says
* the key represents the named entity), but protects against a certificate authority incorrectly claiming others'
* keys.
*/
@CordaSerializable
data class CertificateOwnershipAssertion(val x500Name: CordaX500Name,
val publicKey: PublicKey)
open class SwapIdentitiesException @JvmOverloads constructor(message: String, cause: Throwable? = null)
: FlowException(message, cause)

View File

@ -4,6 +4,9 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession import net.corda.core.flows.FlowSession
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap import net.corda.core.utilities.unwrap
@ -20,9 +23,14 @@ class SwapIdentitiesHandler(val otherSideSession: FlowSession, val revocationEna
override fun call() { override fun call() {
val revocationEnabled = false val revocationEnabled = false
progressTracker.currentStep = SENDING_KEY progressTracker.currentStep = SENDING_KEY
val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, revocationEnabled) val ourConfidentialIdentity = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, revocationEnabled)
otherSideSession.sendAndReceive<PartyAndCertificate>(legalIdentityAnonymous).unwrap { confidentialIdentity -> val serializedIdentity = SerializedBytes<PartyAndCertificate>(ourConfidentialIdentity.serialize().bytes)
SwapIdentitiesFlow.validateAndRegisterIdentity(serviceHub.identityService, otherSideSession.counterparty, confidentialIdentity) val data = SwapIdentitiesFlow.buildDataToSign(ourConfidentialIdentity)
} val ourSig = serviceHub.keyManagementService.sign(data, ourConfidentialIdentity.owningKey)
otherSideSession.sendAndReceive<SwapIdentitiesFlow.IdentityWithSignature>(SwapIdentitiesFlow.IdentityWithSignature(serializedIdentity, ourSig.withoutKey()))
.unwrap { (theirConfidentialIdentityBytes, theirSigBytes) ->
val theirConfidentialIdentity = theirConfidentialIdentityBytes.deserialize()
SwapIdentitiesFlow.validateAndRegisterIdentity(serviceHub.identityService, otherSideSession.counterparty, theirConfidentialIdentity, theirSigBytes)
}
} }
} }

View File

@ -13,12 +13,14 @@ import net.corda.core.utilities.unwrap
import net.corda.finance.DOLLARS import net.corda.finance.DOLLARS
import net.corda.finance.contracts.asset.Cash import net.corda.finance.contracts.asset.Cash
import net.corda.finance.flows.CashIssueAndPaymentFlow import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull import kotlin.test.assertNull
class IdentitySyncFlowTests { class IdentitySyncFlowTests {
@ -26,31 +28,29 @@ class IdentitySyncFlowTests {
@Before @Before
fun before() { fun before() {
setCordappPackages("net.corda.finance.contracts.asset")
// We run this in parallel threads to help catch any race conditions that may exist. // We run this in parallel threads to help catch any race conditions that may exist.
mockNet = MockNetwork(networkSendManuallyPumped = false, threadPerNode = true) mockNet = MockNetwork(networkSendManuallyPumped = false, threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset"))
} }
@After @After
fun cleanUp() { fun cleanUp() {
mockNet.stopNodes() mockNet.stopNodes()
unsetCordappPackages()
} }
@Test @Test
fun `sync confidential identities`() { fun `sync confidential identities`() {
// Set up values we'll need // Set up values we'll need
val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) val notaryNode = mockNet.createNotaryNode()
val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name) val aliceNode = mockNet.createPartyNode(ALICE_NAME)
val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name) val bobNode = mockNet.createPartyNode(BOB_NAME)
val alice: Party = aliceNode.services.myInfo.chooseIdentity() val alice: Party = aliceNode.info.singleIdentity()
val bob: Party = bobNode.services.myInfo.chooseIdentity() val bob: Party = bobNode.info.singleIdentity()
val notary = notaryNode.services.getDefaultNotary()
bobNode.internals.registerInitiatedFlow(Receive::class.java) bobNode.internals.registerInitiatedFlow(Receive::class.java)
// Alice issues then pays some cash to a new confidential identity that Bob doesn't know about // Alice issues then pays some cash to a new confidential identity that Bob doesn't know about
val anonymous = true val anonymous = true
val ref = OpaqueBytes.of(0x01) val ref = OpaqueBytes.of(0x01)
val notary = aliceNode.services.getDefaultNotary()
val issueFlow = aliceNode.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, alice, anonymous, notary)) val issueFlow = aliceNode.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, alice, anonymous, notary))
val issueTx = issueFlow.resultFuture.getOrThrow().stx val issueTx = issueFlow.resultFuture.getOrThrow().stx
val confidentialIdentity = issueTx.tx.outputs.map { it.data }.filterIsInstance<Cash.State>().single().owner val confidentialIdentity = issueTx.tx.outputs.map { it.data }.filterIsInstance<Cash.State>().single().owner
@ -67,6 +67,40 @@ class IdentitySyncFlowTests {
assertEquals(expected, actual) assertEquals(expected, actual)
} }
@Test
fun `don't offer other's identities confidential identities`() {
// Set up values we'll need
val notaryNode = mockNet.createNotaryNode()
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
val bobNode = mockNet.createPartyNode(BOB_NAME)
val charlieNode = mockNet.createPartyNode(CHARLIE_NAME)
val alice: Party = aliceNode.info.singleIdentity()
val bob: Party = bobNode.info.singleIdentity()
val charlie: Party = charlieNode.info.singleIdentity()
val notary = notaryNode.services.getDefaultNotary()
bobNode.internals.registerInitiatedFlow(Receive::class.java)
// Charlie issues then pays some cash to a new confidential identity
val anonymous = true
val ref = OpaqueBytes.of(0x01)
val issueFlow = charlieNode.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, charlie, anonymous, notary))
val issueTx = issueFlow.resultFuture.getOrThrow().stx
val confidentialIdentity = issueTx.tx.outputs.map { it.data }.filterIsInstance<Cash.State>().single().owner
val confidentialIdentCert = charlieNode.services.identityService.certificateFromKey(confidentialIdentity.owningKey)!!
// Manually inject this identity into Alice's database so the node could leak it, but we prove won't
aliceNode.database.transaction { aliceNode.services.identityService.verifyAndRegisterIdentity(confidentialIdentCert) }
assertNotNull(aliceNode.database.transaction { aliceNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) })
// Generate a payment from Charlie to Alice, including the confidential state
val payTx = charlieNode.services.startFlow(CashPaymentFlow(1000.DOLLARS, alice, anonymous)).resultFuture.getOrThrow().stx
// Run the flow to sync up the identities, and confirm Charlie's confidential identity doesn't leak
assertNull(bobNode.database.transaction { bobNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) })
aliceNode.services.startFlow(Initiator(bob, payTx.tx)).resultFuture.getOrThrow()
assertNull(bobNode.database.transaction { bobNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) })
}
/** /**
* Very lightweight wrapping flow to trigger the counterparty flow that receives the identities. * Very lightweight wrapping flow to trigger the counterparty flow that receives the identities.
*/ */

View File

@ -4,16 +4,10 @@ import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.testing.ALICE import net.corda.testing.*
import net.corda.testing.BOB
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.chooseIdentity
import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.*
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
class SwapIdentitiesFlowTests { class SwapIdentitiesFlowTests {
@Test @Test
@ -22,12 +16,11 @@ class SwapIdentitiesFlowTests {
val mockNet = MockNetwork(false, true) val mockNet = MockNetwork(false, true)
// Set up values we'll need // Set up values we'll need
val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) val notaryNode = mockNet.createNotaryNode()
val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name) val aliceNode = mockNet.createPartyNode(ALICE.name)
val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name) val bobNode = mockNet.createPartyNode(BOB.name)
val alice: Party = aliceNode.services.myInfo.chooseIdentity() val alice = aliceNode.info.singleIdentity()
val bob: Party = bobNode.services.myInfo.chooseIdentity() val bob = bobNode.services.myInfo.singleIdentity()
mockNet.registerIdentities()
// Run the flows // Run the flows
val requesterFlow = aliceNode.services.startFlow(SwapIdentitiesFlow(bob)) val requesterFlow = aliceNode.services.startFlow(SwapIdentitiesFlow(bob))
@ -53,4 +46,69 @@ class SwapIdentitiesFlowTests {
mockNet.stopNodes() mockNet.stopNodes()
} }
/**
* Check that flow is actually validating the name on the certificate presented by the counterparty.
*/
@Test
fun `verifies identity name`() {
// We run this in parallel threads to help catch any race conditions that may exist.
val mockNet = MockNetwork(false, true)
// Set up values we'll need
val notaryNode = mockNet.createNotaryNode(DUMMY_NOTARY.name)
val aliceNode = mockNet.createPartyNode(ALICE.name)
val bobNode = mockNet.createPartyNode(BOB.name)
val bob: Party = bobNode.services.myInfo.singleIdentity()
val notBob = notaryNode.database.transaction {
notaryNode.services.keyManagementService.freshKeyAndCert(notaryNode.services.myInfo.chooseIdentityAndCert(), false)
}
val sigData = SwapIdentitiesFlow.buildDataToSign(notBob)
val signature = notaryNode.services.keyManagementService.sign(sigData, notBob.owningKey)
assertFailsWith<SwapIdentitiesException>("Certificate subject must match counterparty's well known identity.") {
SwapIdentitiesFlow.validateAndRegisterIdentity(aliceNode.services.identityService, bob, notBob, signature.withoutKey())
}
mockNet.stopNodes()
}
/**
* Check that flow is actually validating its the signature presented by the counterparty.
*/
@Test
fun `verifies signature`() {
// We run this in parallel threads to help catch any race conditions that may exist.
val mockNet = MockNetwork(false, true)
// Set up values we'll need
val notaryNode = mockNet.createNotaryNode(DUMMY_NOTARY.name)
val aliceNode = mockNet.createPartyNode(ALICE.name)
val bobNode = mockNet.createPartyNode(BOB.name)
val bob: Party = bobNode.services.myInfo.singleIdentity()
// Check that the wrong signature is rejected
notaryNode.database.transaction {
notaryNode.services.keyManagementService.freshKeyAndCert(notaryNode.services.myInfo.chooseIdentityAndCert(), false)
}.let { anonymousNotary ->
val sigData = SwapIdentitiesFlow.buildDataToSign(anonymousNotary)
val signature = notaryNode.services.keyManagementService.sign(sigData, anonymousNotary.owningKey)
assertFailsWith<SwapIdentitiesException>("Signature does not match the given identity and nonce") {
SwapIdentitiesFlow.validateAndRegisterIdentity(aliceNode.services.identityService, bob, anonymousNotary, signature.withoutKey())
}
}
// Check that the right signing key, but wrong identity is rejected
val anonymousAlice = aliceNode.database.transaction {
aliceNode.services.keyManagementService.freshKeyAndCert(aliceNode.services.myInfo.chooseIdentityAndCert(), false)
}
bobNode.database.transaction {
bobNode.services.keyManagementService.freshKeyAndCert(bobNode.services.myInfo.chooseIdentityAndCert(), false)
}.let { anonymousBob ->
val sigData = SwapIdentitiesFlow.buildDataToSign(anonymousAlice)
val signature = bobNode.services.keyManagementService.sign(sigData, anonymousBob.owningKey)
assertFailsWith<SwapIdentitiesException>("Signature does not match the given identity and nonce.") {
SwapIdentitiesFlow.validateAndRegisterIdentity(aliceNode.services.identityService, bob, anonymousBob, signature.withoutKey())
}
}
mockNet.stopNodes()
}
} }

View File

@ -4,7 +4,6 @@ trustStorePassword : "trustpass"
p2pAddress : "localhost:10002" p2pAddress : "localhost:10002"
rpcAddress : "localhost:10003" rpcAddress : "localhost:10003"
webAddress : "localhost:10004" webAddress : "localhost:10004"
extraAdvertisedServiceIds : [ "corda.interest_rates" ]
networkMapService : { networkMapService : {
address : "localhost:10000" address : "localhost:10000"
legalName : "O=Network Map Service,OU=corda,L=London,C=GB" legalName : "O=Network Map Service,OU=corda,L=London,C=GB"

View File

@ -4,7 +4,6 @@ trustStorePassword : "trustpass"
p2pAddress : "localhost:10005" p2pAddress : "localhost:10005"
rpcAddress : "localhost:10006" rpcAddress : "localhost:10006"
webAddress : "localhost:10007" webAddress : "localhost:10007"
extraAdvertisedServiceIds : [ "corda.interest_rates" ]
networkMapService : { networkMapService : {
address : "localhost:10000" address : "localhost:10000"
legalName : "O=Network Map Service,OU=corda,L=London,C=GB" legalName : "O=Network Map Service,OU=corda,L=London,C=GB"

View File

@ -3,5 +3,7 @@ keyStorePassword : "cordacadevpass"
trustStorePassword : "trustpass" trustStorePassword : "trustpass"
p2pAddress : "localhost:10000" p2pAddress : "localhost:10000"
webAddress : "localhost:10001" webAddress : "localhost:10001"
extraAdvertisedServiceIds : [ "corda.notary.validating" ] notary : {
validating : true
}
useHTTPS : false useHTTPS : false

View File

@ -1,4 +1,4 @@
gradlePluginsVersion=1.0.0 gradlePluginsVersion=2.0.4
kotlinVersion=1.1.50 kotlinVersion=1.1.50
guavaVersion=21.0 guavaVersion=21.0
bouncycastleVersion=1.57 bouncycastleVersion=1.57

View File

@ -2,6 +2,7 @@ apply plugin: 'kotlin'
apply plugin: 'kotlin-jpa' apply plugin: 'kotlin-jpa'
apply plugin: 'net.corda.plugins.quasar-utils' apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'net.corda.plugins.api-scanner'
apply plugin: 'com.jfrog.artifactory' apply plugin: 'com.jfrog.artifactory'
description 'Corda core' description 'Corda core'
@ -94,6 +95,13 @@ jar {
baseName 'corda-core' baseName 'corda-core'
} }
scanApi {
excludeClasses = [
// Kotlin should probably have declared this class as "synthetic".
"net.corda.core.Utils\$toFuture\$1\$subscription\$1"
]
}
publish { publish {
name jar.baseName name jar.baseName
} }

View File

@ -15,10 +15,11 @@ interface CordaThrowable {
open class CordaException internal constructor(override var originalExceptionClassName: String? = null, open class CordaException internal constructor(override var originalExceptionClassName: String? = null,
private var _message: String? = null, private var _message: String? = null,
private var _cause: Throwable? = null) : Exception(null, null, true, true), CordaThrowable { private var _cause: Throwable? = null) : Exception(null, null, true, true), CordaThrowable {
constructor(message: String?, constructor(message: String?,
cause: Throwable?) : this(null, message, cause) cause: Throwable?) : this(null, message, cause)
constructor(message: String?) : this(null, message, null)
override val message: String? override val message: String?
get() = if (originalExceptionClassName == null) originalMessage else { get() = if (originalExceptionClassName == null) originalMessage else {
if (originalMessage == null) "$originalExceptionClassName" else "$originalExceptionClassName: $originalMessage" if (originalMessage == null) "$originalExceptionClassName" else "$originalExceptionClassName: $originalMessage"
@ -59,10 +60,12 @@ open class CordaException internal constructor(override var originalExceptionCla
} }
open class CordaRuntimeException(override var originalExceptionClassName: String?, open class CordaRuntimeException(override var originalExceptionClassName: String?,
private var _message: String? = null, private var _message: String?,
private var _cause: Throwable? = null) : RuntimeException(null, null, true, true), CordaThrowable { private var _cause: Throwable?) : RuntimeException(null, null, true, true), CordaThrowable {
constructor(message: String?, cause: Throwable?) : this(null, message, cause) constructor(message: String?, cause: Throwable?) : this(null, message, cause)
constructor(message: String?) : this(null, message, null)
override val message: String? override val message: String?
get() = if (originalExceptionClassName == null) originalMessage else { get() = if (originalExceptionClassName == null) originalMessage else {
if (originalMessage == null) "$originalExceptionClassName" else "$originalExceptionClassName: $originalMessage" if (originalMessage == null) "$originalExceptionClassName" else "$originalExceptionClassName: $originalMessage"

View File

@ -1,4 +1,5 @@
@file:JvmName("ConcurrencyUtils") @file:JvmName("ConcurrencyUtils")
package net.corda.core.concurrent package net.corda.core.concurrent
import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.openFuture
@ -28,7 +29,7 @@ fun <V, W> firstOf(vararg futures: CordaFuture<out V>, handler: (CordaFuture<out
private val defaultLog = LoggerFactory.getLogger("net.corda.core.concurrent") private val defaultLog = LoggerFactory.getLogger("net.corda.core.concurrent")
@VisibleForTesting @VisibleForTesting
internal val shortCircuitedTaskFailedMessage = "Short-circuited task failed:" internal const val shortCircuitedTaskFailedMessage = "Short-circuited task failed:"
internal fun <V, W> firstOf(futures: Array<out CordaFuture<out V>>, log: Logger, handler: (CordaFuture<out V>) -> W): CordaFuture<W> { internal fun <V, W> firstOf(futures: Array<out CordaFuture<out V>>, log: Logger, handler: (CordaFuture<out V>) -> W): CordaFuture<W> {
val resultFuture = openFuture<W>() val resultFuture = openFuture<W>()

View File

@ -21,7 +21,8 @@ interface TokenizableAssetInfo {
* Amount represents a positive quantity of some token (currency, asset, etc.), measured in quantity of the smallest * Amount represents a positive quantity of some token (currency, asset, etc.), measured in quantity of the smallest
* representable units. The nominal quantity represented by each individual token is equal to the [displayTokenSize]. * representable units. The nominal quantity represented by each individual token is equal to the [displayTokenSize].
* The scale property of the [displayTokenSize] should correctly reflect the displayed decimal places and is used * The scale property of the [displayTokenSize] should correctly reflect the displayed decimal places and is used
* when rounding conversions from indicative/displayed amounts in [BigDecimal] to Amount occur via the Amount.fromDecimal method. * when rounding conversions from indicative/displayed amounts in [BigDecimal] to Amount occur via the
* [Amount.fromDecimal] method.
* *
* Amounts of different tokens *do not mix* and attempting to add or subtract two amounts of different currencies * Amounts of different tokens *do not mix* and attempting to add or subtract two amounts of different currencies
* will throw [IllegalArgumentException]. Amounts may not be negative. Amounts are represented internally using a signed * will throw [IllegalArgumentException]. Amounts may not be negative. Amounts are represented internally using a signed
@ -29,10 +30,11 @@ interface TokenizableAssetInfo {
* multiplication are overflow checked and will throw [ArithmeticException] if the operation would have caused integer * multiplication are overflow checked and will throw [ArithmeticException] if the operation would have caused integer
* overflow. * overflow.
* *
* @property quantity the number of tokens as a Long value. * @property quantity the number of tokens as a long value.
* @property displayTokenSize the nominal display unit size of a single token, potentially with trailing decimal display places if the scale parameter is non-zero. * @property displayTokenSize the nominal display unit size of a single token, potentially with trailing decimal display
* @property token an instance of type T, usually a singleton. * places if the scale parameter is non-zero.
* @param T the type of the token, for example [Currency]. T should implement TokenizableAssetInfo if automatic conversion to/from a display format is required. * @property token the type of token this is an amount of. This is usually a singleton.
* @param T the type of the token, for example [Currency]. T should implement [TokenizableAssetInfo] if automatic conversion to/from a display format is required.
*/ */
@CordaSerializable @CordaSerializable
data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal, val token: T) : Comparable<Amount<T>> { data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal, val token: T) : Comparable<Amount<T>> {
@ -40,11 +42,10 @@ data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal,
companion object { companion object {
/** /**
* Build an Amount from a decimal representation. For example, with an input of "12.34 GBP", * Build an Amount from a decimal representation. For example, with an input of "12.34 GBP",
* returns an amount with a quantity of "1234" tokens. The displayTokenSize as determined via * returns an amount with a quantity of "1234" tokens. The function [getDisplayTokenSize] is used to determine the
* getDisplayTokenSize is used to determine the conversion scaling. * conversion scaling, for example bonds might be in nominal amounts of 100, currencies in 0.01 penny units.
* e.g. Bonds might be in nominal amounts of 100, currencies in 0.01 penny units.
* *
* @see Amount<Currency>.toDecimal * @see Amount.toDecimal
* @throws ArithmeticException if the intermediate calculations cannot be converted to an unsigned 63-bit token amount. * @throws ArithmeticException if the intermediate calculations cannot be converted to an unsigned 63-bit token amount.
*/ */
@JvmStatic @JvmStatic
@ -166,7 +167,7 @@ data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal,
} }
} }
} }
} catch(e: Exception) { } catch (e: Exception) {
throw IllegalArgumentException("Could not parse $input as a currency", e) throw IllegalArgumentException("Could not parse $input as a currency", e)
} }
throw IllegalArgumentException("Did not recognise the currency in $input or could not parse") throw IllegalArgumentException("Did not recognise the currency in $input or could not parse")
@ -195,6 +196,7 @@ data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal,
* Mixing non-identical token types will throw [IllegalArgumentException]. * Mixing non-identical token types will throw [IllegalArgumentException].
* *
* @throws ArithmeticException if there is overflow of Amount tokens during the summation * @throws ArithmeticException if there is overflow of Amount tokens during the summation
* @throws IllegalArgumentException if mixing non-identical token types.
*/ */
operator fun plus(other: Amount<T>): Amount<T> { operator fun plus(other: Amount<T>): Amount<T> {
checkToken(other) checkToken(other)
@ -202,11 +204,11 @@ data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal,
} }
/** /**
* A checked addition operator is supported to simplify netting of Amounts. * A checked subtraction operator is supported to simplify netting of Amounts.
* If this leads to the Amount going negative this will throw [IllegalArgumentException].
* Mixing non-identical token types will throw [IllegalArgumentException].
* *
* @throws ArithmeticException if there is Numeric underflow * @throws ArithmeticException if there is numeric underflow.
* @throws IllegalArgumentException if this leads to the amount going negative, or would mix non-identical token
* types.
*/ */
operator fun minus(other: Amount<T>): Amount<T> { operator fun minus(other: Amount<T>): Amount<T> {
checkToken(other) checkToken(other)
@ -222,6 +224,8 @@ data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal,
* The multiplication operator is supported to allow easy calculation for multiples of a primitive Amount. * The multiplication operator is supported to allow easy calculation for multiples of a primitive Amount.
* Note this is not a conserving operation, so it may not always be correct modelling of proper token behaviour. * Note this is not a conserving operation, so it may not always be correct modelling of proper token behaviour.
* N.B. Division is not supported as fractional tokens are not representable by an Amount. * N.B. Division is not supported as fractional tokens are not representable by an Amount.
*
* @throws ArithmeticException if there is overflow of Amount tokens during the multiplication.
*/ */
operator fun times(other: Long): Amount<T> = Amount(Math.multiplyExact(quantity, other), displayTokenSize, token) operator fun times(other: Long): Amount<T> = Amount(Math.multiplyExact(quantity, other), displayTokenSize, token)
@ -229,13 +233,15 @@ data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal,
* The multiplication operator is supported to allow easy calculation for multiples of a primitive Amount. * The multiplication operator is supported to allow easy calculation for multiples of a primitive Amount.
* Note this is not a conserving operation, so it may not always be correct modelling of proper token behaviour. * Note this is not a conserving operation, so it may not always be correct modelling of proper token behaviour.
* N.B. Division is not supported as fractional tokens are not representable by an Amount. * N.B. Division is not supported as fractional tokens are not representable by an Amount.
*
* @throws ArithmeticException if there is overflow of Amount tokens during the multiplication.
*/ */
operator fun times(other: Int): Amount<T> = Amount(Math.multiplyExact(quantity, other.toLong()), displayTokenSize, token) operator fun times(other: Int): Amount<T> = Amount(Math.multiplyExact(quantity, other.toLong()), displayTokenSize, token)
/** /**
* This method provides a token conserving divide mechanism. * This method provides a token conserving divide mechanism.
* @param partitions the number of amounts to divide the current quantity into. * @param partitions the number of amounts to divide the current quantity into.
* @result Returns [partitions] separate Amount objects which sum to the same quantity as this Amount * @return 'partitions' separate Amount objects which sum to the same quantity as this Amount
* and differ by no more than a single token in size. * and differ by no more than a single token in size.
*/ */
fun splitEvenly(partitions: Int): List<Amount<T>> { fun splitEvenly(partitions: Int): List<Amount<T>> {
@ -249,8 +255,10 @@ data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal,
/** /**
* Convert a currency [Amount] to a decimal representation. For example, with an amount with a quantity * Convert a currency [Amount] to a decimal representation. For example, with an amount with a quantity
* of "1234" GBP, returns "12.34". The precise representation is controlled by the displayTokenSize, * of "1234" GBP, returns "12.34". The precise representation is controlled by the display token size (
* which determines the size of a single token and controls the trailing decimal places via it's scale property. * from [getDisplayTokenSize]), which determines the size of a single token and controls the trailing decimal
* places via its scale property. *Note* that currencies such as the Bahraini Dinar use 3 decimal places,
* and it must not be presumed that this converts amounts to 2 decimal places.
* *
* @see Amount.fromDecimal * @see Amount.fromDecimal
*/ */

View File

@ -4,6 +4,7 @@ package net.corda.core.contracts
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.uncheckedCast
import java.security.PublicKey import java.security.PublicKey
import java.util.* import java.util.*
@ -33,7 +34,7 @@ inline fun <R> requireThat(body: Requirements.() -> R) = Requirements.body()
/** Filters the command list by type, party and public key all at once. */ /** Filters the command list by type, party and public key all at once. */
inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>.select(signer: PublicKey? = null, inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>.select(signer: PublicKey? = null,
party: AbstractParty? = null) = party: AbstractParty? = null) =
filter { it.value is T }. filter { it.value is T }.
filter { if (signer == null) true else signer in it.signers }. filter { if (signer == null) true else signer in it.signers }.
filter { if (party == null) true else party in it.signingParties }. filter { if (party == null) true else party in it.signingParties }.
@ -43,7 +44,7 @@ inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>
/** Filters the command list by type, parties and public keys all at once. */ /** Filters the command list by type, parties and public keys all at once. */
inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>.select(signers: Collection<PublicKey>?, inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>.select(signers: Collection<PublicKey>?,
parties: Collection<Party>?) = parties: Collection<Party>?) =
filter { it.value is T }. filter { it.value is T }.
filter { if (signers == null) true else it.signers.containsAll(signers) }. filter { if (signers == null) true else it.signers.containsAll(signers) }.
filter { if (parties == null) true else it.signingParties.containsAll(parties) }. filter { if (parties == null) true else it.signingParties.containsAll(parties) }.
@ -58,7 +59,7 @@ inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>
/** Ensures that a transaction has only one command that is of the given type, otherwise throws an exception. */ /** Ensures that a transaction has only one command that is of the given type, otherwise throws an exception. */
fun <C : CommandData> Collection<CommandWithParties<CommandData>>.requireSingleCommand(klass: Class<C>) = fun <C : CommandData> Collection<CommandWithParties<CommandData>>.requireSingleCommand(klass: Class<C>) =
mapNotNull { @Suppress("UNCHECKED_CAST") if (klass.isInstance(it.value)) it as CommandWithParties<C> else null }.single() mapNotNull { if (klass.isInstance(it.value)) uncheckedCast<CommandWithParties<CommandData>, CommandWithParties<C>>(it) else null }.single()
/** /**
* Simple functionality for verifying a move command. Verifies that each input has a signature from its owning key. * Simple functionality for verifying a move command. Verifies that each input has a signature from its owning key.

View File

@ -199,9 +199,6 @@ interface MoveCommand : CommandData {
val contract: Class<out Contract>? val contract: Class<out Contract>?
} }
/** Indicates that this transaction replaces the inputs contract state to another contract state */
data class UpgradeCommand(val upgradedContractClass: ContractClassName) : CommandData
// DOCSTART 6 // DOCSTART 6
/** A [Command] where the signing parties have been looked up if they have a well known/recognised institutional key. */ /** A [Command] where the signing parties have been looked up if they have a well known/recognised institutional key. */
@CordaSerializable @CordaSerializable

View File

@ -64,6 +64,17 @@ abstract class TimeWindow {
*/ */
abstract val midpoint: Instant? abstract val midpoint: Instant?
/**
* Returns the duration between [fromTime] and [untilTime] if both are non-null. Otherwise returns null.
*/
val length: Duration? get() {
return if (fromTime == null || untilTime == null) {
null
} else {
Duration.between(fromTime, untilTime)
}
}
/** Returns true iff the given [instant] is within the time interval of this [TimeWindow]. */ /** Returns true iff the given [instant] is within the time interval of this [TimeWindow]. */
abstract operator fun contains(instant: Instant): Boolean abstract operator fun contains(instant: Instant): Boolean
@ -85,6 +96,7 @@ abstract class TimeWindow {
init { init {
require(fromTime < untilTime) { "fromTime must be earlier than untilTime" } require(fromTime < untilTime) { "fromTime must be earlier than untilTime" }
} }
override val midpoint: Instant get() = fromTime + (fromTime until untilTime) / 2 override val midpoint: Instant get() = fromTime + (fromTime until untilTime) / 2
override fun contains(instant: Instant): Boolean = instant >= fromTime && instant < untilTime override fun contains(instant: Instant): Boolean = instant >= fromTime && instant < untilTime
override fun toString(): String = "[$fromTime, $untilTime)" override fun toString(): String = "[$fromTime, $untilTime)"

View File

@ -1,8 +1,8 @@
package net.corda.core.cordapp package net.corda.core.cordapp
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SerializeAsToken
import java.net.URL import java.net.URL
@ -13,20 +13,26 @@ import java.net.URL
* *
* This will only need to be constructed manually for certain kinds of tests. * This will only need to be constructed manually for certain kinds of tests.
* *
* @property name Cordapp name - derived from the base name of the Cordapp JAR (therefore may not be unique)
* @property contractClassNames List of contracts * @property contractClassNames List of contracts
* @property initiatedFlows List of initiatable flow classes * @property initiatedFlows List of initiatable flow classes
* @property rpcFlows List of RPC initiable flows classes * @property rpcFlows List of RPC initiable flows classes
* @property serviceFlows List of [CordaService] initiable flows classes
* @property schedulableFlows List of flows startable by the scheduler
* @property servies List of RPC services * @property servies List of RPC services
* @property plugins List of Corda plugin registries * @property serializationWhitelists List of Corda plugin registries
* @property customSchemas List of custom schemas * @property customSchemas List of custom schemas
* @property jarPath The path to the JAR for this CorDapp * @property jarPath The path to the JAR for this CorDapp
*/ */
interface Cordapp { interface Cordapp {
val name: String
val contractClassNames: List<String> val contractClassNames: List<String>
val initiatedFlows: List<Class<out FlowLogic<*>>> val initiatedFlows: List<Class<out FlowLogic<*>>>
val rpcFlows: List<Class<out FlowLogic<*>>> val rpcFlows: List<Class<out FlowLogic<*>>>
val serviceFlows: List<Class<out FlowLogic<*>>>
val schedulableFlows: List<Class<out FlowLogic<*>>>
val services: List<Class<out SerializeAsToken>> val services: List<Class<out SerializeAsToken>>
val plugins: List<CordaPluginRegistry> val serializationWhitelists: List<SerializationWhitelist>
val customSchemas: Set<MappedSchema> val customSchemas: Set<MappedSchema>
val jarPath: URL val jarPath: URL
val cordappClasses: List<String> val cordappClasses: List<String>

View File

@ -1,6 +1,5 @@
package net.corda.core.crypto package net.corda.core.crypto
import net.corda.core.crypto.composite.CompositeSignaturesWithKeys
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.security.* import java.security.*

View File

@ -1,6 +1,5 @@
package net.corda.core.crypto.composite package net.corda.core.crypto
import net.corda.core.crypto.TransactionSignature
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
/** /**

View File

@ -31,6 +31,8 @@ class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME secur
object CordaObjectIdentifier { object CordaObjectIdentifier {
// UUID-based OID // UUID-based OID
// TODO: Register for an OID space and issue our own shorter OID. // TODO: Register for an OID space and issue our own shorter OID.
@JvmField val COMPOSITE_KEY = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791002") @JvmField
@JvmField val COMPOSITE_SIGNATURE = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791003") val COMPOSITE_KEY = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791002")
@JvmField
val COMPOSITE_SIGNATURE = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791003")
} }

View File

@ -2,7 +2,10 @@ package net.corda.core.crypto
import net.corda.core.internal.X509EdDSAEngine import net.corda.core.internal.X509EdDSAEngine
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.i2p.crypto.eddsa.* import net.i2p.crypto.eddsa.EdDSAEngine
import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey
import net.i2p.crypto.eddsa.EdDSASecurityProvider
import net.i2p.crypto.eddsa.math.GroupElement import net.i2p.crypto.eddsa.math.GroupElement
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
@ -39,8 +42,6 @@ import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey
import org.bouncycastle.pqc.jcajce.spec.SPHINCS256KeyGenParameterSpec import org.bouncycastle.pqc.jcajce.spec.SPHINCS256KeyGenParameterSpec
import java.math.BigInteger import java.math.BigInteger
import java.security.* import java.security.*
import java.security.KeyFactory
import java.security.KeyPairGenerator
import java.security.spec.InvalidKeySpecException import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec import java.security.spec.X509EncodedKeySpec
@ -148,7 +149,7 @@ object Crypto {
"at the cost of larger key sizes and loss of compatibility." "at the cost of larger key sizes and loss of compatibility."
) )
/** Corda composite key type */ /** Corda composite key type. */
@JvmField @JvmField
val COMPOSITE_KEY = SignatureScheme( val COMPOSITE_KEY = SignatureScheme(
6, 6,
@ -774,9 +775,10 @@ object Crypto {
// it forms, by itself, the new private key, which in turn is used to compute the new public key. // it forms, by itself, the new private key, which in turn is used to compute the new public key.
val pointQ = FixedPointCombMultiplier().multiply(parameterSpec.g, deterministicD) val pointQ = FixedPointCombMultiplier().multiply(parameterSpec.g, deterministicD)
// This is unlikely to happen, but we should check for point at infinity. // This is unlikely to happen, but we should check for point at infinity.
if (pointQ.isInfinity) if (pointQ.isInfinity) {
// Instead of throwing an exception, we retry with SHA256(seed). // Instead of throwing an exception, we retry with SHA256(seed).
return deriveKeyPairECDSA(parameterSpec, privateKey, seed.sha256().bytes) return deriveKeyPairECDSA(parameterSpec, privateKey, seed.sha256().bytes)
}
val publicKeySpec = ECPublicKeySpec(pointQ, parameterSpec) val publicKeySpec = ECPublicKeySpec(pointQ, parameterSpec)
val publicKeyD = BCECPublicKey(privateKey.algorithm, publicKeySpec, BouncyCastleProvider.CONFIGURATION) val publicKeyD = BCECPublicKey(privateKey.algorithm, publicKeySpec, BouncyCastleProvider.CONFIGURATION)
@ -822,7 +824,7 @@ object Crypto {
@JvmStatic @JvmStatic
fun deriveKeyPairFromEntropy(entropy: BigInteger): KeyPair = deriveKeyPairFromEntropy(DEFAULT_SIGNATURE_SCHEME, entropy) fun deriveKeyPairFromEntropy(entropy: BigInteger): KeyPair = deriveKeyPairFromEntropy(DEFAULT_SIGNATURE_SCHEME, entropy)
// custom key pair generator from entropy. // Custom key pair generator from entropy.
private fun deriveEdDSAKeyPairFromEntropy(entropy: BigInteger): KeyPair { private fun deriveEdDSAKeyPairFromEntropy(entropy: BigInteger): KeyPair {
val params = EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec val params = EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec
val bytes = entropy.toByteArray().copyOf(params.curve.field.getb() / 8) // Need to pad the entropy to the valid seed length. val bytes = entropy.toByteArray().copyOf(params.curve.field.getb() / 8) // Need to pad the entropy to the valid seed length.
@ -849,6 +851,7 @@ object Crypto {
override fun generatePublic(keyInfo: SubjectPublicKeyInfo?): PublicKey? { override fun generatePublic(keyInfo: SubjectPublicKeyInfo?): PublicKey? {
return keyInfo?.let { decodePublicKey(signatureScheme, it.encoded) } return keyInfo?.let { decodePublicKey(signatureScheme, it.encoded) }
} }
override fun generatePrivate(keyInfo: PrivateKeyInfo?): PrivateKey? { override fun generatePrivate(keyInfo: PrivateKeyInfo?): PrivateKey? {
return keyInfo?.let { decodePrivateKey(signatureScheme, it.encoded) } return keyInfo?.let { decodePrivateKey(signatureScheme, it.encoded) }
} }
@ -880,7 +883,7 @@ object Crypto {
} }
} }
// return true if EdDSA publicKey is point at infinity. // Return true if EdDSA publicKey is point at infinity.
// For EdDSA a custom function is required as it is not supported by the I2P implementation. // For EdDSA a custom function is required as it is not supported by the I2P implementation.
private fun isEdDSAPointAtInfinity(publicKey: EdDSAPublicKey): Boolean { private fun isEdDSAPointAtInfinity(publicKey: EdDSAPublicKey): Boolean {
return publicKey.a.toP3() == (EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec).curve.getZero(GroupElement.Representation.P3) return publicKey.a.toP3() == (EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec).curve.getZero(GroupElement.Representation.P3)
@ -892,7 +895,7 @@ object Crypto {
return signatureScheme.schemeCodeName in signatureSchemeMap return signatureScheme.schemeCodeName in signatureSchemeMap
} }
// validate a key, by checking its algorithmic params. // Validate a key, by checking its algorithmic params.
private fun validateKey(signatureScheme: SignatureScheme, key: Key): Boolean { private fun validateKey(signatureScheme: SignatureScheme, key: Key): Boolean {
return when (key) { return when (key) {
is PublicKey -> validatePublicKey(signatureScheme, key) is PublicKey -> validatePublicKey(signatureScheme, key)
@ -901,7 +904,7 @@ object Crypto {
} }
} }
// check if a public key satisfies algorithm specs (for ECC: key should lie on the curve and not being point-at-infinity). // Check if a public key satisfies algorithm specs (for ECC: key should lie on the curve and not being point-at-infinity).
private fun validatePublicKey(signatureScheme: SignatureScheme, key: PublicKey): Boolean { private fun validatePublicKey(signatureScheme: SignatureScheme, key: PublicKey): Boolean {
return when (key) { return when (key) {
is BCECPublicKey, is EdDSAPublicKey -> publicKeyOnCurve(signatureScheme, key) is BCECPublicKey, is EdDSAPublicKey -> publicKeyOnCurve(signatureScheme, key)
@ -910,7 +913,7 @@ object Crypto {
} }
} }
// check if a private key satisfies algorithm specs. // Check if a private key satisfies algorithm specs.
private fun validatePrivateKey(signatureScheme: SignatureScheme, key: PrivateKey): Boolean { private fun validatePrivateKey(signatureScheme: SignatureScheme, key: PrivateKey): Boolean {
return when (key) { return when (key) {
is BCECPrivateKey -> key.parameters == signatureScheme.algSpec is BCECPrivateKey -> key.parameters == signatureScheme.algSpec
@ -922,7 +925,6 @@ object Crypto {
/** /**
* Convert a public key to a supported implementation. * Convert a public key to a supported implementation.
*
* @param key a public key. * @param key a public key.
* @return a supported implementation of the input public key. * @return a supported implementation of the input public key.
* @throws IllegalArgumentException on not supported scheme or if the given key specification * @throws IllegalArgumentException on not supported scheme or if the given key specification
@ -941,7 +943,16 @@ object Crypto {
* is inappropriate for a supported key factory to produce a private key. * is inappropriate for a supported key factory to produce a private key.
*/ */
@JvmStatic @JvmStatic
fun toSupportedPublicKey(key: PublicKey): PublicKey = decodePublicKey(key.encoded) fun toSupportedPublicKey(key: PublicKey): PublicKey {
return when (key) {
is BCECPublicKey -> key
is BCRSAPublicKey -> key
is BCSphincs256PublicKey -> key
is EdDSAPublicKey -> key
is CompositeKey -> key
else -> decodePublicKey(key.encoded)
}
}
/** /**
* Convert a private key to a supported implementation. This can be used to convert a SUN's EC key to an BC key. * Convert a private key to a supported implementation. This can be used to convert a SUN's EC key to an BC key.
@ -952,5 +963,13 @@ object Crypto {
* is inappropriate for a supported key factory to produce a private key. * is inappropriate for a supported key factory to produce a private key.
*/ */
@JvmStatic @JvmStatic
fun toSupportedPrivateKey(key: PrivateKey): PrivateKey = decodePrivateKey(key.encoded) fun toSupportedPrivateKey(key: PrivateKey): PrivateKey {
return when (key) {
is BCECPrivateKey -> key
is BCRSAPrivateKey -> key
is BCSphincs256PrivateKey -> key
is EdDSAPrivateKey -> key
else -> decodePrivateKey(key.encoded)
}
}
} }

View File

@ -21,9 +21,19 @@ import java.security.*
* @throws InvalidKeyException if the private key is invalid. * @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key. * @throws SignatureException if signing is not possible due to malformed data or private key.
*/ */
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class) @Throws(InvalidKeyException::class, SignatureException::class)
fun PrivateKey.sign(bytesToSign: ByteArray): DigitalSignature = DigitalSignature(Crypto.doSign(this, bytesToSign)) fun PrivateKey.sign(bytesToSign: ByteArray): DigitalSignature = DigitalSignature(Crypto.doSign(this, bytesToSign))
/**
* Utility to simplify the act of signing a byte array and return a [DigitalSignature.WithKey] object.
* Note that there is no check if the public key matches with the signing private key.
* @param bytesToSign the data/message to be signed in [ByteArray] form (usually the Merkle root).
* @return the [DigitalSignature.WithKey] object on the input message [bytesToSign] and [publicKey].
* @throws IllegalArgumentException if the signature scheme is not supported for this private key.
* @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(InvalidKeyException::class, SignatureException::class)
fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey) = DigitalSignature.WithKey(publicKey, this.sign(bytesToSign).bytes) fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey) = DigitalSignature.WithKey(publicKey, this.sign(bytesToSign).bytes)
/** /**
@ -34,9 +44,13 @@ fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey) = DigitalSigna
* @throws InvalidKeyException if the private key is invalid. * @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key. * @throws SignatureException if signing is not possible due to malformed data or private key.
*/ */
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class) @Throws(InvalidKeyException::class, SignatureException::class)
fun KeyPair.sign(bytesToSign: ByteArray) = private.sign(bytesToSign, public) fun KeyPair.sign(bytesToSign: ByteArray) = private.sign(bytesToSign, public)
/** Helper function to sign the bytes of [bytesToSign] with a key pair. */
@Throws(InvalidKeyException::class, SignatureException::class)
fun KeyPair.sign(bytesToSign: OpaqueBytes) = sign(bytesToSign.bytes) fun KeyPair.sign(bytesToSign: OpaqueBytes) = sign(bytesToSign.bytes)
/** /**
* Helper function for signing a [SignableData] object. * Helper function for signing a [SignableData] object.
* @param signableData the object to be signed. * @param signableData the object to be signed.
@ -56,8 +70,8 @@ fun KeyPair.sign(signableData: SignableData): TransactionSignature = Crypto.doSi
* @throws SignatureException if the signature is invalid (i.e. damaged), or does not match the key (incorrect). * @throws SignatureException if the signature is invalid (i.e. damaged), or does not match the key (incorrect).
* @throws IllegalArgumentException if the signature scheme is not supported or if any of the clear or signature data is empty. * @throws IllegalArgumentException if the signature scheme is not supported or if any of the clear or signature data is empty.
*/ */
// TODO: SignatureException should be used only for a damaged signature, as per `java.security.Signature.verify()`, // TODO: SignatureException should be used only for a damaged signature, as per `java.security.Signature.verify()`.
@Throws(SignatureException::class, IllegalArgumentException::class, InvalidKeyException::class) @Throws(SignatureException::class, InvalidKeyException::class)
fun PublicKey.verify(content: ByteArray, signature: DigitalSignature) = Crypto.doVerify(this, signature.bytes, content) fun PublicKey.verify(content: ByteArray, signature: DigitalSignature) = Crypto.doVerify(this, signature.bytes, content)
/** /**
@ -70,21 +84,25 @@ fun PublicKey.verify(content: ByteArray, signature: DigitalSignature) = Crypto.d
* signature). * signature).
* @throws SignatureException if the signature is invalid (i.e. damaged). * @throws SignatureException if the signature is invalid (i.e. damaged).
* @throws IllegalArgumentException if the signature scheme is not supported or if any of the clear or signature data is empty. * @throws IllegalArgumentException if the signature scheme is not supported or if any of the clear or signature data is empty.
* @throws IllegalStateException if this is a [CompositeKey], because verification of composite key signatures is not supported.
* @return whether the signature is correct for this key. * @return whether the signature is correct for this key.
*/ */
@Throws(IllegalStateException::class, SignatureException::class, IllegalArgumentException::class) @Throws(SignatureException::class, InvalidKeyException::class)
fun PublicKey.isValid(content: ByteArray, signature: DigitalSignature) : Boolean { fun PublicKey.isValid(content: ByteArray, signature: DigitalSignature): Boolean {
if (this is CompositeKey) if (this is CompositeKey)
throw IllegalStateException("Verification of CompositeKey signatures currently not supported.") // TODO CompositeSignature verification. throw IllegalStateException("Verification of CompositeKey signatures currently not supported.") // TODO CompositeSignature verification.
return Crypto.isValid(this, signature.bytes, content) return Crypto.isValid(this, signature.bytes, content)
} }
/** Render a public key to its hash (in Base58) of its serialised form using the DL prefix. */ /** Render a public key to its hash (in Base58) of its serialised form using the DL prefix. */
fun PublicKey.toStringShort(): String = "DL" + this.toSHA256Bytes().toBase58() fun PublicKey.toStringShort(): String = "DL" + this.toSHA256Bytes().toBase58()
/** Return a [Set] of the contained keys if this is a [CompositeKey]; otherwise, return a [Set] with a single element (this [PublicKey]). */
val PublicKey.keys: Set<PublicKey> get() = (this as? CompositeKey)?.leafKeys ?: setOf(this) val PublicKey.keys: Set<PublicKey> get() = (this as? CompositeKey)?.leafKeys ?: setOf(this)
fun PublicKey.isFulfilledBy(otherKey: PublicKey): Boolean = isFulfilledBy(setOf(otherKey)) /** Return true if [otherKey] fulfils the requirements of this [PublicKey]. */
fun PublicKey.isFulfilledBy(otherKey: PublicKey): Boolean = isFulfilledBy(setOf(otherKey))
/** Return true if [otherKeys] fulfil the requirements of this [PublicKey]. */
fun PublicKey.isFulfilledBy(otherKeys: Iterable<PublicKey>): Boolean = (this as? CompositeKey)?.isFulfilledBy(otherKeys) ?: (this in otherKeys) fun PublicKey.isFulfilledBy(otherKeys: Iterable<PublicKey>): Boolean = (this as? CompositeKey)?.isFulfilledBy(otherKeys) ?: (this in otherKeys)
/** Checks whether any of the given [keys] matches a leaf on the [CompositeKey] tree or a single [PublicKey]. */ /** Checks whether any of the given [keys] matches a leaf on the [CompositeKey] tree or a single [PublicKey]. */
@ -98,8 +116,9 @@ fun Iterable<TransactionSignature>.byKeys() = map { it.by }.toSet()
// Allow Kotlin destructuring: // Allow Kotlin destructuring:
// val (private, public) = keyPair // val (private, public) = keyPair
/* The [PrivateKey] of this [KeyPair] .*/
operator fun KeyPair.component1(): PrivateKey = this.private operator fun KeyPair.component1(): PrivateKey = this.private
/* The [PublicKey] of this [KeyPair] .*/
operator fun KeyPair.component2(): PublicKey = this.public operator fun KeyPair.component2(): PublicKey = this.public
/** A simple wrapper that will make it easier to swap out the EC algorithm we use in future. */ /** A simple wrapper that will make it easier to swap out the EC algorithm we use in future. */
@ -122,7 +141,7 @@ fun entropyToKeyPair(entropy: BigInteger): KeyPair = Crypto.deriveKeyPairFromEnt
* if this signatureData algorithm is unable to process the input data provided, etc. * if this signatureData algorithm is unable to process the input data provided, etc.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key or if any of the clear or signature data is empty. * @throws IllegalArgumentException if the signature scheme is not supported for this private key or if any of the clear or signature data is empty.
*/ */
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class) @Throws(InvalidKeyException::class, SignatureException::class)
fun PublicKey.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Crypto.doVerify(this, signatureData, clearData) fun PublicKey.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Crypto.doVerify(this, signatureData, clearData)
/** /**
@ -135,7 +154,7 @@ fun PublicKey.verify(signatureData: ByteArray, clearData: ByteArray): Boolean =
* if this signatureData algorithm is unable to process the input data provided, etc. * if this signatureData algorithm is unable to process the input data provided, etc.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key or if any of the clear or signature data is empty. * @throws IllegalArgumentException if the signature scheme is not supported for this private key or if any of the clear or signature data is empty.
*/ */
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class) @Throws(InvalidKeyException::class, SignatureException::class)
fun KeyPair.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Crypto.doVerify(this.public, signatureData, clearData) fun KeyPair.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Crypto.doVerify(this.public, signatureData, clearData)
/** /**

View File

@ -23,6 +23,7 @@ open class DigitalSignature(bytes: ByteArray) : OpaqueBytes(bytes) {
*/ */
@Throws(InvalidKeyException::class, SignatureException::class) @Throws(InvalidKeyException::class, SignatureException::class)
fun verify(content: ByteArray) = by.verify(content, this) fun verify(content: ByteArray) = by.verify(content, this)
/** /**
* Utility to simplify the act of verifying a signature. * Utility to simplify the act of verifying a signature.
* *
@ -32,6 +33,7 @@ open class DigitalSignature(bytes: ByteArray) : OpaqueBytes(bytes) {
*/ */
@Throws(InvalidKeyException::class, SignatureException::class) @Throws(InvalidKeyException::class, SignatureException::class)
fun verify(content: OpaqueBytes) = by.verify(content.bytes, this) fun verify(content: OpaqueBytes) = by.verify(content.bytes, this)
/** /**
* Utility to simplify the act of verifying a signature. In comparison to [verify] doesn't throw an * Utility to simplify the act of verifying a signature. In comparison to [verify] doesn't throw an
* exception, making it more suitable where a boolean is required, but normally you should use the function * exception, making it more suitable where a boolean is required, but normally you should use the function
@ -44,5 +46,6 @@ open class DigitalSignature(bytes: ByteArray) : OpaqueBytes(bytes) {
*/ */
@Throws(InvalidKeyException::class, SignatureException::class) @Throws(InvalidKeyException::class, SignatureException::class)
fun isValid(content: ByteArray) = by.isValid(content, this) fun isValid(content: ByteArray) = by.isValid(content, this)
fun withoutKey() : DigitalSignature = DigitalSignature(this.bytes)
} }
} }

View File

@ -1,11 +1,12 @@
package net.corda.core.crypto package net.corda.core.crypto
import net.corda.core.CordaException
import net.corda.core.crypto.SecureHash.Companion.zeroHash import net.corda.core.crypto.SecureHash.Companion.zeroHash
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import java.util.* import java.util.*
@CordaSerializable @CordaSerializable
class MerkleTreeException(val reason: String) : Exception("Partial Merkle Tree exception. Reason: $reason") class MerkleTreeException(val reason: String) : CordaException("Partial Merkle Tree exception. Reason: $reason")
/** /**
* Building and verification of Partial Merkle Tree. * Building and verification of Partial Merkle Tree.

View File

@ -19,32 +19,87 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
} }
} }
/**
* Convert the hash value to an uppercase hexadecimal [String].
*/
override fun toString(): String = bytes.toHexString() override fun toString(): String = bytes.toHexString()
/**
* Returns the first [prefixLen] hexadecimal digits of the [SecureHash] value.
* @param prefixLen The number of characters in the prefix.
*/
fun prefixChars(prefixLen: Int = 6) = toString().substring(0, prefixLen) fun prefixChars(prefixLen: Int = 6) = toString().substring(0, prefixLen)
/**
* Append a second hash value to this hash value, and then compute the SHA-256 hash of the result.
* @param other The hash to append to this one.
*/
fun hashConcat(other: SecureHash) = (this.bytes + other.bytes).sha256() fun hashConcat(other: SecureHash) = (this.bytes + other.bytes).sha256()
// Like static methods in Java, except the 'companion' is a singleton that can have state. // Like static methods in Java, except the 'companion' is a singleton that can have state.
companion object { companion object {
/**
* Converts a SHA-256 hash value represented as a hexadecimal [String] into a [SecureHash].
* @param str A sequence of 64 hexadecimal digits that represents a SHA-256 hash value.
* @throws IllegalArgumentException The input string does not contain 64 hexadecimal digits, or it contains incorrectly-encoded characters.
*/
@JvmStatic @JvmStatic
fun parse(str: String) = str.toUpperCase().parseAsHex().let { fun parse(str: String): SHA256 {
when (it.size) { return str.toUpperCase().parseAsHex().let {
32 -> SHA256(it) when (it.size) {
else -> throw IllegalArgumentException("Provided string is ${it.size} bytes not 32 bytes in hex: $str") 32 -> SHA256(it)
else -> throw IllegalArgumentException("Provided string is ${it.size} bytes not 32 bytes in hex: $str")
}
} }
} }
@JvmStatic fun sha256(bytes: ByteArray) = SHA256(MessageDigest.getInstance("SHA-256").digest(bytes)) /**
@JvmStatic fun sha256Twice(bytes: ByteArray) = sha256(sha256(bytes).bytes) * Computes the SHA-256 hash value of the [ByteArray].
@JvmStatic fun sha256(str: String) = sha256(str.toByteArray()) * @param bytes The [ByteArray] to hash.
*/
@JvmStatic
fun sha256(bytes: ByteArray) = SHA256(MessageDigest.getInstance("SHA-256").digest(bytes))
@JvmStatic fun randomSHA256() = sha256(newSecureRandom().generateSeed(32)) /**
* Computes the SHA-256 hash of the [ByteArray], and then computes the SHA-256 hash of the hash.
* @param bytes The [ByteArray] to hash.
*/
@JvmStatic
fun sha256Twice(bytes: ByteArray) = sha256(sha256(bytes).bytes)
/**
* Computes the SHA-256 hash of the [String]'s UTF-8 byte contents.
* @param str [String] whose UTF-8 contents will be hashed.
*/
@JvmStatic
fun sha256(str: String) = sha256(str.toByteArray())
/**
* Generates a random SHA-256 value.
*/
@JvmStatic
fun randomSHA256() = sha256(newSecureRandom().generateSeed(32))
/**
* A SHA-256 hash value consisting of 32 0x00 bytes.
*/
val zeroHash = SecureHash.SHA256(ByteArray(32, { 0.toByte() })) val zeroHash = SecureHash.SHA256(ByteArray(32, { 0.toByte() }))
/**
* A SHA-256 hash value consisting of 32 0xFF bytes.
*/
val allOnesHash = SecureHash.SHA256(ByteArray(32, { 255.toByte() })) val allOnesHash = SecureHash.SHA256(ByteArray(32, { 255.toByte() }))
} }
// In future, maybe SHA3, truncated hashes etc. // In future, maybe SHA3, truncated hashes etc.
} }
/**
* Compute the SHA-256 hash for the contents of the [ByteArray].
*/
fun ByteArray.sha256(): SecureHash.SHA256 = SecureHash.sha256(this) fun ByteArray.sha256(): SecureHash.SHA256 = SecureHash.sha256(this)
/**
* Compute the SHA-256 hash for the contents of the [OpaqueBytes].
*/
fun OpaqueBytes.sha256(): SecureHash.SHA256 = SecureHash.sha256(this.bytes) fun OpaqueBytes.sha256(): SecureHash.SHA256 = SecureHash.sha256(this.bytes)

View File

@ -1,5 +1,6 @@
package net.corda.core.crypto package net.corda.core.crypto
import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
@ -23,8 +24,7 @@ open class SignedData<T : Any>(val raw: SerializedBytes<T>, val sig: DigitalSign
@Throws(SignatureException::class) @Throws(SignatureException::class)
fun verified(): T { fun verified(): T {
sig.by.verify(raw.bytes, sig) sig.by.verify(raw.bytes, sig)
@Suppress("UNCHECKED_CAST") val data: T = uncheckedCast(raw.deserialize<Any>())
val data = raw.deserialize<Any>() as T
verifyData(data) verifyData(data)
return data return data
} }

View File

@ -11,7 +11,7 @@ import java.util.*
* This is similar to [DigitalSignature.WithKey], but targeted to DLT transaction signatures. * This is similar to [DigitalSignature.WithKey], but targeted to DLT transaction signatures.
*/ */
@CordaSerializable @CordaSerializable
class TransactionSignature(bytes: ByteArray, val by: PublicKey, val signatureMetadata: SignatureMetadata): DigitalSignature(bytes) { class TransactionSignature(bytes: ByteArray, val by: PublicKey, val signatureMetadata: SignatureMetadata) : DigitalSignature(bytes) {
/** /**
* Function to verify a [SignableData] object's signature. * Function to verify a [SignableData] object's signature.
* Note that [SignableData] contains the id of the transaction and extra metadata, such as DLT's platform version. * Note that [SignableData] contains the id of the transaction and extra metadata, such as DLT's platform version.

View File

@ -70,15 +70,7 @@ abstract class AbstractStateReplacementFlow {
val finalTx = stx + signatures val finalTx = stx + signatures
serviceHub.recordTransactions(finalTx) serviceHub.recordTransactions(finalTx)
val newOutput = run { return stx.resolveBaseTransaction(serviceHub).outRef<T>(0)
if (stx.isNotaryChangeTransaction()) {
stx.resolveNotaryChangeTransaction(serviceHub).outRef<T>(0)
} else {
stx.tx.outRef<T>(0)
}
}
return newOutput
} }
/** /**
@ -136,7 +128,8 @@ abstract class AbstractStateReplacementFlow {
// We use Void? instead of Unit? as that's what you'd use in Java. // We use Void? instead of Unit? as that's what you'd use in Java.
abstract class Acceptor<in T>(val initiatingSession: FlowSession, abstract class Acceptor<in T>(val initiatingSession: FlowSession,
override val progressTracker: ProgressTracker = Acceptor.tracker()) : FlowLogic<Void?>() { override val progressTracker: ProgressTracker = Acceptor.tracker()) : FlowLogic<Void?>() {
constructor(initiatingSession: FlowSession) : this(initiatingSession, Acceptor.tracker()) constructor(initiatingSession: FlowSession) : this(initiatingSession, Acceptor.tracker())
companion object { companion object {
object VERIFYING : ProgressTracker.Step("Verifying state replacement proposal") object VERIFYING : ProgressTracker.Step("Verifying state replacement proposal")
object APPROVING : ProgressTracker.Step("State replacement approved") object APPROVING : ProgressTracker.Step("State replacement approved")
@ -173,11 +166,7 @@ abstract class AbstractStateReplacementFlow {
} }
val finalTx = stx + allSignatures val finalTx = stx + allSignatures
if (finalTx.isNotaryChangeTransaction()) { finalTx.resolveTransactionWithSignatures(serviceHub).verifyRequiredSignatures()
finalTx.resolveNotaryChangeTransaction(serviceHub).verifyRequiredSignatures()
} else {
finalTx.verifyRequiredSignatures()
}
serviceHub.recordTransactions(finalTx) serviceHub.recordTransactions(finalTx)
} }
@ -194,11 +183,7 @@ abstract class AbstractStateReplacementFlow {
// TODO Check the set of multiple identities? // TODO Check the set of multiple identities?
val myKey = ourIdentity.owningKey val myKey = ourIdentity.owningKey
val requiredKeys = if (stx.isNotaryChangeTransaction()) { val requiredKeys = stx.resolveTransactionWithSignatures(serviceHub).requiredSigningKeys
stx.resolveNotaryChangeTransaction(serviceHub).requiredSigningKeys
} else {
stx.tx.requiredSigningKeys
}
require(myKey in requiredKeys) { "Party is not a participant for any of the input states of transaction ${stx.id}" } require(myKey in requiredKeys) { "Party is not a participant for any of the input states of transaction ${stx.id}" }
} }

View File

@ -61,11 +61,12 @@ import java.security.PublicKey
* just in the states. If null, the default well known identity of the node is used. * just in the states. If null, the default well known identity of the node is used.
*/ */
// TODO: AbstractStateReplacementFlow needs updating to use this flow. // TODO: AbstractStateReplacementFlow needs updating to use this flow.
class CollectSignaturesFlow @JvmOverloads constructor (val partiallySignedTx: SignedTransaction, class CollectSignaturesFlow @JvmOverloads constructor(val partiallySignedTx: SignedTransaction,
val sessionsToCollectFrom: Collection<FlowSession>, val sessionsToCollectFrom: Collection<FlowSession>,
val myOptionalKeys: Iterable<PublicKey>?, val myOptionalKeys: Iterable<PublicKey>?,
override val progressTracker: ProgressTracker = CollectSignaturesFlow.tracker()) : FlowLogic<SignedTransaction>() { override val progressTracker: ProgressTracker = CollectSignaturesFlow.tracker()) : FlowLogic<SignedTransaction>() {
@JvmOverloads constructor(partiallySignedTx: SignedTransaction, sessionsToCollectFrom: Collection<FlowSession>, progressTracker: ProgressTracker = CollectSignaturesFlow.tracker()) : this(partiallySignedTx, sessionsToCollectFrom, null, progressTracker) @JvmOverloads constructor(partiallySignedTx: SignedTransaction, sessionsToCollectFrom: Collection<FlowSession>, progressTracker: ProgressTracker = CollectSignaturesFlow.tracker()) : this(partiallySignedTx, sessionsToCollectFrom, null, progressTracker)
companion object { companion object {
object COLLECTING : ProgressTracker.Step("Collecting signatures from counter-parties.") object COLLECTING : ProgressTracker.Step("Collecting signatures from counter-parties.")
object VERIFYING : ProgressTracker.Step("Verifying collected signatures.") object VERIFYING : ProgressTracker.Step("Verifying collected signatures.")
@ -134,6 +135,7 @@ class CollectSignaturesFlow @JvmOverloads constructor (val partiallySignedTx: Si
class CollectSignatureFlow(val partiallySignedTx: SignedTransaction, val session: FlowSession, val signingKeys: List<PublicKey>) : FlowLogic<List<TransactionSignature>>() { class CollectSignatureFlow(val partiallySignedTx: SignedTransaction, val session: FlowSession, val signingKeys: List<PublicKey>) : FlowLogic<List<TransactionSignature>>() {
constructor(partiallySignedTx: SignedTransaction, session: FlowSession, vararg signingKeys: PublicKey) : constructor(partiallySignedTx: SignedTransaction, session: FlowSession, vararg signingKeys: PublicKey) :
this(partiallySignedTx, session, listOf(*signingKeys)) this(partiallySignedTx, session, listOf(*signingKeys))
@Suspendable @Suspendable
override fun call(): List<TransactionSignature> { override fun call(): List<TransactionSignature> {
// SendTransactionFlow allows counterparty to access our data to resolve the transaction. // SendTransactionFlow allows counterparty to access our data to resolve the transaction.
@ -224,7 +226,7 @@ abstract class SignTransactionFlow(val otherSideSession: FlowSession,
// Perform some custom verification over the transaction. // Perform some custom verification over the transaction.
try { try {
checkTransaction(stx) checkTransaction(stx)
} catch(e: Exception) { } catch (e: Exception) {
if (e is IllegalStateException || e is IllegalArgumentException || e is AssertionError) if (e is IllegalStateException || e is IllegalArgumentException || e is AssertionError)
throw FlowException(e) throw FlowException(e)
else else

View File

@ -3,9 +3,6 @@ package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.internal.ContractUpgradeUtils import net.corda.core.internal.ContractUpgradeUtils
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import java.security.PublicKey
/** /**
* A flow to be used for authorising and upgrading state objects of an old contract to a new contract. * A flow to be used for authorising and upgrading state objects of an old contract to a new contract.
@ -16,30 +13,6 @@ import java.security.PublicKey
* use the new updated state for future transactions. * use the new updated state for future transactions.
*/ */
object ContractUpgradeFlow { object ContractUpgradeFlow {
@JvmStatic
fun verify(tx: LedgerTransaction) {
// Contract Upgrade transaction should have 1 input, 1 output and 1 command.
verify(tx.inputs.single().state,
tx.outputs.single(),
tx.commandsOfType<UpgradeCommand>().single())
}
@JvmStatic
fun verify(input: TransactionState<ContractState>, output: TransactionState<ContractState>, commandData: Command<UpgradeCommand>) {
val command = commandData.value
val participantKeys: Set<PublicKey> = input.data.participants.map { it.owningKey }.toSet()
val keysThatSigned: Set<PublicKey> = commandData.signers.toSet()
@Suppress("UNCHECKED_CAST")
val upgradedContract = javaClass.classLoader.loadClass(command.upgradedContractClass).newInstance() as UpgradedContract<ContractState, *>
requireThat {
"The signing keys include all participant keys" using keysThatSigned.containsAll(participantKeys)
"Inputs state reference the legacy contract" using (input.contract == upgradedContract.legacyContract)
"Outputs state reference the upgraded contract" using (output.contract == command.upgradedContractClass)
"Output state must be an upgraded version of the input state" using (output.data == upgradedContract.upgrade(input.data))
}
}
/** /**
* Authorise a contract state upgrade. * Authorise a contract state upgrade.
* *
@ -49,11 +22,13 @@ object ContractUpgradeFlow {
* *
* This flow will NOT initiate the upgrade process. To start the upgrade process, see [Initiate]. * This flow will NOT initiate the upgrade process. To start the upgrade process, see [Initiate].
*/ */
// DOCSTART 1
@StartableByRPC @StartableByRPC
class Authorise( class Authorise(
val stateAndRef: StateAndRef<*>, val stateAndRef: StateAndRef<*>,
private val upgradedContractClass: Class<out UpgradedContract<*, *>> private val upgradedContractClass: Class<out UpgradedContract<*, *>>
) : FlowLogic<Void?>() { ) : FlowLogic<Void?>() {
// DOCEND 1
@Suspendable @Suspendable
override fun call(): Void? { override fun call(): Void? {
val upgrade = upgradedContractClass.newInstance() val upgrade = upgradedContractClass.newInstance()
@ -70,10 +45,12 @@ object ContractUpgradeFlow {
* Deauthorise a contract state upgrade. * Deauthorise a contract state upgrade.
* This will remove the upgrade authorisation from persistent store (and prevent any further upgrade) * This will remove the upgrade authorisation from persistent store (and prevent any further upgrade)
*/ */
// DOCSTART 2
@StartableByRPC @StartableByRPC
class Deauthorise(val stateRef: StateRef) : FlowLogic<Void?>() { class Deauthorise(val stateRef: StateRef) : FlowLogic<Void?>() {
@Suspendable @Suspendable
override fun call(): Void? { override fun call(): Void? {
//DOCEND 2
serviceHub.contractUpgradeService.removeAuthorisedContractUpgrade(stateRef) serviceHub.contractUpgradeService.removeAuthorisedContractUpgrade(stateRef)
return null return null
} }
@ -89,23 +66,6 @@ object ContractUpgradeFlow {
newContractClass: Class<out UpgradedContract<OldState, NewState>> newContractClass: Class<out UpgradedContract<OldState, NewState>>
) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<out UpgradedContract<OldState, NewState>>>(originalState, newContractClass) { ) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<out UpgradedContract<OldState, NewState>>>(originalState, newContractClass) {
companion object {
fun <OldState : ContractState, NewState : ContractState> assembleBareTx(
stateRef: StateAndRef<OldState>,
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>,
privacySalt: PrivacySalt
): TransactionBuilder {
val contractUpgrade = upgradedContractClass.newInstance()
return TransactionBuilder(stateRef.state.notary)
.withItems(
stateRef,
StateAndContract(contractUpgrade.upgrade(stateRef.state.data), upgradedContractClass.name),
Command(UpgradeCommand(upgradedContractClass.name), stateRef.state.data.participants.map { it.owningKey }),
privacySalt
)
}
}
@Suspendable @Suspendable
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx { override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
val baseTx = ContractUpgradeUtils.assembleBareTx(originalState, modification, PrivacySalt()) val baseTx = ContractUpgradeUtils.assembleBareTx(originalState, modification, PrivacySalt())

View File

@ -26,8 +26,8 @@ import net.corda.core.utilities.ProgressTracker
*/ */
@InitiatingFlow @InitiatingFlow
class FinalityFlow(val transaction: SignedTransaction, class FinalityFlow(val transaction: SignedTransaction,
private val extraRecipients: Set<Party>, private val extraRecipients: Set<Party>,
override val progressTracker: ProgressTracker) : FlowLogic<SignedTransaction>() { override val progressTracker: ProgressTracker) : FlowLogic<SignedTransaction>() {
constructor(transaction: SignedTransaction, extraParticipants: Set<Party>) : this(transaction, extraParticipants, tracker()) constructor(transaction: SignedTransaction, extraParticipants: Set<Party>) : this(transaction, extraParticipants, tracker())
constructor(transaction: SignedTransaction) : this(transaction, emptySet(), tracker()) constructor(transaction: SignedTransaction) : this(transaction, emptySet(), tracker())
constructor(transaction: SignedTransaction, progressTracker: ProgressTracker) : this(transaction, emptySet(), progressTracker) constructor(transaction: SignedTransaction, progressTracker: ProgressTracker) : this(transaction, emptySet(), progressTracker)
@ -88,7 +88,7 @@ class FinalityFlow(val transaction: SignedTransaction,
private fun hasNoNotarySignature(stx: SignedTransaction): Boolean { private fun hasNoNotarySignature(stx: SignedTransaction): Boolean {
val notaryKey = stx.tx.notary?.owningKey val notaryKey = stx.tx.notary?.owningKey
val signers = stx.sigs.map { it.by }.toSet() val signers = stx.sigs.map { it.by }.toSet()
return !(notaryKey?.isFulfilledBy(signers) ?: false) return notaryKey?.isFulfilledBy(signers) != true
} }
private fun getPartiesToSend(ltx: LedgerTransaction): Set<Party> { private fun getPartiesToSend(ltx: LedgerTransaction): Set<Party> {

View File

@ -16,14 +16,22 @@ sealed class FlowInitiator : Principal {
data class RPC(val username: String) : FlowInitiator() { data class RPC(val username: String) : FlowInitiator() {
override fun getName(): String = username override fun getName(): String = username
} }
/** Started when we get new session initiation request. */ /** Started when we get new session initiation request. */
data class Peer(val party: Party) : FlowInitiator() { data class Peer(val party: Party) : FlowInitiator() {
override fun getName(): String = party.name.toString() override fun getName(): String = party.name.toString()
} }
/** Started by a CordaService. */
data class Service(val serviceClassName: String) : FlowInitiator() {
override fun getName(): String = serviceClassName
}
/** Started as scheduled activity. */ /** Started as scheduled activity. */
data class Scheduled(val scheduledState: ScheduledStateRef) : FlowInitiator() { data class Scheduled(val scheduledState: ScheduledStateRef) : FlowInitiator() {
override fun getName(): String = "Scheduler" override fun getName(): String = "Scheduler"
} }
// TODO When proper ssh access enabled, add username/use RPC? // TODO When proper ssh access enabled, add username/use RPC?
object Shell : FlowInitiator() { object Shell : FlowInitiator() {
override fun getName(): String = "Shell User" override fun getName(): String = "Shell User"

View File

@ -1,11 +1,13 @@
package net.corda.core.flows package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import co.paralleluniverse.strands.Strand
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.FlowStateMachine
import net.corda.core.internal.abbreviate import net.corda.core.internal.abbreviate
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.DataFeed import net.corda.core.messaging.DataFeed
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
@ -15,6 +17,8 @@ import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import org.slf4j.Logger import org.slf4j.Logger
import java.time.Duration
import java.time.Instant
/** /**
* A sub-class of [FlowLogic<T>] implements a flow using direct, straight line blocking code. Thus you * A sub-class of [FlowLogic<T>] implements a flow using direct, straight line blocking code. Thus you
@ -42,6 +46,34 @@ abstract class FlowLogic<out T> {
/** This is where you should log things to. */ /** This is where you should log things to. */
val logger: Logger get() = stateMachine.logger val logger: Logger get() = stateMachine.logger
companion object {
/**
* Return the outermost [FlowLogic] instance, or null if not in a flow.
*/
@JvmStatic
val currentTopLevel: FlowLogic<*>? get() = (Strand.currentStrand() as? FlowStateMachine<*>)?.logic
/**
* If on a flow, suspends the flow and only wakes it up after at least [duration] time has passed. Otherwise,
* just sleep for [duration]. This sleep function is not designed to aid scheduling, for which you should
* consider using [SchedulableState]. It is designed to aid with managing contention for which you have not
* managed via another means.
*
* Warning: long sleeps and in general long running flows are highly discouraged, as there is currently no
* support for flow migration! This method will throw an exception if you attempt to sleep for longer than
* 5 minutes.
*/
@Suspendable
@JvmStatic
@Throws(FlowException::class)
fun sleep(duration: Duration) {
if (duration.compareTo(Duration.ofMinutes(5)) > 0) {
throw FlowException("Attempt to sleep for longer than 5 minutes is not supported. Consider using SchedulableState.")
}
(Strand.currentStrand() as? FlowStateMachine<*>)?.sleepUntil(Instant.now() + duration) ?: Strand.sleep(duration.toMillis())
}
}
/** /**
* Returns a wrapped [java.util.UUID] object that identifies this state machine run (i.e. subflows have the same * Returns a wrapped [java.util.UUID] object that identifies this state machine run (i.e. subflows have the same
* identifier as their parents). * identifier as their parents).
@ -55,6 +87,10 @@ abstract class FlowLogic<out T> {
*/ */
val serviceHub: ServiceHub get() = stateMachine.serviceHub val serviceHub: ServiceHub get() = stateMachine.serviceHub
/**
* Creates a communication session with [party]. Subsequently you may send/receive using this session object. Note
* that this function does not communicate in itself, the counter-flow will be kicked off by the first send/receive.
*/
@Suspendable @Suspendable
fun initiateFlow(party: Party): FlowSession = stateMachine.initiateFlow(party, flowUsedForSessions) fun initiateFlow(party: Party): FlowSession = stateMachine.initiateFlow(party, flowUsedForSessions)
@ -100,7 +136,7 @@ abstract class FlowLogic<out T> {
* Note that this function is not just a simple send+receive pair: it is more efficient and more correct to * 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. * 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. * @return an [UntrustworthyData] wrapper around the received object.
*/ */
@Deprecated("Use FlowSession.sendAndReceive()", level = DeprecationLevel.WARNING) @Deprecated("Use FlowSession.sendAndReceive()", level = DeprecationLevel.WARNING)
inline fun <reified R : Any> sendAndReceive(otherParty: Party, payload: Any): UntrustworthyData<R> { inline fun <reified R : Any> sendAndReceive(otherParty: Party, payload: Any): UntrustworthyData<R> {
@ -116,7 +152,7 @@ abstract class FlowLogic<out T> {
* Note that this function is not just a simple send+receive pair: it is more efficient and more correct to * 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. * 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. * @return an [UntrustworthyData] wrapper around the received object.
*/ */
@Deprecated("Use FlowSession.sendAndReceive()", level = DeprecationLevel.WARNING) @Deprecated("Use FlowSession.sendAndReceive()", level = DeprecationLevel.WARNING)
@Suspendable @Suspendable
@ -165,7 +201,7 @@ abstract class FlowLogic<out T> {
* verified for consistency and that all expectations are satisfied, as a malicious peer may send you subtly * 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. * corrupted data in order to exploit your code.
* *
* @returns an [UntrustworthyData] wrapper around the received object. * @return an [UntrustworthyData] wrapper around the received object.
*/ */
@Deprecated("Use FlowSession.receive()", level = DeprecationLevel.WARNING) @Deprecated("Use FlowSession.receive()", level = DeprecationLevel.WARNING)
@Suspendable @Suspendable
@ -173,6 +209,38 @@ abstract class FlowLogic<out T> {
return stateMachine.receive(receiveType, otherParty, flowUsedForSessions) return stateMachine.receive(receiveType, otherParty, flowUsedForSessions)
} }
/** Suspends until a message has been received for each session in the specified [sessions].
*
* Consider [receiveAll(receiveType: Class<R>, sessions: List<FlowSession>): List<UntrustworthyData<R>>] when the same type is expected from all sessions.
*
* 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.
*
* @returns a [Map] containing the objects received, wrapped in an [UntrustworthyData], by the [FlowSession]s who sent them.
*/
@Suspendable
open fun receiveAll(sessions: Map<FlowSession, Class<out Any>>): Map<FlowSession, UntrustworthyData<Any>> {
return stateMachine.receiveAll(sessions, this)
}
/**
* Suspends until a message has been received for each session in the specified [sessions].
*
* Consider [sessions: Map<FlowSession, Class<out Any>>): Map<FlowSession, UntrustworthyData<Any>>] when sessions are expected to receive different types.
*
* 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.
*
* @returns a [List] containing the objects received, wrapped in an [UntrustworthyData], with the same order of [sessions].
*/
@Suspendable
open fun <R : Any> receiveAll(receiveType: Class<R>, sessions: List<FlowSession>): List<UntrustworthyData<R>> {
enforceNoDuplicates(sessions)
return castMapValuesToKnownType(receiveAll(associateSessionsToReceiveType(receiveType, sessions)))
}
/** /**
* Queues the given [payload] for sending to the [otherParty] and continues without suspending. * Queues the given [payload] for sending to the [otherParty] and continues without suspending.
* *
@ -227,7 +295,6 @@ abstract class FlowLogic<out T> {
stateMachine.checkFlowPermission(permissionName, extraAuditData) stateMachine.checkFlowPermission(permissionName, extraAuditData)
} }
/** /**
* Flows can call this method to record application level flow audit events * Flows can call this method to record application level flow audit events
* @param eventType is a string representing the type of event. Each flow is given a distinct namespace for these names. * @param eventType is a string representing the type of event. Each flow is given a distinct namespace for these names.
@ -330,6 +397,18 @@ abstract class FlowLogic<out T> {
ours.setChildProgressTracker(ours.currentStep, theirs) ours.setChildProgressTracker(ours.currentStep, theirs)
} }
} }
private fun enforceNoDuplicates(sessions: List<FlowSession>) {
require(sessions.size == sessions.toSet().size) { "A flow session can only appear once as argument." }
}
private fun <R> associateSessionsToReceiveType(receiveType: Class<R>, sessions: List<FlowSession>): Map<FlowSession, Class<R>> {
return sessions.associateByTo(LinkedHashMap(), { it }, { receiveType })
}
private fun <R> castMapValuesToKnownType(map: Map<FlowSession, UntrustworthyData<Any>>): List<UntrustworthyData<R>> {
return map.values.map { uncheckedCast<Any, UntrustworthyData<R>>(it) }
}
} }
/** /**
@ -347,4 +426,4 @@ data class FlowInfo(
* to deduplicate it from other releases of the same CorDapp, typically a version string. See the * to deduplicate it from other releases of the same CorDapp, typically a version string. See the
* [CorDapp JAR format](https://docs.corda.net/cordapp-build-systems.html#cordapp-jar-format) for more details. * [CorDapp JAR format](https://docs.corda.net/cordapp-build-systems.html#cordapp-jar-format) for more details.
*/ */
val appName: String) val appName: String)

View File

@ -24,16 +24,4 @@ class IllegalFlowLogicException(type: Class<*>, msg: String) : IllegalArgumentEx
*/ */
// TODO: align this with the existing [FlowRef] in the bank-side API (probably replace some of the API classes) // TODO: align this with the existing [FlowRef] in the bank-side API (probably replace some of the API classes)
@CordaSerializable @CordaSerializable
interface FlowLogicRef interface FlowLogicRef
/**
* This is just some way to track what attachments need to be in the class loader, but may later include some app
* properties loaded from the attachments. And perhaps the authenticated user for an API call?
*/
@CordaSerializable
data class AppContext(val attachments: List<SecureHash>) {
// TODO: build a real [AttachmentsClassLoader] etc
val classLoader: ClassLoader
get() = this.javaClass.classLoader
}

View File

@ -5,7 +5,18 @@ import net.corda.core.identity.Party
import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.UntrustworthyData
/** /**
* To port existing flows: *
* A [FlowSession] is a handle on a communication sequence between two paired flows, possibly running on separate nodes.
* It is used to send and receive messages between the flows as well as to query information about the counter-flow.
*
* There are two ways of obtaining such a session:
*
* 1. Calling [FlowLogic.initiateFlow]. This will create a [FlowSession] object on which the first send/receive
* operation will attempt to kick off a corresponding [InitiatedBy] flow on the counterparty's node.
* 2. As constructor parameter to [InitiatedBy] flows. This session is the one corresponding to the initiating flow and
* may be used for replies.
*
* To port flows using the old Party-based API:
* *
* Look for [Deprecated] usages of send/receive/sendAndReceive/getFlowInfo. * Look for [Deprecated] usages of send/receive/sendAndReceive/getFlowInfo.
* *
@ -31,6 +42,10 @@ import net.corda.core.utilities.UntrustworthyData
* otherSideSession.send(something) * otherSideSession.send(something)
*/ */
abstract class FlowSession { abstract class FlowSession {
/**
* The [Party] on the other side of this session. In the case of a session created by [FlowLogic.initiateFlow]
* [counterparty] is the same Party as the one passed to that function.
*/
abstract val counterparty: Party abstract val counterparty: Party
/** /**
@ -54,12 +69,13 @@ abstract class FlowSession {
* Note that this function is not just a simple send+receive pair: it is more efficient and more correct to * 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. * 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. * @return an [UntrustworthyData] wrapper around the received object.
*/ */
@Suspendable @Suspendable
inline fun <reified R : Any> sendAndReceive(payload: Any): UntrustworthyData<R> { inline fun <reified R : Any> sendAndReceive(payload: Any): UntrustworthyData<R> {
return sendAndReceive(R::class.java, payload) return sendAndReceive(R::class.java, payload)
} }
/** /**
* Serializes and queues the given [payload] object for sending to the [counterparty]. Suspends until a response * Serializes and queues the given [payload] object for sending to the [counterparty]. Suspends until a response
* is received, which must be of the given [receiveType]. Remember that when receiving data from other parties the data * is received, which must be of the given [receiveType]. Remember that when receiving data from other parties the data
@ -69,7 +85,7 @@ abstract class FlowSession {
* Note that this function is not just a simple send+receive pair: it is more efficient and more correct to * 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. * 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. * @return an [UntrustworthyData] wrapper around the received object.
*/ */
@Suspendable @Suspendable
abstract fun <R : Any> sendAndReceive(receiveType: Class<R>, payload: Any): UntrustworthyData<R> abstract fun <R : Any> sendAndReceive(receiveType: Class<R>, payload: Any): UntrustworthyData<R>
@ -85,6 +101,7 @@ abstract class FlowSession {
inline fun <reified R : Any> receive(): UntrustworthyData<R> { inline fun <reified R : Any> receive(): UntrustworthyData<R> {
return receive(R::class.java) return receive(R::class.java)
} }
/** /**
* Suspends until [counterparty] sends us a message of type [receiveType]. * Suspends until [counterparty] sends us a message of type [receiveType].
* *
@ -92,7 +109,7 @@ abstract class FlowSession {
* verified for consistency and that all expectations are satisfied, as a malicious peer may send you subtly * 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. * corrupted data in order to exploit your code.
* *
* @returns an [UntrustworthyData] wrapper around the received object. * @return an [UntrustworthyData] wrapper around the received object.
*/ */
@Suspendable @Suspendable
abstract fun <R : Any> receive(receiveType: Class<R>): UntrustworthyData<R> abstract fun <R : Any> receive(receiveType: Class<R>): UntrustworthyData<R>

View File

@ -13,7 +13,6 @@ import net.corda.core.node.services.NotaryService
import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.core.node.services.TrustedAuthorityNotaryService
import net.corda.core.node.services.UniquenessProvider import net.corda.core.node.services.UniquenessProvider
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.FilteredTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.UntrustworthyData
@ -55,11 +54,7 @@ class NotaryFlow {
} }
try { try {
if (stx.isNotaryChangeTransaction()) { stx.resolveTransactionWithSignatures(serviceHub).verifySignaturesExcept(notaryParty.owningKey)
stx.resolveNotaryChangeTransaction(serviceHub).verifySignaturesExcept(notaryParty.owningKey)
} else {
stx.verifySignaturesExcept(notaryParty.owningKey)
}
} catch (ex: SignatureException) { } catch (ex: SignatureException) {
throw NotaryException(NotaryError.TransactionInvalid(ex)) throw NotaryException(NotaryError.TransactionInvalid(ex))
} }
@ -138,8 +133,11 @@ class NotaryFlow {
// Check if transaction is intended to be signed by this notary. // Check if transaction is intended to be signed by this notary.
@Suspendable @Suspendable
protected fun checkNotary(notary: Party?) { protected fun checkNotary(notary: Party?) {
if (notary !in serviceHub.myInfo.legalIdentities) // TODO This check implies that it's OK to use the node's main identity. Shouldn't it be just limited to the
// notary identities?
if (notary !in serviceHub.myInfo.legalIdentities) {
throw NotaryException(NotaryError.WrongNotary) throw NotaryException(NotaryError.WrongNotary)
}
} }
@Suspendable @Suspendable
@ -171,5 +169,5 @@ sealed class NotaryError {
override fun toString() = cause.toString() override fun toString() = cause.toString()
} }
object WrongNotary: NotaryError() object WrongNotary : NotaryError()
} }

View File

@ -13,7 +13,7 @@ import java.security.SignatureException
* This flow is a combination of [FlowSession.receive], resolve and [SignedTransaction.verify]. This flow will receive the * This flow is a combination of [FlowSession.receive], resolve and [SignedTransaction.verify]. This flow will receive the
* [SignedTransaction] and perform the resolution back-and-forth required to check the dependencies and download any missing * [SignedTransaction] and perform the resolution back-and-forth required to check the dependencies and download any missing
* attachments. The flow will return the [SignedTransaction] after it is resolved and then verified using [SignedTransaction.verify]. * attachments. The flow will return the [SignedTransaction] after it is resolved and then verified using [SignedTransaction.verify].
* *
* @param otherSideSession session to the other side which is calling [SendTransactionFlow]. * @param otherSideSession session to the other side which is calling [SendTransactionFlow].
* @param checkSufficientSignatures if true checks all required signatures are present. See [SignedTransaction.verify]. * @param checkSufficientSignatures if true checks all required signatures are present. See [SignedTransaction.verify].
*/ */

View File

@ -0,0 +1,12 @@
package net.corda.core.flows
import kotlin.annotation.AnnotationTarget.CLASS
/**
* Any [FlowLogic] which is to be started by the [AppServiceHub] interface from
* within a [CordaService] must have this annotation. If it's missing the
* flow will not be allowed to start and an exception will be thrown.
*/
@Target(CLASS)
@MustBeDocumented
annotation class StartableByService

View File

@ -13,6 +13,7 @@ import java.security.PublicKey
abstract class AbstractParty(val owningKey: PublicKey) { abstract class AbstractParty(val owningKey: PublicKey) {
/** Anonymised parties do not include any detail apart from owning key, so equality is dependent solely on the key */ /** Anonymised parties do not include any detail apart from owning key, so equality is dependent solely on the key */
override fun equals(other: Any?): Boolean = other === this || other is AbstractParty && other.owningKey == owningKey override fun equals(other: Any?): Boolean = other === this || other is AbstractParty && other.owningKey == owningKey
override fun hashCode(): Int = owningKey.hashCode() override fun hashCode(): Int = owningKey.hashCode()
abstract fun nameOrNull(): CordaX500Name? abstract fun nameOrNull(): CordaX500Name?

View File

@ -81,7 +81,7 @@ data class CordaX500Name(val commonName: String?,
private val countryCodes: Set<String> = ImmutableSet.copyOf(Locale.getISOCountries()) private val countryCodes: Set<String> = ImmutableSet.copyOf(Locale.getISOCountries())
@JvmStatic @JvmStatic
fun build(principal: X500Principal) : CordaX500Name { fun build(principal: X500Principal): CordaX500Name {
val x500Name = X500Name.getInstance(principal.encoded) val x500Name = X500Name.getInstance(principal.encoded)
val attrsMap: Map<ASN1ObjectIdentifier, ASN1Encodable> = x500Name.rdNs val attrsMap: Map<ASN1ObjectIdentifier, ASN1Encodable> = x500Name.rdNs
.flatMap { it.typesAndValues.asList() } .flatMap { it.typesAndValues.asList() }
@ -109,7 +109,7 @@ data class CordaX500Name(val commonName: String?,
} }
@JvmStatic @JvmStatic
fun parse(name: String) : CordaX500Name = build(X500Principal(name)) fun parse(name: String): CordaX500Name = build(X500Principal(name))
} }
@Transient @Transient

View File

@ -29,6 +29,7 @@ import java.security.cert.X509Certificate
class Party(val name: CordaX500Name, owningKey: PublicKey) : AbstractParty(owningKey) { class Party(val name: CordaX500Name, owningKey: PublicKey) : AbstractParty(owningKey) {
constructor(certificate: X509Certificate) constructor(certificate: X509Certificate)
: this(CordaX500Name.build(certificate.subjectX500Principal), Crypto.toSupportedPublicKey(certificate.publicKey)) : this(CordaX500Name.build(certificate.subjectX500Principal), Crypto.toSupportedPublicKey(certificate.publicKey))
override fun nameOrNull(): CordaX500Name = name override fun nameOrNull(): CordaX500Name = name
fun anonymise(): AnonymousParty = AnonymousParty(owningKey) fun anonymise(): AnonymousParty = AnonymousParty(owningKey)
override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this, bytes) override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this, bytes)

View File

@ -11,7 +11,9 @@ import java.security.cert.*
*/ */
@CordaSerializable @CordaSerializable
class PartyAndCertificate(val certPath: CertPath) { class PartyAndCertificate(val certPath: CertPath) {
@Transient val certificate: X509Certificate @Transient
val certificate: X509Certificate
init { init {
require(certPath.type == "X.509") { "Only X.509 certificates supported" } require(certPath.type == "X.509") { "Only X.509 certificates supported" }
val certs = certPath.certificates val certs = certPath.certificates
@ -19,7 +21,8 @@ class PartyAndCertificate(val certPath: CertPath) {
certificate = certs[0] as X509Certificate certificate = certs[0] as X509Certificate
} }
@Transient val party: Party = Party(certificate) @Transient
val party: Party = Party(certificate)
val owningKey: PublicKey get() = party.owningKey val owningKey: PublicKey get() = party.owningKey
val name: CordaX500Name get() = party.name val name: CordaX500Name get() = party.name

View File

@ -13,22 +13,38 @@ object Emoji {
(System.getenv("TERM_PROGRAM") == "JediTerm" && System.getProperty("java.vendor") == "JetBrains s.r.o") (System.getenv("TERM_PROGRAM") == "JediTerm" && System.getProperty("java.vendor") == "JetBrains s.r.o")
} }
@JvmStatic val CODE_SANTA_CLAUS: String = codePointsString(0x1F385) @JvmStatic
@JvmStatic val CODE_DIAMOND: String = codePointsString(0x1F537) val CODE_SANTA_CLAUS: String = codePointsString(0x1F385)
@JvmStatic val CODE_BAG_OF_CASH: String = codePointsString(0x1F4B0) @JvmStatic
@JvmStatic val CODE_NEWSPAPER: String = codePointsString(0x1F4F0) val CODE_DIAMOND: String = codePointsString(0x1F537)
@JvmStatic val CODE_RIGHT_ARROW: String = codePointsString(0x27A1, 0xFE0F) @JvmStatic
@JvmStatic val CODE_LEFT_ARROW: String = codePointsString(0x2B05, 0xFE0F) val CODE_BAG_OF_CASH: String = codePointsString(0x1F4B0)
@JvmStatic val CODE_GREEN_TICK: String = codePointsString(0x2705) @JvmStatic
@JvmStatic val CODE_PAPERCLIP: String = codePointsString(0x1F4CE) val CODE_NEWSPAPER: String = codePointsString(0x1F4F0)
@JvmStatic val CODE_COOL_GUY: String = codePointsString(0x1F60E) @JvmStatic
@JvmStatic val CODE_NO_ENTRY: String = codePointsString(0x1F6AB) val CODE_RIGHT_ARROW: String = codePointsString(0x27A1, 0xFE0F)
@JvmStatic val CODE_SKULL_AND_CROSSBONES: String = codePointsString(0x2620) @JvmStatic
@JvmStatic val CODE_BOOKS: String = codePointsString(0x1F4DA) val CODE_LEFT_ARROW: String = codePointsString(0x2B05, 0xFE0F)
@JvmStatic val CODE_SLEEPING_FACE: String = codePointsString(0x1F634) @JvmStatic
@JvmStatic val CODE_LIGHTBULB: String = codePointsString(0x1F4A1) val CODE_GREEN_TICK: String = codePointsString(0x2705)
@JvmStatic val CODE_FREE: String = codePointsString(0x1F193) @JvmStatic
@JvmStatic val CODE_SOON: String = codePointsString(0x1F51C) val CODE_PAPERCLIP: String = codePointsString(0x1F4CE)
@JvmStatic
val CODE_COOL_GUY: String = codePointsString(0x1F60E)
@JvmStatic
val CODE_NO_ENTRY: String = codePointsString(0x1F6AB)
@JvmStatic
val CODE_SKULL_AND_CROSSBONES: String = codePointsString(0x2620)
@JvmStatic
val CODE_BOOKS: String = codePointsString(0x1F4DA)
@JvmStatic
val CODE_SLEEPING_FACE: String = codePointsString(0x1F634)
@JvmStatic
val CODE_LIGHTBULB: String = codePointsString(0x1F4A1)
@JvmStatic
val CODE_FREE: String = codePointsString(0x1F193)
@JvmStatic
val CODE_SOON: String = codePointsString(0x1F51C)
/** /**

View File

@ -114,8 +114,7 @@ sealed class FetchDataFlow<T : NamedByHash, in W : Any>(
protected abstract fun load(txid: SecureHash): T? protected abstract fun load(txid: SecureHash): T?
@Suppress("UNCHECKED_CAST") protected open fun convert(wire: W): T = uncheckedCast(wire)
protected open fun convert(wire: W): T = wire as T
private fun validateFetchResponse(maybeItems: UntrustworthyData<ArrayList<W>>, private fun validateFetchResponse(maybeItems: UntrustworthyData<ArrayList<W>>,
requests: List<SecureHash>): List<T> { requests: List<SecureHash>): List<T> {

View File

@ -10,6 +10,7 @@ import net.corda.core.node.ServiceHub
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.UntrustworthyData
import org.slf4j.Logger import org.slf4j.Logger
import java.time.Instant
/** This is an internal interface that is implemented by code in the node module. You should look at [FlowLogic]. */ /** This is an internal interface that is implemented by code in the node module. You should look at [FlowLogic]. */
interface FlowStateMachine<R> { interface FlowStateMachine<R> {
@ -30,25 +31,32 @@ interface FlowStateMachine<R> {
fun <T : Any> receive(receiveType: Class<T>, otherParty: Party, 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<*>): Unit fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>)
@Suspendable @Suspendable
fun waitForLedgerCommit(hash: SecureHash, sessionFlow: FlowLogic<*>): SignedTransaction fun waitForLedgerCommit(hash: SecureHash, sessionFlow: FlowLogic<*>): SignedTransaction
fun checkFlowPermission(permissionName: String, extraAuditData: Map<String, String>): Unit @Suspendable
fun sleepUntil(until: Instant)
fun recordAuditEvent(eventType: String, comment: String, extraAuditData: Map<String, String>): Unit fun checkFlowPermission(permissionName: String, extraAuditData: Map<String, String>)
fun recordAuditEvent(eventType: String, comment: String, extraAuditData: Map<String, String>)
@Suspendable @Suspendable
fun flowStackSnapshot(flowClass: Class<out FlowLogic<*>>): FlowStackSnapshot? fun flowStackSnapshot(flowClass: Class<out FlowLogic<*>>): FlowStackSnapshot?
@Suspendable @Suspendable
fun persistFlowStackSnapshot(flowClass: Class<out FlowLogic<*>>): Unit fun persistFlowStackSnapshot(flowClass: Class<out FlowLogic<*>>)
val logic: FlowLogic<R>
val serviceHub: ServiceHub val serviceHub: ServiceHub
val logger: Logger val logger: Logger
val id: StateMachineRunId val id: StateMachineRunId
val resultFuture: CordaFuture<R> val resultFuture: CordaFuture<R>
val flowInitiator: FlowInitiator val flowInitiator: FlowInitiator
val ourIdentityAndCert: PartyAndCertificate val ourIdentityAndCert: PartyAndCertificate
@Suspendable
fun receiveAll(sessions: Map<FlowSession, Class<out Any>>, sessionFlow: FlowLogic<*>): Map<FlowSession, UntrustworthyData<Any>>
} }

View File

@ -1,7 +1,14 @@
@file:JvmName("InternalUtils")
package net.corda.core.internal package net.corda.core.internal
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256 import net.corda.core.crypto.sha256
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.SerializationContext
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction
import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.slf4j.Logger import org.slf4j.Logger
@ -44,6 +51,7 @@ operator fun Duration.times(multiplicand: Long): Duration = multipliedBy(multipl
* separator problems. * separator problems.
*/ */
operator fun Path.div(other: String): Path = resolve(other) operator fun Path.div(other: String): Path = resolve(other)
operator fun String.div(other: String): Path = Paths.get(this) / other operator fun String.div(other: String): Path = Paths.get(this) / other
/** /**
@ -99,6 +107,7 @@ fun Path.copyToDirectory(targetDir: Path, vararg options: CopyOption): Path {
Files.copy(this, targetFile, *options) Files.copy(this, targetFile, *options)
return targetFile return targetFile
} }
fun Path.moveTo(target: Path, vararg options: CopyOption): Path = Files.move(this, target, *options) fun Path.moveTo(target: Path, vararg options: CopyOption): Path = Files.move(this, target, *options)
fun Path.isRegularFile(vararg options: LinkOption): Boolean = Files.isRegularFile(this, *options) fun Path.isRegularFile(vararg options: LinkOption): Boolean = Files.isRegularFile(this, *options)
fun Path.isDirectory(vararg options: LinkOption): Boolean = Files.isDirectory(this, *options) fun Path.isDirectory(vararg options: LinkOption): Boolean = Files.isDirectory(this, *options)
@ -226,25 +235,29 @@ private fun IntProgression.toSpliterator(): Spliterator.OfInt {
fun IntProgression.stream(parallel: Boolean = false): IntStream = StreamSupport.intStream(toSpliterator(), parallel) fun IntProgression.stream(parallel: Boolean = false): IntStream = StreamSupport.intStream(toSpliterator(), parallel)
@Suppress("UNCHECKED_CAST") // When toArray has filled in the array, the component type is no longer T? but T (that may itself be nullable). // When toArray has filled in the array, the component type is no longer T? but T (that may itself be nullable):
inline fun <reified T> Stream<out T>.toTypedArray() = toArray { size -> arrayOfNulls<T>(size) } as Array<T> inline fun <reified T> Stream<out T>.toTypedArray(): Array<T> = uncheckedCast(toArray { size -> arrayOfNulls<T>(size) })
fun <T> Class<T>.castIfPossible(obj: Any): T? = if (isInstance(obj)) cast(obj) else null fun <T> Class<T>.castIfPossible(obj: Any): T? = if (isInstance(obj)) cast(obj) else null
/** Returns a [DeclaredField] wrapper around the declared (possibly non-public) static field of the receiver [Class]. */ /** Returns a [DeclaredField] wrapper around the declared (possibly non-public) static field of the receiver [Class]. */
fun <T> Class<*>.staticField(name: String): DeclaredField<T> = DeclaredField(this, name, null) fun <T> Class<*>.staticField(name: String): DeclaredField<T> = DeclaredField(this, name, null)
/** Returns a [DeclaredField] wrapper around the declared (possibly non-public) static field of the receiver [KClass]. */ /** Returns a [DeclaredField] wrapper around the declared (possibly non-public) static field of the receiver [KClass]. */
fun <T> KClass<*>.staticField(name: String): DeclaredField<T> = DeclaredField(java, name, null) fun <T> KClass<*>.staticField(name: String): DeclaredField<T> = DeclaredField(java, name, null)
/** Returns a [DeclaredField] wrapper around the declared (possibly non-public) instance field of the receiver object. */
/** @suppress Returns a [DeclaredField] wrapper around the declared (possibly non-public) instance field of the receiver object. */
fun <T> Any.declaredField(name: String): DeclaredField<T> = DeclaredField(javaClass, name, this) fun <T> Any.declaredField(name: String): DeclaredField<T> = DeclaredField(javaClass, name, this)
/** /**
* Returns a [DeclaredField] wrapper around the (possibly non-public) instance field of the receiver object, but declared * Returns a [DeclaredField] wrapper around the (possibly non-public) instance field of the receiver object, but declared
* in its superclass [clazz]. * in its superclass [clazz].
* @suppress
*/ */
fun <T> Any.declaredField(clazz: KClass<*>, name: String): DeclaredField<T> = DeclaredField(clazz.java, name, this) fun <T> Any.declaredField(clazz: KClass<*>, name: String): DeclaredField<T> = DeclaredField(clazz.java, name, this)
/** creates a new instance if not a Kotlin object */ /** creates a new instance if not a Kotlin object */
fun <T: Any> KClass<T>.objectOrNewInstance(): T { fun <T : Any> KClass<T>.objectOrNewInstance(): T {
return this.objectInstance ?: this.createInstance() return this.objectInstance ?: this.createInstance()
} }
@ -255,8 +268,7 @@ fun <T: Any> KClass<T>.objectOrNewInstance(): T {
class DeclaredField<T>(clazz: Class<*>, name: String, private val receiver: Any?) { class DeclaredField<T>(clazz: Class<*>, name: String, private val receiver: Any?) {
private val javaField = clazz.getDeclaredField(name).apply { isAccessible = true } private val javaField = clazz.getDeclaredField(name).apply { isAccessible = true }
var value: T var value: T
@Suppress("UNCHECKED_CAST") get() = uncheckedCast<Any?, T>(javaField.get(receiver))
get() = javaField.get(receiver) as T
set(value) = javaField.set(receiver, value) set(value) = javaField.set(receiver, value)
} }
@ -273,4 +285,21 @@ annotation class VisibleForTesting
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <T, U : T> uncheckedCast(obj: T) = obj as U fun <T, U : T> uncheckedCast(obj: T) = obj as U
fun <K, V> Iterable<Pair<K, V>>.toMultiMap(): Map<K, List<V>> = this.groupBy({ it.first }) { it.second } fun <K, V> Iterable<Pair<K, V>>.toMultiMap(): Map<K, List<V>> = this.groupBy({ it.first }) { it.second }
/**
* Provide access to internal method for AttachmentClassLoaderTests
* @suppress
*/
fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serializationContext: SerializationContext): WireTransaction {
return toWireTransactionWithContext(services, serializationContext)
}
/**
* Provide access to internal method for AttachmentClassLoaderTests
* @suppress
*/
fun TransactionBuilder.toLedgerTransaction(services: ServiceHub, serializationContext: SerializationContext) = toLedgerTransactionWithContext(services, serializationContext)
/** Convenience method to get the package name of a class literal. */
val KClass<*>.packageName get() = java.`package`.name

View File

@ -27,6 +27,7 @@ class LazyPool<A>(
STARTED, STARTED,
FINISHED FINISHED
} }
private val lifeCycle = LifeCycle(State.STARTED) private val lifeCycle = LifeCycle(State.STARTED)
private fun clearIfNeeded(instance: A): A { private fun clearIfNeeded(instance: A): A {

View File

@ -18,6 +18,7 @@ class LazyStickyPool<A : Any>(
private class InstanceBox<A> { private class InstanceBox<A> {
var instance: LinkedBlockingQueue<A>? = null var instance: LinkedBlockingQueue<A>? = null
} }
private val random = Random() private val random = Random()
private val boxes = Array(size) { InstanceBox<A>() } private val boxes = Array(size) { InstanceBox<A>() }

View File

@ -2,6 +2,7 @@ package net.corda.core.internal
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession import net.corda.core.flows.FlowSession
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
@ -19,14 +20,15 @@ import java.util.*
class ResolveTransactionsFlow(private val txHashes: Set<SecureHash>, class ResolveTransactionsFlow(private val txHashes: Set<SecureHash>,
private val otherSide: FlowSession) : FlowLogic<List<SignedTransaction>>() { private val otherSide: FlowSession) : FlowLogic<List<SignedTransaction>>() {
/** /**
* Resolves and validates the dependencies of the specified [signedTransaction]. Fetches the attachments, but does * Resolves and validates the dependencies of the specified [SignedTransaction]. Fetches the attachments, but does
* *not* validate or store the [signedTransaction] itself. * *not* validate or store the [SignedTransaction] itself.
* *
* @return a list of verified [SignedTransaction] objects, in a depth-first order. * @return a list of verified [SignedTransaction] objects, in a depth-first order.
*/ */
constructor(signedTransaction: SignedTransaction, otherSide: FlowSession) : this(dependencyIDs(signedTransaction), otherSide) { constructor(signedTransaction: SignedTransaction, otherSide: FlowSession) : this(dependencyIDs(signedTransaction), otherSide) {
this.signedTransaction = signedTransaction this.signedTransaction = signedTransaction
} }
companion object { companion object {
private fun dependencyIDs(stx: SignedTransaction) = stx.inputs.map { it.txhash }.toSet() private fun dependencyIDs(stx: SignedTransaction) = stx.inputs.map { it.txhash }.toSet()
@ -63,7 +65,7 @@ class ResolveTransactionsFlow(private val txHashes: Set<SecureHash>,
} }
@CordaSerializable @CordaSerializable
class ExcessivelyLargeTransactionGraph : Exception() class ExcessivelyLargeTransactionGraph : FlowException()
/** Transaction for fetch attachments for */ /** Transaction for fetch attachments for */
private var signedTransaction: SignedTransaction? = null private var signedTransaction: SignedTransaction? = null

View File

@ -0,0 +1,7 @@
package net.corda.core.internal
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractClassName
/** Indicates that this transaction replaces the inputs contract state to another contract state */
data class UpgradeCommand(val upgradedContractClass: ContractClassName) : CommandData

View File

@ -6,7 +6,7 @@ import kotlin.reflect.KProperty
* A write-once property to be used as delegate for Kotlin var properties. The expectation is that this is initialised * A write-once property to be used as delegate for Kotlin var properties. The expectation is that this is initialised
* prior to the spawning of any threads that may access it and so there's no need for it to be volatile. * prior to the spawning of any threads that may access it and so there's no need for it to be volatile.
*/ */
class WriteOnceProperty<T : Any>(private val defaultValue:T? = null) { class WriteOnceProperty<T : Any>(private val defaultValue: T? = null) {
private var v: T? = defaultValue private var v: T? = defaultValue
operator fun getValue(thisRef: Any?, property: KProperty<*>) = v ?: throw IllegalStateException("Write-once property $property not set.") operator fun getValue(thisRef: Any?, property: KProperty<*>) = v ?: throw IllegalStateException("Write-once property $property not set.")

View File

@ -46,6 +46,7 @@ class X509EdDSAEngine : Signature {
override fun engineSetParameter(params: AlgorithmParameterSpec) = engine.setParameter(params) override fun engineSetParameter(params: AlgorithmParameterSpec) = engine.setParameter(params)
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
override fun engineGetParameter(param: String): Any = engine.getParameter(param) override fun engineGetParameter(param: String): Any = engine.getParameter(param)
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
override fun engineSetParameter(param: String, value: Any?) = engine.setParameter(param, value) override fun engineSetParameter(param: String, value: Any?) = engine.setParameter(param, value)
} }

View File

@ -2,23 +2,28 @@ package net.corda.core.internal.cordapp
import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.Cordapp
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SerializeAsToken
import java.io.File
import java.net.URL import java.net.URL
data class CordappImpl( data class CordappImpl(
override val contractClassNames: List<String>, override val contractClassNames: List<String>,
override val initiatedFlows: List<Class<out FlowLogic<*>>>, override val initiatedFlows: List<Class<out FlowLogic<*>>>,
override val rpcFlows: List<Class<out FlowLogic<*>>>, override val rpcFlows: List<Class<out FlowLogic<*>>>,
override val serviceFlows: List<Class<out FlowLogic<*>>>,
override val schedulableFlows: List<Class<out FlowLogic<*>>>,
override val services: List<Class<out SerializeAsToken>>, override val services: List<Class<out SerializeAsToken>>,
override val plugins: List<CordaPluginRegistry>, override val serializationWhitelists: List<SerializationWhitelist>,
override val customSchemas: Set<MappedSchema>, override val customSchemas: Set<MappedSchema>,
override val jarPath: URL) : Cordapp { override val jarPath: URL) : Cordapp {
override val name: String = File(jarPath.toURI()).name.removeSuffix(".jar")
/** /**
* An exhaustive list of all classes relevant to the node within this CorDapp * An exhaustive list of all classes relevant to the node within this CorDapp
* *
* TODO: Also add [SchedulableFlow] as a Cordapp class * TODO: Also add [SchedulableFlow] as a Cordapp class
*/ */
override val cordappClasses = ((rpcFlows + initiatedFlows + services + plugins.map { javaClass }).map { it.name } + contractClassNames) override val cordappClasses = ((rpcFlows + initiatedFlows + services + serializationWhitelists.map { javaClass }).map { it.name } + contractClassNames)
} }

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