Merge branch 'master' of https://github.com/corda/enterprise into christians_perftestflows

This commit is contained in:
Christian Sailer 2017-10-17 15:29:03 +01:00
commit 1340b037c6
710 changed files with 20100 additions and 11193 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/dataSources
.idea/markdown-navigator
.idea/runConfigurations
/gradle-plugins/.idea/
# Include the -parameters compiler option by default in IntelliJ required for serialization.
!.idea/compiler.xml
!.idea/codeStyleSettings.xml
# if you remove the above rule, at least ignore the following:
@ -88,6 +90,9 @@ docs/virtualenv/
# bft-smart
**/config/currentView
# Node Explorer
/tools/explorer/conf/CordaExplorer.properties
# vim
*.swp
*.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">
<component name="CompilerConfiguration">
<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_main" 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_main" 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_test" 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_main" 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_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_test" 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_main" 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_test" 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)
<a href="https://ci-master.corda.r3cev.com/viewType.html?buildTypeId=CordaEnterprise_Build&tab=buildTypeStatusDiv"><img src="https://ci.corda.r3cev.com/app/rest/builds/buildType:Corda_CordaBuild/statusIcon"/></a>
# Corda Enterprise
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.
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
* Doorman
@ -12,3 +25,36 @@ plan to charge for.
* Flow triage screen in Explorer
* No stupid jokes at startup
* 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.jansi_version = '1.14'
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.dokka_version = '0.9.14'
ext.eddsa_version = '0.2.0'
@ -59,7 +59,9 @@ buildscript {
classpath "net.corda.plugins:publish-utils:$gradle_plugins_version"
classpath "net.corda.plugins:quasar-utils:$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.dokka:dokka-gradle-plugin:${dokka_version}"
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 {
attributes('Corda-Release-Version': corda_release_version)
attributes('Corda-Platform-Version': corda_platform_version)
attributes('Corda-Revision': corda_revision)
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.
dependencies {
cordaCompile project(':node')
compile project(':node')
compile "com.google.guava:guava:$guava_version"
// Set to corda compile to ensure it exists now deploy nodes no longer relies on build
cordaCompile project(path: ":node:capsule", configuration: 'runtimeArtifacts')
cordaCompile project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts')
compile project(path: ":node:capsule", configuration: 'runtimeArtifacts')
compile project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts')
// For the buildCordappDependenciesJar task
cordaRuntime project(':client:jfx')
cordaRuntime project(':client:mock')
cordaRuntime project(':client:rpc')
cordaRuntime project(':core')
cordaRuntime project(':confidential-identities')
cordaRuntime project(':finance')
cordaRuntime project(':webserver')
runtime project(':client:jfx')
runtime project(':client:mock')
runtime project(':client:rpc')
runtime project(':core')
runtime project(':confidential-identities')
runtime project(':finance')
runtime project(':webserver')
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) {
dependsOn = subprojects.test
additionalSourceDirs = files(subprojects.sourceSets.main.allSource.srcDirs)
@ -223,16 +231,14 @@ tasks.withType(Test) {
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
directory "./build/nodes"
networkMap "O=Controller,OU=corda,L=London,C=GB"
node {
name "O=Controller,OU=corda,L=London,C=GB"
advertisedServices = ["corda.notary.validating"]
notary = [validating : true]
p2pPort 10002
cordapps = []
}
node {
name "O=Bank A,OU=corda,L=London,C=GB"
advertisedServices = []
p2pPort 10012
rpcPort 10013
webPort 10014
@ -240,7 +246,6 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
}
node {
name "O=Bank B,OU=corda,L=London,C=GB"
advertisedServices = []
p2pPort 10007
rpcPort 10008
webPort 10009
@ -289,12 +294,20 @@ artifactory {
publish {
contextUrl = 'https://ci-artifactory.corda.r3cev.com/artifactory'
repository {
repoKey = 'corda-releases'
repoKey = 'enterprise-dev'
username = 'teamcity'
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD')
}
defaults {
publications('corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'cordform-common', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver', 'corda-node-driver', 'corda-confidential-identities')
// Root project applies the plugin (for this block) but does not need to be published
if(project != rootProject) {
publications(project.extensions.publish.name())
}
}
}
}
task generateApi(type: net.corda.plugins.GenerateApi){
baseName = "api-corda"
}

View File

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

View File

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

View File

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

View File

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

View File

@ -14,15 +14,16 @@ import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.MINI_CORP
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.contracts.DummyContract
import net.i2p.crypto.eddsa.EdDSAPublicKey
import org.junit.Before
import org.junit.Test
import java.math.BigInteger
import java.security.PublicKey
import java.util.*
import kotlin.reflect.jvm.jvmName
import kotlin.test.assertEquals
class JacksonSupportTest : TestDependencyInjectionBase() {
companion object {
private val SEED = BigInteger.valueOf(20170922L)
val mapper = JacksonSupport.createNonRpcMapper()
}
@ -37,15 +38,34 @@ class JacksonSupportTest : TestDependencyInjectionBase() {
}
@Test
fun publicKeySerializingWorks() {
val publicKey = generateKeyPair().public
fun `should serialize Composite keys`() {
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 parsedKey = mapper.readValue(serialized, EdDSAPublicKey::class.java)
assertEquals(expected, serialized)
val parsedKey = mapper.readValue(serialized, PublicKey::class.java)
assertEquals(publicKey, parsedKey)
}
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
fun readAmount() {
val oldJson = """
@ -72,10 +92,10 @@ class JacksonSupportTest : TestDependencyInjectionBase() {
fun writeTransaction() {
val attachmentRef = SecureHash.randomSHA256()
whenever(cordappProvider.getContractAttachmentID(DummyContract.PROGRAM_ID))
.thenReturn(attachmentRef)
.thenReturn(attachmentRef)
fun makeDummyTx(): SignedTransaction {
val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1))
.toWireTransaction(services)
.toWireTransaction(services)
val signatures = TransactionSignature(
ByteArray(1),
ALICE_PUBKEY,

View File

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

View File

@ -27,8 +27,6 @@ import net.corda.finance.flows.CashExitFlow
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
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.testing.*
import net.corda.testing.driver.driver
@ -59,7 +57,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
startFlowPermission<CashExitFlow>())
)
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()
aliceNode = aliceNodeHandle.nodeInfo
newNode = { nodeName -> startNode(providedName = nodeName).getOrThrow().nodeInfo }
@ -71,7 +69,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
vaultUpdates = monitor.vaultUpdates.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!!
notaryParty = notaryHandle.nodeInfo.legalIdentities[1]
@ -79,7 +77,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
bobNode = bobNodeHandle.nodeInfo
val monitorBob = NodeMonitorModel()
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!!
runTest()
}

View File

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

View File

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

View File

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

View File

@ -38,11 +38,14 @@ class NetworkIdentityModel {
})
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()
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 }
fun partyFromPublicKey(publicKey: PublicKey): ObservableValue<NodeInfo?> = identityCache[publicKey]

View File

@ -55,13 +55,12 @@ class NodeMonitorModel {
* Register for updates to/from a given vault.
* 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(
hostAndPort = nodeHostAndPort,
configuration = CordaRPCClientConfiguration.default.copy(
nodeHostAndPort,
CordaRPCClientConfiguration.DEFAULT.copy(
connectionMaxRetryInterval = 10.seconds
),
initialiseSerialization = initialiseSerialization
)
)
val connection = client.start(username, password)
val proxy = connection.proxy

View File

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

View File

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

View File

@ -1,4 +1,5 @@
@file:JvmName("ObservableUtilities")
package net.corda.client.jfx.utils
import javafx.application.Platform
@ -14,6 +15,7 @@ import javafx.collections.ObservableMap
import javafx.collections.transformation.FilteredList
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.DataFeed
import net.corda.core.node.services.Vault
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.
*/
fun <A, B> ObservableValue<out A>.bindOut(function: (A) -> ObservableValue<out B>): ObservableValue<out B> =
@Suppress("UNCHECKED_CAST")
EasyBind.monadic(this).flatMap(function as (A) -> ObservableValue<B>)
EasyBind.monadic(this).flatMap(uncheckedCast(function))
/**
* 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> {
// We cast here to enforce variance, FilteredList should be covariant
@Suppress("UNCHECKED_CAST")
return FilteredList<A>(this as ObservableList<A>).apply {
return FilteredList<A>(uncheckedCast(this)).apply {
predicateProperty().bind(predicate.map { predicateFunction ->
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> {
//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 (this as ObservableList<A?>).filtered(object : Predicate<A?> {
return uncheckedCast(uncheckedCast<Any, ObservableList<A?>>(this).filtered(object : Predicate<A?> {
override fun test(t: A?): Boolean {
return t != null
}
}) as ObservableList<A>
}))
}
/**

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
package net.corda.client.mock
import net.corda.client.mock.Generator.Companion.choice
import net.corda.core.internal.uncheckedCast
import net.corda.core.utilities.Try
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> sequence(generators: List<Generator<A>>) = Generator {
fun <A> sequence(generators: List<Generator<A>>) = Generator<List<A>> {
val result = mutableListOf<A>()
for (generator in generators) {
val element = generator.generate(it)
@Suppress("UNCHECKED_CAST")
when (element) {
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)
@ -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 result = mutableListOf<A>()
var finish = false
@ -191,8 +191,7 @@ class Generator<out A>(val generate: (SplittableRandom) -> Try<A>) {
}
}
if (res is Try.Failure) {
@Suppress("UNCHECKED_CAST")
return@Generator res as Try<List<A>>
return@Generator uncheckedCast(res)
}
}
Try.Success(result)

View File

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

View File

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

View File

@ -1,6 +1,5 @@
package net.corda.client.rpc;
import net.corda.client.rpc.internal.RPCClient;
import net.corda.core.concurrent.CordaFuture;
import net.corda.core.contracts.Amount;
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.CashIssueFlow;
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.StartedNode;
import net.corda.node.services.transactions.ValidatingNotaryService;
import net.corda.nodeapi.internal.ServiceInfo;
import net.corda.nodeapi.User;
import net.corda.testing.CoreTestUtils;
import net.corda.testing.node.NodeBasedTest;
@ -25,24 +22,26 @@ import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import static java.util.Collections.*;
import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;
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.contracts.GetBalances.getCashBalance;
import static net.corda.node.services.FlowPermissions.startFlowPermission;
import static net.corda.testing.CoreTestUtils.*;
import static net.corda.testing.TestConstants.getALICE;
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 Set<String> permSet = new HashSet<>(perms);
private User rpcUser = new User("user1", "test", permSet);
private StartedNode<Node> node;
private CordaRPCClient client;
private RPCClient.RPCConnection<CordaRPCOps> connection = null;
private RPCConnection<CordaRPCOps> connection = null;
private CordaRPCOps rpcProxy;
private void login(String username, String password) {
@ -52,18 +51,14 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
@Before
public void setUp() throws ExecutionException, InterruptedException {
setCordappPackages("net.corda.finance.contracts");
Set<ServiceInfo> services = new HashSet<>(singletonList(new ServiceInfo(ValidatingNotaryService.Companion.getType(), null)));
CordaFuture<StartedNode<Node>> nodeFuture = startNode(getALICE().getName(), 1, services, singletonList(rpcUser), emptyMap());
CordaFuture<StartedNode<Node>> nodeFuture = startNotaryNode(getALICE().getName(), singletonList(rpcUser), true);
node = nodeFuture.get();
node.getInternals().registerCustomSchemas(Collections.singleton(CashSchemaV1.INSTANCE));
client = new CordaRPCClient(requireNonNull(node.getInternals().getConfiguration().getRpcAddress()), getDefault(), false);
client = new CordaRPCClient(requireNonNull(node.getInternals().getConfiguration().getRpcAddress()));
}
@After
public void done() throws IOException {
connection.close();
unsetCordappPackages();
}
@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.flows.FlowInitiator
import net.corda.core.internal.packageName
import net.corda.core.messaging.FlowProgressHandle
import net.corda.core.messaging.StateMachineUpdate
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.StartedNode
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.testing.ALICE
import net.corda.testing.chooseIdentity
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.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.After
@ -36,7 +33,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertFalse
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(
startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>()
@ -51,16 +48,13 @@ class CordaRPCClientTest : NodeBasedTest() {
@Before
fun setUp() {
setCordappPackages("net.corda.finance.contracts")
node = startNode(ALICE.name, rpcUsers = listOf(rpcUser), advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type))).getOrThrow()
node.internals.registerCustomSchemas(setOf(CashSchemaV1))
client = CordaRPCClient(node.internals.configuration.rpcAddress!!, initialiseSerialization = false)
node = startNotaryNode(ALICE.name, rpcUsers = listOf(rpcUser)).getOrThrow()
client = CordaRPCClient(node.internals.configuration.rpcAddress!!)
}
@After
fun done() {
connection?.close()
unsetCordappPackages()
}
@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.messaging.RPCOps
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.serialize
import net.corda.core.utilities.*
import net.corda.node.services.messaging.RPCServerConfiguration
import net.corda.nodeapi.RPCApi
@ -75,10 +76,12 @@ class RPCStabilityTests {
rpcDriver {
Try.on { startRpcClient<RPCOps>(NetworkHostAndPort("localhost", 9999)).get() }
val server = startRpcServer<RPCOps>(ops = DummyOps)
Try.on { startRpcClient<RPCOps>(
server.get().broker.hostAndPort!!,
configuration = RPCClientConfiguration.default.copy(minimumServerProtocolVersion = 1)
).get() }
Try.on {
startRpcClient<RPCOps>(
server.get().broker.hostAndPort!!,
configuration = RPCClientConfiguration.default.copy(minimumServerProtocolVersion = 1)
).get()
}
}
}
repeat(5) {
@ -172,7 +175,7 @@ class RPCStabilityTests {
}
}
interface LeakObservableOps: RPCOps {
interface LeakObservableOps : RPCOps {
fun leakObservable(): Observable<Nothing>
}
@ -248,6 +251,7 @@ class RPCStabilityTests {
val trackSubscriberCountObservable = UnicastSubject.create<Unit>().share().
doOnSubscribe { subscriberCount.incrementAndGet() }.
doOnUnsubscribe { subscriberCount.decrementAndGet() }
override fun subscribe(): Observable<Unit> {
return trackSubscriberCountObservable
}
@ -260,7 +264,7 @@ class RPCStabilityTests {
).get()
val numberOfClients = 4
val clients = (1 .. numberOfClients).map {
val clients = (1..numberOfClients).map {
startRandomRpcClient<TrackSubscriberOps>(server.broker.hostAndPort!!)
}.transpose().get()
@ -271,7 +275,7 @@ class RPCStabilityTests {
clients[0].destroyForcibly()
pollUntilClientNumber(server, numberOfClients - 1)
// Kill the rest
(1 .. numberOfClients - 1).forEach {
(1..numberOfClients - 1).forEach {
clients[it].destroyForcibly()
}
pollUntilClientNumber(server, 0)
@ -283,6 +287,7 @@ class RPCStabilityTests {
interface SlowConsumerRPCOps : RPCOps {
fun streamAtInterval(interval: Duration, size: Int): Observable<ByteArray>
}
class SlowConsumerRPCOpsImpl : SlowConsumerRPCOps {
override val protocolVersion = 0
@ -291,6 +296,7 @@ class RPCStabilityTests {
return Observable.interval(interval.toMillis(), TimeUnit.MILLISECONDS).map { chunk }
}
}
@Test
fun `slow consumers are kicked`() {
rpcDriver {
@ -315,9 +321,9 @@ class RPCStabilityTests {
clientAddress = SimpleString(myQueue),
id = RPCApi.RpcRequestId(random63BitValue()),
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)
session.commit()

View File

@ -10,42 +10,68 @@ import net.corda.nodeapi.ConnectionDirection
import net.corda.nodeapi.internal.serialization.KRYO_RPC_CLIENT_CONTEXT
import java.time.Duration
/** @see RPCClient.RPCConnection */
class CordaRPCConnection internal constructor(
connection: RPCClient.RPCConnection<CordaRPCOps>
) : RPCClient.RPCConnection<CordaRPCOps> by connection
/**
* This class is essentially just a wrapper for an RPCConnection<CordaRPCOps> and can be treated identically.
*
* @see RPCConnection
*/
class CordaRPCConnection internal constructor(connection: RPCConnection<CordaRPCOps>) : RPCConnection<CordaRPCOps> by connection
/** @see RPCClientConfiguration */
data class CordaRPCClientConfiguration(
val connectionMaxRetryInterval: Duration
) {
/**
* Can be used to configure the RPC client connection.
*
* @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 {
return RPCClientConfiguration.default.copy(
connectionMaxRetryInterval = connectionMaxRetryInterval
)
}
companion object {
@JvmStatic
val default = CordaRPCClientConfiguration(
connectionMaxRetryInterval = RPCClientConfiguration.default.connectionMaxRetryInterval
)
/**
* Returns the default configuration we recommend you use.
*/
@JvmField
val DEFAULT = CordaRPCClientConfiguration(connectionMaxRetryInterval = RPCClientConfiguration.default.connectionMaxRetryInterval)
}
}
/** @see RPCClient */
//TODO Add SSL support
class CordaRPCClient(
/**
* An RPC client connects to the specified server and allows you to make calls to the server that perform various
* 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,
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default,
initialiseSerialization: Boolean = true
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT
) {
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?
if (initialiseSerialization) {
KryoClientSerializationScheme.initialiseSerialization()
}
KryoClientSerializationScheme.initialiseSerialization()
}
private val rpcClient = RPCClient<CordaRPCOps>(
@ -54,10 +80,24 @@ class CordaRPCClient(
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 {
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 {
return start(username, password).use(block)
}

View File

@ -1,10 +1,10 @@
package net.corda.client.rpc
import net.corda.core.CordaRuntimeException
import net.corda.core.serialization.CordaSerializable
/**
* Thrown to indicate that the calling user does not have permission for something they have requested (for example
* calling a method).
*/
@CordaSerializable
class PermissionException(msg: String) : RuntimeException(msg)
class PermissionException(msg: String) : CordaRuntimeException(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.utilities.ByteSequence
import net.corda.nodeapi.internal.serialization.*
import java.util.concurrent.atomic.AtomicBoolean
class KryoClientSerializationScheme : AbstractKryoSerializationScheme() {
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
@ -23,7 +24,9 @@ class KryoClientSerializationScheme : AbstractKryoSerializationScheme() {
override fun rpcServerKryoPool(context: SerializationContext): KryoPool = throw UnsupportedOperationException()
companion object {
val isInitialised = AtomicBoolean(false)
fun initialiseSerialization() {
if (!isInitialised.compareAndSet(false, true)) return
try {
SerializationDefaults.SERIALIZATION_FACTORY = SerializationFactoryImpl().apply {
registerScheme(KryoClientSerializationScheme())
@ -31,10 +34,14 @@ class KryoClientSerializationScheme : AbstractKryoSerializationScheme() {
}
SerializationDefaults.P2P_CONTEXT = KRYO_P2P_CONTEXT
SerializationDefaults.RPC_CLIENT_CONTEXT = KRYO_RPC_CLIENT_CONTEXT
} catch(e: IllegalStateException) {
} catch (e: IllegalStateException) {
// Check that it's registered as we expect
check(SerializationDefaults.SERIALIZATION_FACTORY is SerializationFactoryImpl) { "RPC client encountered conflicting configuration of serialization subsystem." }
check((SerializationDefaults.SERIALIZATION_FACTORY as SerializationFactoryImpl).alreadyRegisteredSchemes.any { it is KryoClientSerializationScheme }) { "RPC client encountered conflicting configuration of serialization subsystem." }
val factory = SerializationDefaults.SERIALIZATION_FACTORY
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
import net.corda.client.rpc.RPCConnection
import net.corda.client.rpc.RPCException
import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.logElapsedTime
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.RPCOps
import net.corda.core.serialization.SerializationContext
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.TransportConfiguration
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
import java.io.Closeable
import java.lang.reflect.Proxy
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>(
val transport: TransportConfiguration,
val rpcConfiguration: RPCClientConfiguration = RPCClientConfiguration.default,
@ -101,54 +96,6 @@ class RPCClient<I : RPCOps>(
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(
rpcOpsClass: Class<I>,
username: String,
@ -168,10 +115,7 @@ class RPCClient<I : RPCOps>(
val proxyHandler = RPCClientProxyHandler(rpcConfiguration, username, password, serverLocator, clientAddress, rpcOpsClass, serializationContext)
try {
proxyHandler.start()
@Suppress("UNCHECKED_CAST")
val ops = Proxy.newProxyInstance(rpcOpsClass.classLoader, arrayOf(rpcOpsClass), proxyHandler) as I
val ops: I = uncheckedCast(Proxy.newProxyInstance(rpcOpsClass.classLoader, arrayOf(rpcOpsClass), proxyHandler))
val serverProtocolVersion = ops.protocolVersion
if (serverProtocolVersion < rpcConfiguration.minimumServerProtocolVersion) {
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.messaging.RPCOps
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.serialize
import net.corda.core.utilities.Try
import net.corda.core.utilities.debug
import net.corda.core.utilities.getOrThrow
@ -78,6 +79,7 @@ class RPCClientProxyHandler(
STARTED,
FINISHED
}
private val lifeCycle = LifeCycle(State.UNSTARTED)
private companion object {
@ -208,11 +210,12 @@ class RPCClientProxyHandler(
val rpcId = RPCApi.RpcRequestId(random63BitValue())
callSiteMap?.set(rpcId.toLong, Throwable("<Call site of root RPC '${method.name}'>"))
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>()
sessionAndProducerPool.run {
val message = it.session.createMessage(false)
request.writeToClientMessage(serializationContextWithObservableContext, message)
request.writeToClientMessage(message)
log.debug {
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.messaging.CordaRPCOps;
import net.corda.core.messaging.FlowHandle;
import net.corda.core.node.NodeInfo;
import net.corda.core.utilities.OpaqueBytes;
import net.corda.finance.flows.AbstractCashFlow;
import net.corda.finance.flows.CashIssueFlow;
@ -41,7 +40,6 @@ public class StandaloneCordaRPCJavaClientTest {
private NodeProcess notary;
private CordaRPCOps rpcProxy;
private CordaRPCConnection connection;
private NodeInfo notaryNode;
private Party notaryNodeIdentity;
private NodeConfig notaryConfig = new NodeConfig(
@ -49,8 +47,8 @@ public class StandaloneCordaRPCJavaClientTest {
port.getAndIncrement(),
port.getAndIncrement(),
port.getAndIncrement(),
Collections.singletonList("corda.notary.validating"),
Arrays.asList(rpcUser),
true,
Collections.singletonList(rpcUser),
null
);
@ -61,7 +59,6 @@ public class StandaloneCordaRPCJavaClientTest {
notary = factory.create(notaryConfig);
connection = notary.connect();
rpcProxy = connection.getProxy();
notaryNode = fetchNotaryIdentity();
notaryNodeIdentity = rpcProxy.nodeInfo().getLegalIdentities().get(0);
}
@ -70,16 +67,16 @@ public class StandaloneCordaRPCJavaClientTest {
try {
connection.close();
} finally {
if(notary != null) {
if (notary != null) {
notary.close();
}
}
}
private void copyFinanceCordapp() {
Path pluginsDir = (factory.baseDirectory(notaryConfig).resolve("plugins"));
Path cordappsDir = (factory.baseDirectory(notaryConfig).resolve("cordapps"));
try {
Files.createDirectories(pluginsDir);
Files.createDirectories(cordappsDir);
} catch (IOException ex) {
fail("Failed to create directories");
}
@ -87,7 +84,7 @@ public class StandaloneCordaRPCJavaClientTest {
paths.forEach(file -> {
if (file.toString().contains("corda-finance")) {
try {
Files.copy(file, pluginsDir.resolve(file.getFileName()));
Files.copy(file, cordappsDir.resolve(file.getFileName()));
} catch (IOException ex) {
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
public void testCashBalances() throws NoSuchFieldException, ExecutionException, InterruptedException {
Amount<Currency> dollars123 = new Amount<>(123, Currency.getInstance("USD"));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,14 +12,12 @@ import net.corda.core.utilities.unwrap
object IdentitySyncFlow {
/**
* Flow for ensuring that one or more counterparties to a transaction have the full certificate paths of confidential
* identities used in the transaction. This is intended for use as a subflow of another flow, typically between
* Flow for ensuring that our counterparties in a transaction have the full certificate paths for *our* confidential
* 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
* 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
* identity.
*
* @return a mapping of well known identities to the confidential identities used in the transaction.
*/
// TODO: Can this be triggered automatically from [SendTransactionFlow]
class Send(val otherSideSessions: Set<FlowSession>,
@ -36,17 +34,10 @@ object IdentitySyncFlow {
@Suspendable
override fun call() {
progressTracker.currentStep = SYNCING_IDENTITIES
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 { 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()
val identityCertificates: Map<AbstractParty, PartyAndCertificate?> = extractOurConfidentialIdentities()
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}" }
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
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.InitiatingFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.toX509CertHolder
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.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
@ -27,8 +44,32 @@ class SwapIdentitiesFlow(private val otherParty: Party,
object AWAITING_KEY : ProgressTracker.Step("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
// counterparty.
identityService.verifyAndRegisterIdentity(anonymousOtherSide)
@ -40,6 +81,7 @@ class SwapIdentitiesFlow(private val otherParty: Party,
override fun call(): LinkedHashMap<Party, AnonymousParty> {
progressTracker.currentStep = AWAITING_KEY
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
val identities = LinkedHashMap<Party, AnonymousParty>()
@ -47,13 +89,33 @@ class SwapIdentitiesFlow(private val otherParty: Party,
identities.put(otherParty, legalIdentityAnonymous.party.anonymise())
} else {
val otherSession = initiateFlow(otherParty)
val anonymousOtherSide = otherSession.sendAndReceive<PartyAndCertificate>(legalIdentityAnonymous).unwrap { confidentialIdentity ->
validateAndRegisterIdentity(serviceHub.identityService, otherSession.counterparty, confidentialIdentity)
}
val data = buildDataToSign(legalIdentityAnonymous)
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(otherSession.counterparty, anonymousOtherSide.party.anonymise())
identities.put(otherParty, anonymousOtherSide.party.anonymise())
}
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.FlowSession
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.unwrap
@ -20,9 +23,14 @@ class SwapIdentitiesHandler(val otherSideSession: FlowSession, val revocationEna
override fun call() {
val revocationEnabled = false
progressTracker.currentStep = SENDING_KEY
val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, revocationEnabled)
otherSideSession.sendAndReceive<PartyAndCertificate>(legalIdentityAnonymous).unwrap { confidentialIdentity ->
SwapIdentitiesFlow.validateAndRegisterIdentity(serviceHub.identityService, otherSideSession.counterparty, confidentialIdentity)
}
val ourConfidentialIdentity = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, revocationEnabled)
val serializedIdentity = SerializedBytes<PartyAndCertificate>(ourConfidentialIdentity.serialize().bytes)
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.contracts.asset.Cash
import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.testing.*
import net.corda.testing.node.MockNetwork
import org.junit.After
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
class IdentitySyncFlowTests {
@ -26,31 +28,29 @@ class IdentitySyncFlowTests {
@Before
fun before() {
setCordappPackages("net.corda.finance.contracts.asset")
// 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
fun cleanUp() {
mockNet.stopNodes()
unsetCordappPackages()
}
@Test
fun `sync confidential identities`() {
// Set up values we'll need
val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name)
val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name)
val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name)
val alice: Party = aliceNode.services.myInfo.chooseIdentity()
val bob: Party = bobNode.services.myInfo.chooseIdentity()
val notaryNode = mockNet.createNotaryNode()
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
val bobNode = mockNet.createPartyNode(BOB_NAME)
val alice: Party = aliceNode.info.singleIdentity()
val bob: Party = bobNode.info.singleIdentity()
val notary = notaryNode.services.getDefaultNotary()
bobNode.internals.registerInitiatedFlow(Receive::class.java)
// Alice issues then pays some cash to a new confidential identity that Bob doesn't know about
val anonymous = true
val ref = OpaqueBytes.of(0x01)
val notary = aliceNode.services.getDefaultNotary()
val issueFlow = aliceNode.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, alice, anonymous, notary))
val issueTx = issueFlow.resultFuture.getOrThrow().stx
val confidentialIdentity = issueTx.tx.outputs.map { it.data }.filterIsInstance<Cash.State>().single().owner
@ -67,6 +67,40 @@ class IdentitySyncFlowTests {
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.
*/

View File

@ -4,16 +4,10 @@ import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.utilities.getOrThrow
import net.corda.testing.ALICE
import net.corda.testing.BOB
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.chooseIdentity
import net.corda.testing.*
import net.corda.testing.node.MockNetwork
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
import kotlin.test.*
class SwapIdentitiesFlowTests {
@Test
@ -22,12 +16,11 @@ class SwapIdentitiesFlowTests {
val mockNet = MockNetwork(false, true)
// Set up values we'll need
val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name)
val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name)
val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name)
val alice: Party = aliceNode.services.myInfo.chooseIdentity()
val bob: Party = bobNode.services.myInfo.chooseIdentity()
mockNet.registerIdentities()
val notaryNode = mockNet.createNotaryNode()
val aliceNode = mockNet.createPartyNode(ALICE.name)
val bobNode = mockNet.createPartyNode(BOB.name)
val alice = aliceNode.info.singleIdentity()
val bob = bobNode.services.myInfo.singleIdentity()
// Run the flows
val requesterFlow = aliceNode.services.startFlow(SwapIdentitiesFlow(bob))
@ -53,4 +46,69 @@ class SwapIdentitiesFlowTests {
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"
rpcAddress : "localhost:10003"
webAddress : "localhost:10004"
extraAdvertisedServiceIds : [ "corda.interest_rates" ]
networkMapService : {
address : "localhost:10000"
legalName : "O=Network Map Service,OU=corda,L=London,C=GB"

View File

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

View File

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

View File

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

View File

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

View File

@ -15,10 +15,11 @@ interface CordaThrowable {
open class CordaException internal constructor(override var originalExceptionClassName: String? = null,
private var _message: String? = null,
private var _cause: Throwable? = null) : Exception(null, null, true, true), CordaThrowable {
constructor(message: String?,
cause: Throwable?) : this(null, message, cause)
constructor(message: String?) : this(null, message, null)
override val message: String?
get() = if (originalExceptionClassName == null) originalMessage else {
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?,
private var _message: String? = null,
private var _cause: Throwable? = null) : RuntimeException(null, null, true, true), CordaThrowable {
private var _message: String?,
private var _cause: Throwable?) : RuntimeException(null, null, true, true), CordaThrowable {
constructor(message: String?, cause: Throwable?) : this(null, message, cause)
constructor(message: String?) : this(null, message, null)
override val message: String?
get() = if (originalExceptionClassName == null) originalMessage else {
if (originalMessage == null) "$originalExceptionClassName" else "$originalExceptionClassName: $originalMessage"

View File

@ -1,4 +1,5 @@
@file:JvmName("ConcurrencyUtils")
package net.corda.core.concurrent
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")
@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> {
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
* 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
* 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
* 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
* overflow.
*
* @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 token an instance of type T, 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.
* @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 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
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 {
/**
* 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
* getDisplayTokenSize is used to determine the conversion scaling.
* e.g. Bonds might be in nominal amounts of 100, currencies in 0.01 penny units.
* returns an amount with a quantity of "1234" tokens. The function [getDisplayTokenSize] is used to determine the
* conversion scaling, for example 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.
*/
@JvmStatic
@ -166,7 +167,7 @@ data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal,
}
}
}
} catch(e: Exception) {
} catch (e: Exception) {
throw IllegalArgumentException("Could not parse $input as a currency", e)
}
throw IllegalArgumentException("Did not recognise the currency in $input or could not parse")
@ -195,6 +196,7 @@ data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal,
* Mixing non-identical token types will throw [IllegalArgumentException].
*
* @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> {
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.
* If this leads to the Amount going negative this will throw [IllegalArgumentException].
* Mixing non-identical token types will throw [IllegalArgumentException].
* A checked subtraction operator is supported to simplify netting of Amounts.
*
* @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> {
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.
* 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.
*
* @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)
@ -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.
* 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.
*
* @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)
/**
* This method provides a token conserving divide mechanism.
* @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.
*/
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
* of "1234" GBP, returns "12.34". The precise representation is controlled by the displayTokenSize,
* which determines the size of a single token and controls the trailing decimal places via it's scale property.
* of "1234" GBP, returns "12.34". The precise representation is controlled by the display token size (
* 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
*/

View File

@ -4,6 +4,7 @@ package net.corda.core.contracts
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.internal.uncheckedCast
import java.security.PublicKey
import java.util.*
@ -33,7 +34,7 @@ inline fun <R> requireThat(body: Requirements.() -> R) = Requirements.body()
/** Filters the command list by type, party and public key all at once. */
inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>.select(signer: PublicKey? = null,
party: AbstractParty? = null) =
party: AbstractParty? = null) =
filter { it.value is T }.
filter { if (signer == null) true else signer in it.signers }.
filter { if (party == null) true else party in it.signingParties }.
@ -43,7 +44,7 @@ inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>
/** Filters the command list by type, parties and public keys all at once. */
inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>.select(signers: Collection<PublicKey>?,
parties: Collection<Party>?) =
parties: Collection<Party>?) =
filter { it.value is T }.
filter { if (signers == null) true else it.signers.containsAll(signers) }.
filter { if (parties == null) true else it.signingParties.containsAll(parties) }.
@ -58,7 +59,7 @@ inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>
/** Ensures that a transaction has only one command that is of the given type, otherwise throws an exception. */
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.

View File

@ -199,9 +199,6 @@ interface MoveCommand : CommandData {
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
/** A [Command] where the signing parties have been looked up if they have a well known/recognised institutional key. */
@CordaSerializable

View File

@ -64,6 +64,17 @@ abstract class TimeWindow {
*/
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]. */
abstract operator fun contains(instant: Instant): Boolean
@ -85,6 +96,7 @@ abstract class TimeWindow {
init {
require(fromTime < untilTime) { "fromTime must be earlier than untilTime" }
}
override val midpoint: Instant get() = fromTime + (fromTime until untilTime) / 2
override fun contains(instant: Instant): Boolean = instant >= fromTime && instant < untilTime
override fun toString(): String = "[$fromTime, $untilTime)"

View File

@ -1,8 +1,8 @@
package net.corda.core.cordapp
import net.corda.core.flows.FlowLogic
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.SerializeAsToken
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.
*
* @property name Cordapp name - derived from the base name of the Cordapp JAR (therefore may not be unique)
* @property contractClassNames List of contracts
* @property initiatedFlows List of initiatable flow 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 plugins List of Corda plugin registries
* @property serializationWhitelists List of Corda plugin registries
* @property customSchemas List of custom schemas
* @property jarPath The path to the JAR for this CorDapp
*/
interface Cordapp {
val name: String
val contractClassNames: List<String>
val initiatedFlows: 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 plugins: List<CordaPluginRegistry>
val serializationWhitelists: List<SerializationWhitelist>
val customSchemas: Set<MappedSchema>
val jarPath: URL
val cordappClasses: List<String>

View File

@ -1,6 +1,5 @@
package net.corda.core.crypto
import net.corda.core.crypto.composite.CompositeSignaturesWithKeys
import net.corda.core.serialization.deserialize
import java.io.ByteArrayOutputStream
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
/**

View File

@ -31,6 +31,8 @@ class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME secur
object CordaObjectIdentifier {
// UUID-based OID
// TODO: Register for an OID space and issue our own shorter OID.
@JvmField val COMPOSITE_KEY = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791002")
@JvmField val COMPOSITE_SIGNATURE = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791003")
@JvmField
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.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.spec.EdDSANamedCurveSpec
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 java.math.BigInteger
import java.security.*
import java.security.KeyFactory
import java.security.KeyPairGenerator
import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
@ -148,7 +149,7 @@ object Crypto {
"at the cost of larger key sizes and loss of compatibility."
)
/** Corda composite key type */
/** Corda composite key type. */
@JvmField
val COMPOSITE_KEY = SignatureScheme(
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.
val pointQ = FixedPointCombMultiplier().multiply(parameterSpec.g, deterministicD)
// 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).
return deriveKeyPairECDSA(parameterSpec, privateKey, seed.sha256().bytes)
}
val publicKeySpec = ECPublicKeySpec(pointQ, parameterSpec)
val publicKeyD = BCECPublicKey(privateKey.algorithm, publicKeySpec, BouncyCastleProvider.CONFIGURATION)
@ -822,7 +824,7 @@ object Crypto {
@JvmStatic
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 {
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.
@ -849,6 +851,7 @@ object Crypto {
override fun generatePublic(keyInfo: SubjectPublicKeyInfo?): PublicKey? {
return keyInfo?.let { decodePublicKey(signatureScheme, it.encoded) }
}
override fun generatePrivate(keyInfo: PrivateKeyInfo?): PrivateKey? {
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.
private fun isEdDSAPointAtInfinity(publicKey: EdDSAPublicKey): Boolean {
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
}
// validate a key, by checking its algorithmic params.
// Validate a key, by checking its algorithmic params.
private fun validateKey(signatureScheme: SignatureScheme, key: Key): Boolean {
return when (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 {
return when (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 {
return when (key) {
is BCECPrivateKey -> key.parameters == signatureScheme.algSpec
@ -922,7 +925,6 @@ object Crypto {
/**
* Convert a public key to a supported implementation.
*
* @param key a public key.
* @return a supported implementation of the input public key.
* @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.
*/
@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.
@ -952,5 +963,13 @@ object Crypto {
* is inappropriate for a supported key factory to produce a private key.
*/
@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 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))
/**
* 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)
/**
@ -34,9 +44,13 @@ fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey) = DigitalSigna
* @throws InvalidKeyException if the private key is invalid.
* @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)
/** 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)
/**
* Helper function for signing a [SignableData] object.
* @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 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()`,
@Throws(SignatureException::class, IllegalArgumentException::class, InvalidKeyException::class)
// TODO: SignatureException should be used only for a damaged signature, as per `java.security.Signature.verify()`.
@Throws(SignatureException::class, InvalidKeyException::class)
fun PublicKey.verify(content: ByteArray, signature: DigitalSignature) = Crypto.doVerify(this, signature.bytes, content)
/**
@ -70,21 +84,25 @@ fun PublicKey.verify(content: ByteArray, signature: DigitalSignature) = Crypto.d
* signature).
* @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 IllegalStateException if this is a [CompositeKey], because verification of composite key signatures is not supported.
* @return whether the signature is correct for this key.
*/
@Throws(IllegalStateException::class, SignatureException::class, IllegalArgumentException::class)
fun PublicKey.isValid(content: ByteArray, signature: DigitalSignature) : Boolean {
@Throws(SignatureException::class, InvalidKeyException::class)
fun PublicKey.isValid(content: ByteArray, signature: DigitalSignature): Boolean {
if (this is CompositeKey)
throw IllegalStateException("Verification of CompositeKey signatures currently not supported.") // TODO CompositeSignature verification.
return Crypto.isValid(this, signature.bytes, content)
}
/** 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)
fun PublicKey.isFulfilledBy(otherKey: PublicKey): Boolean = isFulfilledBy(setOf(otherKey))
/** Return true if [otherKey] fulfils the requirements of this [PublicKey]. */
fun PublicKey.isFulfilledBy(otherKey: PublicKey): Boolean = isFulfilledBy(setOf(otherKey))
/** Return true if [otherKeys] fulfil the requirements of this [PublicKey]. */
fun PublicKey.isFulfilledBy(otherKeys: Iterable<PublicKey>): Boolean = (this as? CompositeKey)?.isFulfilledBy(otherKeys) ?: (this in otherKeys)
/** 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:
// val (private, public) = keyPair
/* The [PrivateKey] of this [KeyPair] .*/
operator fun KeyPair.component1(): PrivateKey = this.private
/* The [PublicKey] of this [KeyPair] .*/
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. */
@ -122,7 +141,7 @@ fun entropyToKeyPair(entropy: BigInteger): KeyPair = Crypto.deriveKeyPairFromEnt
* 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(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
@Throws(InvalidKeyException::class, SignatureException::class)
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.
* @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)
/**

View File

@ -23,6 +23,7 @@ open class DigitalSignature(bytes: ByteArray) : OpaqueBytes(bytes) {
*/
@Throws(InvalidKeyException::class, SignatureException::class)
fun verify(content: ByteArray) = by.verify(content, this)
/**
* 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)
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
* 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)
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
import net.corda.core.CordaException
import net.corda.core.crypto.SecureHash.Companion.zeroHash
import net.corda.core.serialization.CordaSerializable
import java.util.*
@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.

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()
/**
* 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)
/**
* 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()
// Like static methods in Java, except the 'companion' is a singleton that can have state.
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
fun parse(str: String) = str.toUpperCase().parseAsHex().let {
when (it.size) {
32 -> SHA256(it)
else -> throw IllegalArgumentException("Provided string is ${it.size} bytes not 32 bytes in hex: $str")
fun parse(str: String): SHA256 {
return str.toUpperCase().parseAsHex().let {
when (it.size) {
32 -> SHA256(it)
else -> throw IllegalArgumentException("Provided string is ${it.size} bytes not 32 bytes in hex: $str")
}
}
}
@JvmStatic fun sha256(bytes: ByteArray) = SHA256(MessageDigest.getInstance("SHA-256").digest(bytes))
@JvmStatic fun sha256Twice(bytes: ByteArray) = sha256(sha256(bytes).bytes)
@JvmStatic fun sha256(str: String) = sha256(str.toByteArray())
/**
* Computes the SHA-256 hash value of the [ByteArray].
* @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() }))
/**
* A SHA-256 hash value consisting of 32 0xFF bytes.
*/
val allOnesHash = SecureHash.SHA256(ByteArray(32, { 255.toByte() }))
}
// 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)
/**
* Compute the SHA-256 hash for the contents of the [OpaqueBytes].
*/
fun OpaqueBytes.sha256(): SecureHash.SHA256 = SecureHash.sha256(this.bytes)

View File

@ -1,5 +1,6 @@
package net.corda.core.crypto
import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializedBytes
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)
fun verified(): T {
sig.by.verify(raw.bytes, sig)
@Suppress("UNCHECKED_CAST")
val data = raw.deserialize<Any>() as T
val data: T = uncheckedCast(raw.deserialize<Any>())
verifyData(data)
return data
}

View File

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

View File

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

View File

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

View File

@ -3,9 +3,6 @@ package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.*
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.
@ -16,30 +13,6 @@ import java.security.PublicKey
* use the new updated state for future transactions.
*/
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.
*
@ -49,11 +22,13 @@ object ContractUpgradeFlow {
*
* This flow will NOT initiate the upgrade process. To start the upgrade process, see [Initiate].
*/
// DOCSTART 1
@StartableByRPC
class Authorise(
val stateAndRef: StateAndRef<*>,
private val upgradedContractClass: Class<out UpgradedContract<*, *>>
) : FlowLogic<Void?>() {
) : FlowLogic<Void?>() {
// DOCEND 1
@Suspendable
override fun call(): Void? {
val upgrade = upgradedContractClass.newInstance()
@ -70,10 +45,12 @@ object ContractUpgradeFlow {
* Deauthorise a contract state upgrade.
* This will remove the upgrade authorisation from persistent store (and prevent any further upgrade)
*/
// DOCSTART 2
@StartableByRPC
class Deauthorise(val stateRef: StateRef) : FlowLogic<Void?>() {
@Suspendable
override fun call(): Void? {
//DOCEND 2
serviceHub.contractUpgradeService.removeAuthorisedContractUpgrade(stateRef)
return null
}
@ -89,23 +66,6 @@ object ContractUpgradeFlow {
newContractClass: Class<out UpgradedContract<OldState, NewState>>
) : 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
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
val baseTx = ContractUpgradeUtils.assembleBareTx(originalState, modification, PrivacySalt())

View File

@ -26,8 +26,8 @@ import net.corda.core.utilities.ProgressTracker
*/
@InitiatingFlow
class FinalityFlow(val transaction: SignedTransaction,
private val extraRecipients: Set<Party>,
override val progressTracker: ProgressTracker) : FlowLogic<SignedTransaction>() {
private val extraRecipients: Set<Party>,
override val progressTracker: ProgressTracker) : FlowLogic<SignedTransaction>() {
constructor(transaction: SignedTransaction, extraParticipants: Set<Party>) : this(transaction, extraParticipants, tracker())
constructor(transaction: SignedTransaction) : this(transaction, emptySet(), tracker())
constructor(transaction: SignedTransaction, progressTracker: ProgressTracker) : this(transaction, emptySet(), progressTracker)
@ -88,7 +88,7 @@ class FinalityFlow(val transaction: SignedTransaction,
private fun hasNoNotarySignature(stx: SignedTransaction): Boolean {
val notaryKey = stx.tx.notary?.owningKey
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> {

View File

@ -16,14 +16,22 @@ sealed class FlowInitiator : Principal {
data class RPC(val username: String) : FlowInitiator() {
override fun getName(): String = username
}
/** Started when we get new session initiation request. */
data class Peer(val party: Party) : FlowInitiator() {
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. */
data class Scheduled(val scheduledState: ScheduledStateRef) : FlowInitiator() {
override fun getName(): String = "Scheduler"
}
// TODO When proper ssh access enabled, add username/use RPC?
object Shell : FlowInitiator() {
override fun getName(): String = "Shell User"

View File

@ -1,11 +1,13 @@
package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import co.paralleluniverse.strands.Strand
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.FlowStateMachine
import net.corda.core.internal.abbreviate
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.DataFeed
import net.corda.core.node.NodeInfo
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.debug
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
@ -42,6 +46,34 @@ abstract class FlowLogic<out T> {
/** This is where you should log things to. */
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
* identifier as their parents).
@ -55,6 +87,10 @@ abstract class FlowLogic<out T> {
*/
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
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
* 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)
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
* 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)
@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
* 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)
@Suspendable
@ -173,6 +209,38 @@ abstract class FlowLogic<out T> {
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.
*
@ -227,7 +295,6 @@ abstract class FlowLogic<out T> {
stateMachine.checkFlowPermission(permissionName, extraAuditData)
}
/**
* 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.
@ -330,6 +397,18 @@ abstract class FlowLogic<out T> {
ours.setChildProgressTracker(ours.currentStep, theirs)
}
}
private fun enforceNoDuplicates(sessions: List<FlowSession>) {
require(sessions.size == sessions.toSet().size) { "A flow session can only appear once as argument." }
}
private fun <R> associateSessionsToReceiveType(receiveType: Class<R>, sessions: List<FlowSession>): Map<FlowSession, Class<R>> {
return sessions.associateByTo(LinkedHashMap(), { it }, { receiveType })
}
private fun <R> castMapValuesToKnownType(map: Map<FlowSession, UntrustworthyData<Any>>): List<UntrustworthyData<R>> {
return map.values.map { uncheckedCast<Any, UntrustworthyData<R>>(it) }
}
}
/**
@ -347,4 +426,4 @@ data class FlowInfo(
* to deduplicate it from other releases of the same CorDapp, typically a version string. See the
* [CorDapp JAR format](https://docs.corda.net/cordapp-build-systems.html#cordapp-jar-format) for more details.
*/
val appName: String)
val appName: String)

View File

@ -24,16 +24,4 @@ class IllegalFlowLogicException(type: Class<*>, msg: String) : IllegalArgumentEx
*/
// TODO: align this with the existing [FlowRef] in the bank-side API (probably replace some of the API classes)
@CordaSerializable
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
}
interface FlowLogicRef

View File

@ -5,7 +5,18 @@ import net.corda.core.identity.Party
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.
*
@ -31,6 +42,10 @@ import net.corda.core.utilities.UntrustworthyData
* otherSideSession.send(something)
*/
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
/**
@ -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
* 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
inline fun <reified R : Any> sendAndReceive(payload: Any): UntrustworthyData<R> {
return sendAndReceive(R::class.java, payload)
}
/**
* 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
@ -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
* 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
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> {
return receive(R::class.java)
}
/**
* 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
* 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
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.UniquenessProvider
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.FilteredTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData
@ -55,11 +54,7 @@ class NotaryFlow {
}
try {
if (stx.isNotaryChangeTransaction()) {
stx.resolveNotaryChangeTransaction(serviceHub).verifySignaturesExcept(notaryParty.owningKey)
} else {
stx.verifySignaturesExcept(notaryParty.owningKey)
}
stx.resolveTransactionWithSignatures(serviceHub).verifySignaturesExcept(notaryParty.owningKey)
} catch (ex: SignatureException) {
throw NotaryException(NotaryError.TransactionInvalid(ex))
}
@ -138,8 +133,11 @@ class NotaryFlow {
// Check if transaction is intended to be signed by this notary.
@Suspendable
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)
}
}
@Suspendable
@ -171,5 +169,5 @@ sealed class NotaryError {
override fun toString() = cause.toString()
}
object WrongNotary: NotaryError()
object WrongNotary : NotaryError()
}

View File

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

View File

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

View File

@ -13,6 +13,7 @@ import java.security.PublicKey
abstract class AbstractParty(val owningKey: PublicKey) {
/** 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 hashCode(): Int = owningKey.hashCode()
abstract fun nameOrNull(): CordaX500Name?

View File

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

View File

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

View File

@ -11,7 +11,9 @@ import java.security.cert.*
*/
@CordaSerializable
class PartyAndCertificate(val certPath: CertPath) {
@Transient val certificate: X509Certificate
@Transient
val certificate: X509Certificate
init {
require(certPath.type == "X.509") { "Only X.509 certificates supported" }
val certs = certPath.certificates
@ -19,7 +21,8 @@ class PartyAndCertificate(val certPath: CertPath) {
certificate = certs[0] as X509Certificate
}
@Transient val party: Party = Party(certificate)
@Transient
val party: Party = Party(certificate)
val owningKey: PublicKey get() = party.owningKey
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")
}
@JvmStatic val CODE_SANTA_CLAUS: String = codePointsString(0x1F385)
@JvmStatic val CODE_DIAMOND: String = codePointsString(0x1F537)
@JvmStatic val CODE_BAG_OF_CASH: String = codePointsString(0x1F4B0)
@JvmStatic val CODE_NEWSPAPER: String = codePointsString(0x1F4F0)
@JvmStatic val CODE_RIGHT_ARROW: String = codePointsString(0x27A1, 0xFE0F)
@JvmStatic val CODE_LEFT_ARROW: String = codePointsString(0x2B05, 0xFE0F)
@JvmStatic val CODE_GREEN_TICK: String = codePointsString(0x2705)
@JvmStatic 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)
@JvmStatic
val CODE_SANTA_CLAUS: String = codePointsString(0x1F385)
@JvmStatic
val CODE_DIAMOND: String = codePointsString(0x1F537)
@JvmStatic
val CODE_BAG_OF_CASH: String = codePointsString(0x1F4B0)
@JvmStatic
val CODE_NEWSPAPER: String = codePointsString(0x1F4F0)
@JvmStatic
val CODE_RIGHT_ARROW: String = codePointsString(0x27A1, 0xFE0F)
@JvmStatic
val CODE_LEFT_ARROW: String = codePointsString(0x2B05, 0xFE0F)
@JvmStatic
val CODE_GREEN_TICK: String = codePointsString(0x2705)
@JvmStatic
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?
@Suppress("UNCHECKED_CAST")
protected open fun convert(wire: W): T = wire as T
protected open fun convert(wire: W): T = uncheckedCast(wire)
private fun validateFetchResponse(maybeItems: UntrustworthyData<ArrayList<W>>,
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.utilities.UntrustworthyData
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]. */
interface FlowStateMachine<R> {
@ -30,25 +31,32 @@ interface FlowStateMachine<R> {
fun <T : Any> receive(receiveType: Class<T>, otherParty: Party, sessionFlow: FlowLogic<*>): UntrustworthyData<T>
@Suspendable
fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>): Unit
fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>)
@Suspendable
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
fun flowStackSnapshot(flowClass: Class<out FlowLogic<*>>): FlowStackSnapshot?
@Suspendable
fun persistFlowStackSnapshot(flowClass: Class<out FlowLogic<*>>): Unit
fun persistFlowStackSnapshot(flowClass: Class<out FlowLogic<*>>)
val logic: FlowLogic<R>
val serviceHub: ServiceHub
val logger: Logger
val id: StateMachineRunId
val resultFuture: CordaFuture<R>
val flowInitiator: FlowInitiator
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
import net.corda.core.crypto.SecureHash
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.jcajce.JcaX509CertificateConverter
import org.slf4j.Logger
@ -44,6 +51,7 @@ operator fun Duration.times(multiplicand: Long): Duration = multipliedBy(multipl
* separator problems.
*/
operator fun Path.div(other: String): Path = resolve(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)
return targetFile
}
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.isDirectory(vararg options: LinkOption): Boolean = Files.isDirectory(this, *options)
@ -226,25 +235,29 @@ private fun IntProgression.toSpliterator(): Spliterator.OfInt {
fun IntProgression.stream(parallel: Boolean = false): IntStream = StreamSupport.intStream(toSpliterator(), parallel)
@Suppress("UNCHECKED_CAST") // 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>
// 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(): Array<T> = uncheckedCast(toArray { size -> arrayOfNulls<T>(size) })
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]. */
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]. */
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)
/**
* Returns a [DeclaredField] wrapper around the (possibly non-public) instance field of the receiver object, but declared
* in its superclass [clazz].
* @suppress
*/
fun <T> Any.declaredField(clazz: KClass<*>, name: String): DeclaredField<T> = DeclaredField(clazz.java, name, this)
/** creates a new instance if not a Kotlin object */
fun <T: Any> KClass<T>.objectOrNewInstance(): T {
fun <T : Any> KClass<T>.objectOrNewInstance(): T {
return this.objectInstance ?: this.createInstance()
}
@ -255,8 +268,7 @@ fun <T: Any> KClass<T>.objectOrNewInstance(): T {
class DeclaredField<T>(clazz: Class<*>, name: String, private val receiver: Any?) {
private val javaField = clazz.getDeclaredField(name).apply { isAccessible = true }
var value: T
@Suppress("UNCHECKED_CAST")
get() = javaField.get(receiver) as T
get() = uncheckedCast<Any?, T>(javaField.get(receiver))
set(value) = javaField.set(receiver, value)
}
@ -273,4 +285,21 @@ annotation class VisibleForTesting
@Suppress("UNCHECKED_CAST")
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,
FINISHED
}
private val lifeCycle = LifeCycle(State.STARTED)
private fun clearIfNeeded(instance: A): A {

View File

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

View File

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

View File

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

View File

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

View File

@ -46,6 +46,7 @@ class X509EdDSAEngine : Signature {
override fun engineSetParameter(params: AlgorithmParameterSpec) = engine.setParameter(params)
@Suppress("DEPRECATION")
override fun engineGetParameter(param: String): Any = engine.getParameter(param)
@Suppress("DEPRECATION")
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.flows.FlowLogic
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.SerializeAsToken
import java.io.File
import java.net.URL
data class CordappImpl(
override val contractClassNames: List<String>,
override val initiatedFlows: 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 plugins: List<CordaPluginRegistry>,
override val serializationWhitelists: List<SerializationWhitelist>,
override val customSchemas: Set<MappedSchema>,
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
*
* TODO: Also add [SchedulableFlow] as a Cordapp class
*/
override val cordappClasses = ((rpcFlows + initiatedFlows + services + plugins.map { javaClass }).map { it.name } + contractClassNames)
override val cordappClasses = ((rpcFlows + initiatedFlows + services + serializationWhitelists.map { javaClass }).map { it.name } + contractClassNames)
}

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