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

@ -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) {
@ -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())

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
@ -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")
@ -211,7 +212,8 @@ 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>
get() {
return methodMap.entries().map { entry -> return methodMap.entries().map { entry ->
val (name, args) = entry // TODO: Kotlin 1.1 val (name, args) = entry // TODO: Kotlin 1.1
val argStr = if (args.parameterCount == 0) "" else { val argStr = if (args.parameterCount == 0) "" else {

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 = """

View File

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

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,7 +253,8 @@ class ConcatenatedList<A>(sourceList: ObservableList<ObservableList<A>>) : Trans
} }
} }
override val size: Int get() { override val size: Int
get() {
recalculateOffsets() recalculateOffsets()
if (nestedIndexOffsets.size > 0) { if (nestedIndexOffsets.size > 0) {
return nestedIndexOffsets.last() return nestedIndexOffsets.last()

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,7 +50,8 @@ 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>>
get() = backingMap.entries.fold(mutableSetOf()) { set, entry ->
set.add(object : MutableMap.MutableEntry<K, A> { set.add(object : MutableMap.MutableEntry<K, A> {
override var value: A = entry.value.first override var value: A = entry.value.first
override val key = entry.key override val key = entry.key

View File

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

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 {
startRpcClient<RPCOps>(
server.get().broker.hostAndPort!!, server.get().broker.hostAndPort!!,
configuration = RPCClientConfiguration.default.copy(minimumServerProtocolVersion = 1) configuration = RPCClientConfiguration.default.copy(minimumServerProtocolVersion = 1)
).get() } ).get()
}
} }
} }
repeat(5) { repeat(5) {
@ -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
} }
@ -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,43 +10,69 @@ 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>(
tcpTransport(ConnectionDirection.Outbound(), hostAndPort, config = null), tcpTransport(ConnectionDirection.Outbound(), hostAndPort, config = null),
@ -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())
@ -33,8 +36,12 @@ class KryoClientSerializationScheme : AbstractKryoSerializationScheme() {
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);
} }
@ -77,9 +74,9 @@ public class StandaloneCordaRPCJavaClientTest {
} }
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

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
} }
@ -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

@ -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
@ -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.*
@ -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,9 +84,10 @@ 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.
@ -82,9 +97,12 @@ fun PublicKey.isValid(content: ByteArray, signature: DigitalSignature) : Boolean
/** 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)
/** Return true if [otherKey] fulfils the requirements of this [PublicKey]. */
fun PublicKey.isFulfilledBy(otherKey: PublicKey): Boolean = isFulfilledBy(setOf(otherKey)) 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 {
return str.toUpperCase().parseAsHex().let {
when (it.size) { when (it.size) {
32 -> SHA256(it) 32 -> SHA256(it)
else -> throw IllegalArgumentException("Provided string is ${it.size} bytes not 32 bytes in hex: $str") 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

@ -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
} }
/** /**
@ -137,6 +129,7 @@ abstract class AbstractStateReplacementFlow {
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

@ -66,6 +66,7 @@ class CollectSignaturesFlow @JvmOverloads constructor (val partiallySignedTx: Si
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.

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

@ -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) }
}
} }
/** /**

View File

@ -25,15 +25,3 @@ 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,9 +133,12 @@ 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
private fun signAndSendResponse(txId: SecureHash) { private fun signAndSendResponse(txId: SecureHash) {

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

@ -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,20 +235,24 @@ 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)
@ -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)
} }
@ -274,3 +286,20 @@ annotation class VisibleForTesting
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

@ -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)
} }

View File

@ -104,12 +104,15 @@ interface CordaRPCOps : RPCOps {
fun <T : ContractState> vaultQuery(contractStateType: Class<out T>): Vault.Page<T> { fun <T : ContractState> vaultQuery(contractStateType: Class<out T>): Vault.Page<T> {
return vaultQueryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType) return vaultQueryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType)
} }
fun <T : ContractState> vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class<out T>): Vault.Page<T> { fun <T : ContractState> vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class<out T>): Vault.Page<T> {
return vaultQueryBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType) return vaultQueryBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType)
} }
fun <T : ContractState> vaultQueryByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): Vault.Page<T> { fun <T : ContractState> vaultQueryByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): Vault.Page<T> {
return vaultQueryBy(criteria, paging, Sort(emptySet()), contractStateType) return vaultQueryBy(criteria, paging, Sort(emptySet()), contractStateType)
} }
fun <T : ContractState> vaultQueryByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): Vault.Page<T> { fun <T : ContractState> vaultQueryByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): Vault.Page<T> {
return vaultQueryBy(criteria, PageSpecification(), sorting, contractStateType) return vaultQueryBy(criteria, PageSpecification(), sorting, contractStateType)
} }
@ -142,12 +145,15 @@ interface CordaRPCOps : RPCOps {
fun <T : ContractState> vaultTrack(contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> { fun <T : ContractState> vaultTrack(contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return vaultTrackBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType) return vaultTrackBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType)
} }
fun <T : ContractState> vaultTrackByCriteria(contractStateType: Class<out T>, criteria: QueryCriteria): DataFeed<Vault.Page<T>, Vault.Update<T>> { fun <T : ContractState> vaultTrackByCriteria(contractStateType: Class<out T>, criteria: QueryCriteria): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return vaultTrackBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType) return vaultTrackBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType)
} }
fun <T : ContractState> vaultTrackByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): DataFeed<Vault.Page<T>, Vault.Update<T>> { fun <T : ContractState> vaultTrackByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return vaultTrackBy(criteria, paging, Sort(emptySet()), contractStateType) return vaultTrackBy(criteria, paging, Sort(emptySet()), contractStateType)
} }
fun <T : ContractState> vaultTrackByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): DataFeed<Vault.Page<T>, Vault.Update<T>> { fun <T : ContractState> vaultTrackByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return vaultTrackBy(criteria, PageSpecification(), sorting, contractStateType) return vaultTrackBy(criteria, PageSpecification(), sorting, contractStateType)
} }
@ -250,12 +256,21 @@ interface CordaRPCOps : RPCOps {
* @return well known identity, if found. * @return well known identity, if found.
*/ */
fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? fun wellKnownPartyFromAnonymous(party: AbstractParty): Party?
/** Returns the [Party] corresponding to the given key, if found. */ /** Returns the [Party] corresponding to the given key, if found. */
fun partyFromKey(key: PublicKey): Party? fun partyFromKey(key: PublicKey): Party?
/** Returns the [Party] with the X.500 principal as it's [Party.name]. */ /** Returns the [Party] with the X.500 principal as it's [Party.name]. */
fun wellKnownPartyFromX500Name(x500Name: CordaX500Name): Party? fun wellKnownPartyFromX500Name(x500Name: CordaX500Name): Party?
/**
* Get a notary identity by name.
*
* @return the notary identity, or null if there is no notary by that name. Note that this will return null if there
* is a peer with that name but they are not a recognised notary service.
*/
fun notaryPartyFromX500Name(x500Name: CordaX500Name): Party?
/** /**
* Returns a list of candidate matches for a given string, with optional fuzzy(ish) matching. Fuzzy matching may * Returns a list of candidate matches for a given string, with optional fuzzy(ish) matching. Fuzzy matching may
* get smarter with time e.g. to correct spelling errors, so you should not hard-code indexes into the results * get smarter with time e.g. to correct spelling errors, so you should not hard-code indexes into the results
@ -271,6 +286,8 @@ interface CordaRPCOps : RPCOps {
/** /**
* Returns a node's info from the network map cache, where known. * Returns a node's info from the network map cache, where known.
* Notice that when there are more than one node for a given name (in case of distributed services) first service node
* found will be returned.
* *
* @return the node info if available. * @return the node info if available.
*/ */

View File

@ -0,0 +1,30 @@
package net.corda.core.node
import net.corda.core.flows.FlowLogic
import net.corda.core.messaging.FlowHandle
import net.corda.core.messaging.FlowProgressHandle
import rx.Observable
/**
* A [CordaService] annotated class requires a constructor taking a
* single parameter of type [AppServiceHub].
* With the [AppServiceHub] parameter a [CordaService] is able to access to privileged operations.
* In particular such a [CordaService] can initiate and track flows marked with [net.corda.core.flows.StartableByService].
*/
interface AppServiceHub : ServiceHub {
/**
* Start the given flow with the given arguments. [flow] must be annotated
* with [net.corda.core.flows.StartableByService].
* TODO it is assumed here that the flow object has an appropriate classloader.
*/
fun <T> startFlow(flow: FlowLogic<T>): FlowHandle<T>
/**
* Start the given flow with the given arguments, returning an [Observable] with a single observation of the
* result of running the flow. [flow] must be annotated with [net.corda.core.flows.StartableByService].
* TODO it is assumed here that the flow object has an appropriate classloader.
*/
fun <T> startTrackedFlow(flow: FlowLogic<T>): FlowProgressHandle<T>
}

View File

@ -1,25 +0,0 @@
package net.corda.core.node
import net.corda.core.contracts.ContractState
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.services.VaultQueryService
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.QueryableState
import net.corda.core.serialization.SerializationCustomization
import java.util.function.Function
/**
* Implement this interface on a class advertised in a META-INF/services/net.corda.core.node.CordaPluginRegistry file
* to extend a Corda node with additional application services.
*/
abstract class CordaPluginRegistry {
/**
* Optionally whitelist types for use in object serialization, as we lock down the types that can be serialized.
*
* For example, if you add a new [net.corda.core.contracts.ContractState] it needs to be whitelisted. You can do that
* either by adding the [net.corda.core.serialization.CordaSerializable] annotation or via this method.
**
* @return true if you register types, otherwise you will be filtered out of the list of plugins considered in future.
*/
open fun customizeSerialization(custom: SerializationCustomization): Boolean = false
}

View File

@ -1,30 +1,51 @@
package net.corda.core.node package net.corda.core.node
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.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
/** /**
* Info about a network node that acts on behalf of some form of contract party. * Information about a network node that acts on behalf of some party. NodeInfos can be found via the network map
* @param legalIdentitiesAndCerts is a non-empty list, where the first identity is assumed to be the default identity of the node. * cache, accessible from a [net.corda.core.node.services.NetworkMapCache]. They are also available via RPC
* using the [net.corda.core.messaging.CordaRPCOps.networkMapSnapshot] method.
*
* @property addresses An ordered list of IP addresses/hostnames where the node can be contacted.
* @property legalIdentitiesAndCerts A non-empty list, where the first identity is assumed to be the default identity of the node.
* @property platformVersion An integer representing the set of protocol features the node supports. See the docsite
* for information on how the platform is versioned.
* @property serial An arbitrary number incremented each time the NodeInfo is changed. This is analogous to the same
* concept in DNS.
*/ */
// TODO We currently don't support multi-IP/multi-identity nodes, we only left slots in the data structures.
@CordaSerializable @CordaSerializable
data class NodeInfo(val addresses: List<NetworkHostAndPort>, data class NodeInfo(val addresses: List<NetworkHostAndPort>,
val legalIdentitiesAndCerts: List<PartyAndCertificate>, val legalIdentitiesAndCerts: List<PartyAndCertificate>,
val platformVersion: Int, val platformVersion: Int,
val serial: Long val serial: Long
) { ) {
// TODO We currently don't support multi-IP/multi-identity nodes, we only left slots in the data structures.
init { init {
require(legalIdentitiesAndCerts.isNotEmpty()) { "Node should have at least one legal identity" } require(legalIdentitiesAndCerts.isNotEmpty()) { "Node should have at least one legal identity" }
} }
@Transient private var _legalIdentities: List<Party>? = null @Transient private var _legalIdentities: List<Party>? = null
val legalIdentities: List<Party> get() {
/**
* An ordered list of legal identities supported by this node. The node will always have at least one, so if you
* are porting code from earlier versions of Corda that expected a single party per node, just use the first item
* in the returned list.
*/
val legalIdentities: List<Party>
get() {
return _legalIdentities ?: legalIdentitiesAndCerts.map { it.party }.also { _legalIdentities = it } return _legalIdentities ?: legalIdentitiesAndCerts.map { it.party }.also { _legalIdentities = it }
} }
/** Returns true if [party] is one of the identities of this node, else false. */ /** Returns true if [party] is one of the identities of this node, else false. */
fun isLegalIdentity(party: Party): Boolean = party in legalIdentities fun isLegalIdentity(party: Party): Boolean = party in legalIdentities
fun identityFromX500Name(name: CordaX500Name): Party {
val identity = legalIdentitiesAndCerts.singleOrNull { it.name == name } ?: throw IllegalArgumentException("Node does not have an identity \"$name\"")
return identity.party
}
} }

View File

@ -6,10 +6,7 @@ import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignableData import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.AbstractParty import net.corda.core.flows.ContractUpgradeFlow
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.internal.toMultiMap
import net.corda.core.node.services.* import net.corda.core.node.services.*
import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SerializeAsToken
import net.corda.core.transactions.FilteredTransaction import net.corda.core.transactions.FilteredTransaction
@ -19,13 +16,29 @@ import java.security.PublicKey
import java.sql.Connection import java.sql.Connection
import java.time.Clock import java.time.Clock
/**
* Part of [ServiceHub].
*/
interface StateLoader {
/**
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
*
* @throws TransactionResolutionException if [stateRef] points to a non-existent transaction.
*/
@Throws(TransactionResolutionException::class)
fun loadState(stateRef: StateRef): TransactionState<*>
}
/** /**
* Subset of node services that are used for loading transactions from the wire into fully resolved, looked up * Subset of node services that are used for loading transactions from the wire into fully resolved, looked up
* forms ready for verification. * forms ready for verification.
*
* @see ServiceHub
*/ */
interface ServicesForResolution { interface ServicesForResolution : StateLoader {
/**
* An identity service maintains a directory of parties by their associated distinguished name/public keys and thus
* supports lookup of a party given its key, or name. The service also manages the certificates linking confidential
* identities back to the well known identity (i.e. the identity in the network map) of a party.
*/
val identityService: IdentityService val identityService: IdentityService
/** Provides access to storage of arbitrary JAR files (which may contain only data, no code). */ /** Provides access to storage of arbitrary JAR files (which may contain only data, no code). */
@ -33,28 +46,44 @@ interface ServicesForResolution {
/** Provides access to anything relating to cordapps including contract attachment resolution and app context */ /** Provides access to anything relating to cordapps including contract attachment resolution and app context */
val cordappProvider: CordappProvider val cordappProvider: CordappProvider
/**
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
*
* @throws TransactionResolutionException if the [StateRef] points to a non-existent transaction.
*/
@Throws(TransactionResolutionException::class)
fun loadState(stateRef: StateRef): TransactionState<*>
} }
/** /**
* A service hub simply vends references to the other services a node has. Some of those services may be missing or * A service hub is the starting point for most operations you can do inside the node. You are provided with one
* mocked out. This class is useful to pass to chunks of pluggable code that might have need of many different kinds of * when a class annotated with [CordaService] is constructed, and you have access to one inside flows. Most RPCs
* functionality and you don't want to hard-code which types in the interface. * simply forward to the services found here after some access checking.
* *
* Any services exposed to flows (public view) need to implement [SerializeAsToken] or similar to avoid their internal * The APIs are organised roughly by category, with a few very important top level APIs available on the ServiceHub
* state from being serialized in checkpoints. * itself. Inside a flow, it's safe to keep a reference to services found here on the stack: checkpointing will do the
* right thing (it won't try to serialise the internals of the service).
*
* In unit test environments, some of those services may be missing or mocked out.
*/ */
interface ServiceHub : ServicesForResolution { interface ServiceHub : ServicesForResolution {
// NOTE: Any services exposed to flows (public view) need to implement [SerializeAsToken] or similar to avoid
// their internal state from being serialized in checkpoints.
/**
* The vault service lets you observe, soft lock and add notes to states that involve you or are relevant to your
* node in some way. Additionally you may query and track states that correspond to various criteria.
*/
val vaultService: VaultService val vaultService: VaultService
val vaultQueryService: VaultQueryService
/**
* The key management service is responsible for storing and using private keys to sign things. An
* implementation of this may, for example, call out to a hardware security module that enforces various
* auditing and frequency-of-use requirements.
*
* You don't normally need to use this directly. If you have a [TransactionBuilder] and wish to sign it to
* get a [SignedTransaction], look at [signInitialTransaction].
*/
val keyManagementService: KeyManagementService val keyManagementService: KeyManagementService
/**
* The [ContractUpgradeService] is responsible for securely upgrading contract state objects according to
* a specified and mutually agreed (amongst participants) contract version.
*
* @see ContractUpgradeFlow to understand the workflow associated with contract upgrades.
*/
val contractUpgradeService: ContractUpgradeService val contractUpgradeService: ContractUpgradeService
/** /**
@ -64,9 +93,27 @@ interface ServiceHub : ServicesForResolution {
*/ */
val validatedTransactions: TransactionStorage val validatedTransactions: TransactionStorage
/**
* A network map contains lists of nodes on the network along with information about their identity keys, services
* they provide and host names or IP addresses where they can be connected to. The cache wraps around a map fetched
* from an authoritative service, and adds easy lookup of the data stored within it. Generally it would be initialised
* with a specified network map service, which it fetches data from and then subscribes to updates of.
*/
val networkMapCache: NetworkMapCache val networkMapCache: NetworkMapCache
/**
* INTERNAL. DO NOT USE.
* @suppress
*/
val transactionVerifierService: TransactionVerifierService val transactionVerifierService: TransactionVerifierService
/**
* A [Clock] representing the node's current time. This should be used in preference to directly accessing the
* clock so the current time can be controlled during unit testing.
*/
val clock: Clock val clock: Clock
/** The [NodeInfo] object corresponding to our own entry in the network map. */
val myInfo: NodeInfo val myInfo: NodeInfo
/** /**
@ -109,19 +156,6 @@ interface ServiceHub : ServicesForResolution {
recordTransactions(true, txs) recordTransactions(true, txs)
} }
/**
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
*
* @throws TransactionResolutionException if [stateRef] points to a non-existent transaction.
*/
@Throws(TransactionResolutionException::class)
override fun loadState(stateRef: StateRef): TransactionState<*> {
val stx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
return if (stx.isNotaryChangeTransaction()) {
stx.resolveNotaryChangeTransaction(this).outputs[stateRef.index]
} else stx.tx.outputs[stateRef.index]
}
/** /**
* Converts the given [StateRef] into a [StateAndRef] object. * Converts the given [StateRef] into a [StateAndRef] object.
* *
@ -130,11 +164,7 @@ interface ServiceHub : ServicesForResolution {
@Throws(TransactionResolutionException::class) @Throws(TransactionResolutionException::class)
fun <T : ContractState> toStateAndRef(stateRef: StateRef): StateAndRef<T> { fun <T : ContractState> toStateAndRef(stateRef: StateRef): StateAndRef<T> {
val stx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash) val stx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
return if (stx.isNotaryChangeTransaction()) { return stx.resolveBaseTransaction(this).outRef<T>(stateRef.index)
stx.resolveNotaryChangeTransaction(this).outRef(stateRef.index)
} else {
stx.tx.outRef(stateRef.index)
}
} }
private val legalIdentityKey: PublicKey get() = this.myInfo.legalIdentitiesAndCerts.first().owningKey private val legalIdentityKey: PublicKey get() = this.myInfo.legalIdentitiesAndCerts.first().owningKey
@ -268,9 +298,12 @@ interface ServiceHub : ServicesForResolution {
/** /**
* Exposes a JDBC connection (session) object using the currently configured database. * Exposes a JDBC connection (session) object using the currently configured database.
* Applications can use this to execute arbitrary SQL queries (native, direct, prepared, callable) * Applications can use this to execute arbitrary SQL queries (native, direct, prepared, callable)
* against its Node database tables (including custom contract tables defined by extending [Queryable]). * against its Node database tables (including custom contract tables defined by extending
* [net.corda.core.schemas.QueryableState]).
*
* When used within a flow, this session automatically forms part of the enclosing flow transaction boundary, * When used within a flow, this session automatically forms part of the enclosing flow transaction boundary,
* and thus queryable data will include everything committed as of the last checkpoint. * and thus queryable data will include everything committed as of the last checkpoint.
*
* @throws IllegalStateException if called outside of a transaction. * @throws IllegalStateException if called outside of a transaction.
* @return A new [Connection] * @return A new [Connection]
*/ */

View File

@ -7,7 +7,7 @@ import kotlin.annotation.AnnotationTarget.CLASS
/** /**
* Annotate any class that needs to be a long-lived service within the node, such as an oracle, with this annotation. * Annotate any class that needs to be a long-lived service within the node, such as an oracle, with this annotation.
* Such a class needs to have a constructor with a single parameter of type [ServiceHub]. This constructor will be invoked * Such a class needs to have a constructor with a single parameter of type [AppServiceHub]. This constructor will be invoked
* during node start to initialise the service. The service hub provided can be used to get information about the node * during node start to initialise the service. The service hub provided can be used to get information about the node
* that may be necessary for the service. Corda services are created as singletons within the node and are available * that may be necessary for the service. Corda services are created as singletons within the node and are available
* to flows via [ServiceHub.cordaService]. * to flows via [ServiceHub.cordaService].

View File

@ -1,5 +1,6 @@
package net.corda.core.node.services package net.corda.core.node.services
import net.corda.core.CordaException
import net.corda.core.contracts.PartyAndReference import net.corda.core.contracts.PartyAndReference
import net.corda.core.identity.* import net.corda.core.identity.*
import java.security.InvalidAlgorithmParameterException import java.security.InvalidAlgorithmParameterException
@ -9,7 +10,11 @@ import java.security.cert.*
/** /**
* An identity service maintains a directory of parties by their associated distinguished name/public keys and thus * An identity service maintains a directory of parties by their associated distinguished name/public keys and thus
* supports lookup of a party given its key, or name. The service also manages the certificates linking confidential * supports lookup of a party given its key, or name. The service also manages the certificates linking confidential
* identities back to the well known identity (i.e. the identity in the network map) of a party. * identities back to the well known identity.
*
* Well known identities in Corda are the public identity of a party, registered with the network map directory,
* whereas confidential identities are distributed only on a need to know basis (typically between parties in
* a transaction being built). See [NetworkMapCache] for retrieving well known identities from the network map.
*/ */
interface IdentityService { interface IdentityService {
val trustRoot: X509Certificate val trustRoot: X509Certificate
@ -42,8 +47,9 @@ interface IdentityService {
fun getAllIdentities(): Iterable<PartyAndCertificate> fun getAllIdentities(): Iterable<PartyAndCertificate>
/** /**
* Get the certificate and path for a known identity's owning key. * Resolves a public key to the well known identity [PartyAndCertificate] instance which is owned by the key.
* *
* @param owningKey The [PublicKey] to determine well known identity for.
* @return the party and certificate, or null if unknown. * @return the party and certificate, or null if unknown.
*/ */
fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate?
@ -58,17 +64,18 @@ interface IdentityService {
fun partyFromKey(key: PublicKey): Party? fun partyFromKey(key: PublicKey): Party?
/** /**
* Resolves a party name to the well known identity [Party] instance for this name. * Resolves a party name to the well known identity [Party] instance for this name. Where possible well known identity
* @param name The [CordaX500Name] to search for. * lookup from name should be done from the network map (via [NetworkMapCache]) instead, as it is the authoritative
* source of well known identities.
*
* @param name The [CordaX500Name] to determine well known identity for.
* @return If known the canonical [Party] with that name, else null. * @return If known the canonical [Party] with that name, else null.
*/ */
fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? fun wellKnownPartyFromX500Name(name: CordaX500Name): Party?
/** /**
* Returns the well known identity from an [AbstractParty]. This is intended to resolve the well known identity, * Resolves a (optionally) confidential identity to the corresponding well known identity [Party].
* as visible in the [NetworkMapCache] from a confidential identity. * It transparently handles returning the well known identity back if a well known identity is passed in.
* It transparently handles returning the well known identity back if
* a well known identity is passed in.
* *
* @param party identity to determine well known identity for. * @param party identity to determine well known identity for.
* @return well known identity, if found. * @return well known identity, if found.
@ -76,11 +83,12 @@ interface IdentityService {
fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? fun wellKnownPartyFromAnonymous(party: AbstractParty): Party?
/** /**
* Returns the well known identity from a PartyAndReference. This is intended to resolve the well known identity, * Resolves a (optionally) confidential identity to the corresponding well known identity [Party].
* as visible in the [NetworkMapCache] from a confidential identity. * Convenience method which unwraps the [Party] from the [PartyAndReference] and then resolves the
* It transparently handles returning the well known identity back if * well known identity as normal.
* a well known identity is passed in. * It transparently handles returning the well known identity back if a well known identity is passed in.
* *
* @param partyRef identity (and reference, which is unused) to determine well known identity for.
* @return the well known identity, or null if unknown. * @return the well known identity, or null if unknown.
*/ */
fun wellKnownPartyFromAnonymous(partyRef: PartyAndReference) = wellKnownPartyFromAnonymous(partyRef.party) fun wellKnownPartyFromAnonymous(partyRef: PartyAndReference) = wellKnownPartyFromAnonymous(partyRef.party)
@ -105,4 +113,4 @@ interface IdentityService {
fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> fun partiesFromName(query: String, exactMatch: Boolean): Set<Party>
} }
class UnknownAnonymousPartyException(msg: String) : Exception(msg) class UnknownAnonymousPartyException(msg: String) : CordaException(msg)

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