Merge remote-tracking branch 'github/master'

This commit is contained in:
Shams Asari 2016-12-08 11:32:38 +00:00
commit 69d2d0e208
12974 changed files with 1001134 additions and 144211 deletions

33
.gitignore vendored
View File

@ -10,36 +10,16 @@ tags
# Created by .ignore support plugin (hsz.mobi)
.gradle
local.properties
/docs/build/doctrees
# General build files
**/build/classes/**
**/build/install/**
**/build/kotlin-classes/**
**/build/libs/**
**/build/resources/**
**/build/tmp/**
**/build/reports/**
**/build/jacoco/***
**/build/test-results/**
**/build/[0-9]*/**
**/build/nodes/**
**/build/scripts/**
**/build/publications/**
# gradle's buildSrc build/
/buildSrc/build/
# This exists only in the internal repo
/network-explorer/build
**/build/*
!docs/build/*
lib/dokka.jar
buyer
seller
rate-fix-demo-data
nodeA
nodeB
**/logs/*
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
@ -53,8 +33,8 @@ nodeB
.idea/inspectionProfiles
.idea/libraries
.idea/shelf
!.idea/modules.xml
.idea/dataSources
.idea/modules.xml
# if you remove the above rule, at least ignore the following:
@ -99,4 +79,3 @@ crashlytics-build.properties
# docs related
docs/virtualenv/
/logs/

49
.idea/modules.xml generated
View File

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/buildSrc.iml" filepath="$PROJECT_DIR$/.idea/modules/buildSrc.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/buildSrc_main.iml" filepath="$PROJECT_DIR$/.idea/modules/buildSrc_main.iml" group="buildSrc" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/buildSrc_test.iml" filepath="$PROJECT_DIR$/.idea/modules/buildSrc_test.iml" group="buildSrc" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/client/client.iml" filepath="$PROJECT_DIR$/.idea/modules/client/client.iml" group="client" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/client/client_integrationTest.iml" filepath="$PROJECT_DIR$/.idea/modules/client/client_integrationTest.iml" group="client" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/client/client_main.iml" filepath="$PROJECT_DIR$/.idea/modules/client/client_main.iml" group="client" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/client/client_test.iml" filepath="$PROJECT_DIR$/.idea/modules/client/client_test.iml" group="client" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/contracts/contracts.iml" filepath="$PROJECT_DIR$/.idea/modules/contracts/contracts.iml" group="contracts" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/contracts/contracts_main.iml" filepath="$PROJECT_DIR$/.idea/modules/contracts/contracts_main.iml" group="contracts" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/contracts/contracts_test.iml" filepath="$PROJECT_DIR$/.idea/modules/contracts/contracts_test.iml" group="contracts" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/core/core.iml" filepath="$PROJECT_DIR$/.idea/modules/core/core.iml" group="core" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/core/core_main.iml" filepath="$PROJECT_DIR$/.idea/modules/core/core_main.iml" group="core" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/core/core_test.iml" filepath="$PROJECT_DIR$/.idea/modules/core/core_test.iml" group="core" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/experimental/experimental.iml" filepath="$PROJECT_DIR$/.idea/modules/experimental/experimental.iml" group="experimental" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/experimental/experimental_main.iml" filepath="$PROJECT_DIR$/.idea/modules/experimental/experimental_main.iml" group="experimental" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/experimental/experimental_test.iml" filepath="$PROJECT_DIR$/.idea/modules/experimental/experimental_test.iml" group="experimental" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/explorer/explorer.iml" filepath="$PROJECT_DIR$/.idea/modules/explorer/explorer.iml" group="explorer" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/explorer/explorer_main.iml" filepath="$PROJECT_DIR$/.idea/modules/explorer/explorer_main.iml" group="explorer" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/explorer/explorer_test.iml" filepath="$PROJECT_DIR$/.idea/modules/explorer/explorer_test.iml" group="explorer" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/contracts/isolated/isolated.iml" filepath="$PROJECT_DIR$/.idea/modules/contracts/isolated/isolated.iml" group="contracts/isolated" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/contracts/isolated/isolated_main.iml" filepath="$PROJECT_DIR$/.idea/modules/contracts/isolated/isolated_main.iml" group="contracts/isolated" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/contracts/isolated/isolated_test.iml" filepath="$PROJECT_DIR$/.idea/modules/contracts/isolated/isolated_test.iml" group="contracts/isolated" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/netPermission/netpermission.iml" filepath="$PROJECT_DIR$/.idea/modules/netPermission/netpermission.iml" group="netpermission" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/netPermission/netpermission_main.iml" filepath="$PROJECT_DIR$/.idea/modules/netPermission/netpermission_main.iml" group="netpermission" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/netPermission/netpermission_test.iml" filepath="$PROJECT_DIR$/.idea/modules/netPermission/netpermission_test.iml" group="netpermission" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/network-explorer/network-explorer_main.iml" filepath="$PROJECT_DIR$/.idea/modules/network-explorer/network-explorer_main.iml" group="network-explorer" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/network-explorer/network-explorer_test.iml" filepath="$PROJECT_DIR$/.idea/modules/network-explorer/network-explorer_test.iml" group="network-explorer" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/network-explorer/network-map-visualiser.iml" filepath="$PROJECT_DIR$/.idea/modules/network-explorer/network-map-visualiser.iml" group="network-explorer" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/network-simulator/network-simulator.iml" filepath="$PROJECT_DIR$/.idea/modules/network-simulator/network-simulator.iml" group="network-simulator" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/network-simulator/network-simulator_main.iml" filepath="$PROJECT_DIR$/.idea/modules/network-simulator/network-simulator_main.iml" group="network-simulator" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/network-simulator/network-simulator_test.iml" filepath="$PROJECT_DIR$/.idea/modules/network-simulator/network-simulator_test.iml" group="network-simulator" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/node/node.iml" filepath="$PROJECT_DIR$/.idea/modules/node/node.iml" group="node" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/node/node_integrationTest.iml" filepath="$PROJECT_DIR$/.idea/modules/node/node_integrationTest.iml" group="node" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/node/node_main.iml" filepath="$PROJECT_DIR$/.idea/modules/node/node_main.iml" group="node" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/node/node_test.iml" filepath="$PROJECT_DIR$/.idea/modules/node/node_test.iml" group="node" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/r3prototyping.iml" filepath="$PROJECT_DIR$/.idea/modules/r3prototyping.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/r3prototyping_integrationTest.iml" filepath="$PROJECT_DIR$/.idea/modules/r3prototyping_integrationTest.iml" group="r3prototyping" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/r3prototyping_main.iml" filepath="$PROJECT_DIR$/.idea/modules/r3prototyping_main.iml" group="r3prototyping" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/r3prototyping_test.iml" filepath="$PROJECT_DIR$/.idea/modules/r3prototyping_test.iml" group="r3prototyping" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/test-utils/test-utils.iml" filepath="$PROJECT_DIR$/.idea/modules/test-utils/test-utils.iml" group="test-utils" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/test-utils/test-utils_main.iml" filepath="$PROJECT_DIR$/.idea/modules/test-utils/test-utils_main.iml" group="test-utils" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/test-utils/test-utils_test.iml" filepath="$PROJECT_DIR$/.idea/modules/test-utils/test-utils_test.iml" group="test-utils" />
</modules>
</component>
</project>

View File

@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Attachment Demo: Run Nodes" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.attachmentdemo.MainKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="attachment-demo_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Attachment Demo: Run Recipient" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.attachmentdemo.AttachmentDemoKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="--role=RECIPIENT" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="attachment-demo_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Attachment Demo: Run Sender" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.attachmentdemo.AttachmentDemoKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="--role SENDER" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="attachment-demo_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -11,8 +11,9 @@
<option name="taskNames">
<list>
<option value="clean" />
<option value="build" />
<option value="installDist" />
<option value="buildCordaJAR" />
<option value="install" />
</list>
</option>
<option name="vmOptions" value="" />

View File

@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="IRS Demo: Run Date Change" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.irs.IRSDemo" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="--role Date 2018-01-01" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="irs-demo_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="IRS Demo: Run Nodes" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.irs.MainKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="irs-demo_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="IRS Demo: Run Trade" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.irs.IRSDemo" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="--role Trade trade1" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="irs-demo_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="IRS Demo: Upload Rates" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.irs.IRSDemo" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="--role UploadRates" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="irs-demo_main" />
<envs />
<method />
</configuration>
</component>

21
.idea/runConfigurations/Install.xml generated Normal file
View File

@ -0,0 +1,21 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Install" type="GradleRunConfiguration" factoryName="Gradle" singleton="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="installDist" />
<option value="install" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<method />
</configuration>
</component>

View File

@ -1,15 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Node: buyer" type="JetRunConfigurationType" factoryName="Kotlin">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="com.r3corda.demos.TraderDemoKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar -Dco.paralleluniverse.fibers.verifyInstrumentation" />
<option name="PROGRAM_PARAMETERS" value="--role=BUYER" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="r3prototyping_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -1,15 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Node: seller" type="JetRunConfigurationType" factoryName="Kotlin">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="com.r3corda.demos.TraderDemoKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar -Dco.paralleluniverse.fibers.verifyInstrumentation" />
<option name="PROGRAM_PARAMETERS" value="--role=SELLER" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="r3prototyping_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Notary Demo: Run Nodes" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.notarydemo.MainKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="raft-notary-demo_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Notary Demo: Run Notarisation" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.notarydemo.NotaryDemoKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="raft-notary-demo_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="SIMM Valuation Demo" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.vega.MainKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="simm-valuation-demo_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Trader Demo: Run Buyer" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.traderdemo.TraderDemoKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="--role BUYER" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="trader-demo_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Trader Demo: Run Nodes" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.traderdemo.MainKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="trader-demo_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Trader Demo: Run Seller" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.traderdemo.TraderDemoKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="--role SELLER" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="trader-demo_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -6,7 +6,7 @@
<option name="LIVE_STYLESHEETS" value="false" />
<option name="DUMP_STYLESHEETS" value="false" />
<option name="LIVE_VIEWS" value="false" />
<option name="MAIN_CLASS_NAME" value="com.r3corda.explorer.Main" />
<option name="MAIN_CLASS_NAME" value="net.corda.explorer.Main" />
<option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />

52
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,52 @@
# Contributing to Corda
To start contributing you can fork our repo and begin making pull requests. Please use
descriptive commit messages and follow our [coding style guidelines](https://docs.corda.net/codestyle.html).
## Community Locations
* [GitHub](https://github.com/corda/corda)
* [Forums](https://discourse.corda.net)
* [Chat](https://slack.corda.net)
## Developer Certificate of Origin
All contributions to this project are subject to the terms of the Developer Certificate of Origin, below:
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
1 Letterman Drive
Suite D4700
San Francisco, CA, 94129
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.

13
LICENSE Normal file
View File

@ -0,0 +1,13 @@
Copyright 2016, R3 Limited.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

188
README.md
View File

@ -1,124 +1,74 @@
# Introduction
![Corda](https://www.corda.net/wp-content/uploads/2016/11/fg005_corda_b.png)
This source repository contains explorations of various design concepts the R3 DLG is exploring.
# Corda
Things you need to know:
Corda is a decentralised database system in which nodes trust each other as little as possible.
* The main code documentation is in the form of a website in the git repository. There is a copy of the site online, so
[access the website](http://docs.corda.r3cev.com) using the username 'corda' and password 'delegato' to start reading
about what's included, how to get set up, and to read a tutorial on writing smart contracts in this framework.
* The architecture documentation is on the [Architecture Working Group Wiki](https://r3-cev.atlassian.net/wiki/display/AWG/Architecture+Working+Group) site - please
refer to that for an explanation of some of the background concepts that the prototype is exploring.
## Features
* The code is a JVM project written mostly in [Kotlin](https://kotlinlang.org/), which you can think of as a simpler
version of Scala (or alternatively, a much better syntax for Java). Kotlin can be learned quickly and is designed
to be readable, so you won't need to know it very well to understand what the code is doing. If you'd like to
add new features, please read its documentation on the website.
There is also Java code included, to demonstrate how to use the framework from a more familiar language.
* For bug tracking and project management we use [JIRA](https://r3-cev.atlassian.net/secure/RapidBoard.jspa?rapidView=25&projectKey=COR).
For source control we use this BitBucket repository. You should have received credentials for these
services as part of getting set up. If you don't have access, please contact Richard Brown or James Carlyle.
* There will be a mailing list for discussion, brainstorming etc called [r3dlg-awg](https://groups.google.com/forum/#!forum/r3dlg-awg).
# License
This code is not yet released under a traditional open source license. Until it is, the following license applies:
_Copyright Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members
pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms
set forth therein. Distributed as Non-Project IP to R3 LRC Members pursuant to their respective Member
and Services Agreements and subject to the Non-Project IP license terms. set forth therein. All other rights reserved._
# Instructions for installing prerequisite software
## JDK for Java 8
Install the Oracle JDK 8u45 or higher. It is possible that OpenJDK will also work but we have not tested with this.
## Using IntelliJ
It's a good idea to use a modern IDE. We use IntelliJ. Install the __latest version__ of IntelliJ community edition (which is free):
https://www.jetbrains.com/idea/download/
Upgrade the Kotlin plugin to the latest version (1.0-beta-2423) by clicking "Configure > Plugins" in the opening screen,
then clicking "Install JetBrains plugin", then searching for Kotlin, then hitting "Upgrade" and then "Restart".
Choose "Check out from version control" and use this git URL
https://your_username@bitbucket.org/R3-CEV/r3prototyping.git
Agree to the defaults for importing a Gradle project. Wait for it to download the dependencies.
Right click on the tests directory, click "Run -> All Tests" (note: NOT the first item in the submenu that has the gradle logo next to it).
The code should build, the unit tests should show as all green.
You can catch up with the latest code by selecting "VCS -> Update Project" in the menu.
# Troubleshooting
## IntelliJ
If on attempting to open the project, IntelliJ refuses because SDK was not selected, do the following:
Configure -> Project Defaults -> Project Structure
on that tab:
Project Settings / Project
click on New… next to the red <No SDK> symbol, and select JDK. It should then pop up and show the latest JDK it has found at something like
jdk1.8.0_xx…/Contents/Home
Also select Project language level: as 8. Click OK. Open should now work.
## Quasar
If you get an error about a missing Quasar agent, then your JVM is being invoked without a needed command line argument.
Make sure an argument like `-javaagent:lib/quasar.jar` is being passed to the invocation.
You may need/want to edit your default JUnit run config in IntelliJ to ensure that parameter is being set, along with
`-Dco.paralleluniverse.fibers.verifyInstrumentation` which is useful to catch mistakes. To do that, click the dropdown
in the toolbar and select "Edit configurations", then expand the defaults tree, then select JUnit and add the two
arguments to the VM options edit.
## "Foo is ambiguous" error during compilation
Gradle's incremental compilation isn't always reliable. Run `./gradlew clean` and then try again.
## ClassNotFoundException during Gradle quasarScan task
Your Gradle build server is hosed. Run `gradle --stop` and then try `gradle clean build`.
# Accessing Source Without an IDE
If you don't want to explore or modify the code in a local IDE, you can also just use the command line and a text editor:
git clone https://your_username@bitbucket.org/R3-CEV/r3prototyping.git
You will need to have your Bitbucket account set up already from R3. Then:
cd r3prototyping
Run the following to run the unit tests:
./gradlew test
For the first time only, this will download and configure Gradle.
Run "git pull" to upgrade
# Starting point - the Tutorial
We have prepared a comprehensive tutorial.
One you have access to the source, open the following in a browser:
r3prototyping/docs/build/html/index.html
* A P2P network of nodes
* Smart contracts
* Flow framework
* "Notary" infrastructure to validate uniqueness of transactions
* Written as a platform for distributed apps called CorDapps
* Written in [Kotlin](https://kotlinlang.org), targeting the JVM
![Screenshot](https://r3-cev.atlassian.net/wiki/download/attachments/3441064/Screen%20Shot%202015-12-10%20at%2010.43.06.png)
Read our full and planned feature list [here](https://docs.corda.net/inthebox.html).
## Getting started
Firstly, read the [Getting started](https://docs.corda.net/getting-set-up.html) documentation.
Watching the following webinars will give you a great introduction to Corda:
### Webinar 1 [Introduction to Corda](https://vimeo.com/192757743/c2ec39c1e1)
Richard Brown, R3 Chief Technology Officer, explains Corda's unique architecture, the only distributed ledger platform designed by and for the financial industry's unique requirements. You may want to read the [Corda non-technical whitepaper](https://www.r3.com/s/corda-introductory-whitepaper-final.pdf) as pre-reading for this session.
### Webinar 2 [Corda Developers Tutorial](https://vimeo.com/192797322/aab499b152)
Roger Willis, R3 Developer Relations Lead, provides an overview of Corda from a developers perspective and guidance on how to start building CorDapps. You may want to view [Webinar 1 - Introduction to Corda](https://vimeo.com/192757743/c2ec39c1e1) as preparation for this session. **NB. This was recorded for the M5 release.**
## Building on Corda
To build your own CorDapps:
1. Clone the [CorDapp Template repository](https://github.com/corda/cordapp-template)
2. Read the [README](https://github.com/corda/cordapp-template/blob/master/README.md) (**IMPORTANT!**)
3. Read the [Writing a CorDapp](https://docs.corda.net/tutorial-cordapp.html) documentation
To look at the Corda source and run some sample applications:
1. Clone this repository
2. To run some sample CorDapps, read the [running the demos documentation](https://docs.corda.r3cev.com/running-the-demos.html)
3. Start hacking and [contribute](./CONTRIBUTING.md)!
## Useful links
* [Project website](https://corda.net)
* [Documentation](https://docs.corda.net)
* [Slack channel] (https://slack.corda.net/)
* [Forum](https://discourse.corda.net)
## Development State
Corda is currently in very early development and should not be used in production systems. Breaking
changes will happen on minor versions until 1.0. Experimentation with Corda is recommended.
Pull requests, experiments, and contributions are encouraged and welcomed.
## Background
The project is supported by R3, a financial industry consortium, which is why it
contains some code for financial use cases and why the documentation focuses on finance. The goal is to use it
to construct a global ledger, simplifying finance and reducing the overheads of banking. But it is run as
an open source project and the basic technology of a peer-to-peer decentralised database may be useful
for many different projects.
## Contributing
Please read [here](./CONTRIBUTING.md).
## License
[Apache 2.0](./LICENCE)

4
TRADEMARK Normal file
View File

@ -0,0 +1,4 @@
Corda and the Corda logo are trademarks of R3CEV LLC and its affiliates.
All rights reserved.
For R3CEV LLC's trademark and logo usage information, please consult our Trademark Usage Policy available at https://www.r3.com/trademark-usage-policy

View File

@ -1,22 +1,35 @@
buildscript {
ext.kotlin_version = '1.0.3'
// Our version: bump this on release.
ext.corda_version = "0.7-SNAPSHOT"
ext.gradle_plugins_version = "0.6.1"
ext.kotlin_version = '1.0.5'
ext.quasar_version = '0.7.6'
ext.asm_version = '0.5.3'
ext.artemis_version = '1.4.0'
ext.jackson_version = '2.8.0.rc2'
ext.jetty_version = '9.3.9.v20160517'
ext.jersey_version = '2.23.1'
ext.jolokia_version = '2.0.0-M1'
ext.jolokia_version = '2.0.0-M1'
ext.assertj_version = '3.5.1'
ext.log4j_version = '2.6.2'
ext.bouncycastle_version = '1.54'
repositories {
mavenLocal()
mavenCentral()
jcenter()
// TODO: Remove this once all packages are published to jcenter or maven central. (M6 or 7).
maven {
url "http://r3.bintray.com/corda"
}
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4'
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"
// Can run 'gradle dependencyUpdates' to find new versions of things.
classpath 'com.github.ben-manes:gradle-versions-plugin:0.12.0'
@ -30,11 +43,20 @@ plugins {
}
apply plugin: 'kotlin'
apply plugin: 'application'
apply plugin: 'project-report'
apply plugin: QuasarPlugin
apply plugin: 'com.github.ben-manes.versions'
apply plugin: 'maven-publish'
apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.cordformation'
// We need the following three lines even though they're inside an allprojects {} block below because otherwise
// IntelliJ gets confused when importing the project and ends up erasing and recreating the .idea directory, along
// with the run configurations. It also doesn't realise that the project is a Java 8 project and misconfigures
// the resulting import. This fixes it.
apply plugin: 'java'
sourceCompatibility = 1.8
targetCompatibility = 1.8
allprojects {
apply plugin: 'java'
@ -44,118 +66,36 @@ allprojects {
targetCompatibility = 1.8
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" << "-Xlint:-options"
}
// Our version: bump this on release.
group 'com.r3corda'
version '0.5-SNAPSHOT'
group 'net.corda'
version "$corda_version"
}
// Check that we are running on a Java 8 JDK. The source/targetCompatibility values above aren't sufficient to
// guarantee this because those are properties checked by the Java plugin, but we're using Kotlin.
//
// We recommend a specific minor version (unfortunately, not checkable directly) because JavaFX adds APIs in
// minor releases, so we can't work with just any Java 8, it has to be a recent one.
if (!JavaVersion.current().java8Compatible)
throw new GradleException("Corda requires Java 8, please upgrade to at least 1.8.0_112")
repositories {
mavenLocal()
mavenCentral()
maven {
url 'http://oss.sonatype.org/content/repositories/snapshots'
}
jcenter()
maven {
url 'https://dl.bintray.com/kotlin/exposed'
}
}
sourceSets {
integrationTest {
kotlin {
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
srcDir file('src/integration-test/kotlin')
}
}
main {
resources {
srcDir "config/dev"
}
}
test {
resources {
srcDir "config/test"
}
}
}
//noinspection GroovyAssignabilityCheck
configurations {
// we don't want isolated.jar in classPath, since we want to test jar being dynamically loaded as an attachment
runtime.exclude module: 'isolated'
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime
}
// This is required for quasar. I think.
applicationDefaultJvmArgs = ["-javaagent:${configurations.quasar.singleFile}"]
// Needed by the :startScripts task
mainClassName = 'com.r3corda.demos.TraderDemoKt'
// To find potential version conflicts, run "gradle htmlDependencyReport" and then look in
// build/reports/project/dependencies/index.html for green highlighted parts of the tree.
// Required for building out the fat JAR.
dependencies {
compile project(':node')
// TODO: Demos should not depend on test code, but only use production APIs
compile project(':test-utils')
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
compile "org.jetbrains.kotlinx:kotlinx-support-jdk8:0.2"
// Unit testing helpers.
testCompile 'junit:junit:4.12'
testCompile 'org.assertj:assertj-core:3.4.1'
// Integration test helpers
integrationTestCompile 'junit:junit:4.12'
integrationTestCompile 'org.assertj:assertj-core:${assertj_version}'
integrationTestCompile project(':test-utils')
compile "com.google.guava:guava:19.0"
runtime project(path: ":node", configuration: 'runtimeArtifacts')
}
// Package up the demo programs.
task getAttachmentDemo(type: CreateStartScripts) {
mainClassName = "com.r3corda.demos.attachment.AttachmentDemoKt"
applicationName = "attachment-demo"
defaultJvmOpts = ["-javaagent:${configurations.quasar.singleFile}"]
outputDir = new File(project.buildDir, 'scripts')
classpath = jar.outputs.files + project.configurations.runtime
}
task getTraderDemo(type: CreateStartScripts) {
mainClassName = "com.r3corda.demos.TraderDemoKt"
applicationName = "trader-demo"
defaultJvmOpts = ["-javaagent:${configurations.quasar.singleFile}"]
outputDir = new File(project.buildDir, 'scripts')
classpath = jar.outputs.files + project.configurations.runtime
}
// Force windows script classpath to wildcard path to avoid the 'Command Line Is Too Long' issues
// with generated scripts. Include Jolokia .war explicitly as this isn't picked up by wildcard
tasks.withType(CreateStartScripts) {
doLast {
windowsScript.text = windowsScript
.readLines()
.collect { line -> line.replaceAll(~/^set CLASSPATH=.*$/, 'set CLASSPATH=%APP_HOME%/lib/*;%APP_HOME%/lib/jolokia-agent-war-'+project.ext.jolokia_version+'.war') }
.join('\r\n')
}
}
task integrationTest(type: Test, dependsOn: [':node:integrationTest',':client:integrationTest']) {
testClassesDir = sourceSets.integrationTest.output.classesDir
classpath = sourceSets.integrationTest.runtimeClasspath
}
task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) {
dependsOn = subprojects.test
additionalSourceDirs = files(subprojects.sourceSets.main.allSource.srcDirs)
@ -181,80 +121,37 @@ tasks.withType(Test) {
reports.html.destination = file("${reporting.baseDir}/${name}")
}
quasarScan.dependsOn('classes', 'core:classes', 'contracts:classes', 'node:classes')
applicationDistribution.into("bin") {
from(getAttachmentDemo)
from(getTraderDemo)
fileMode = 0755
}
task buildCordaJAR(type: FatCapsule, dependsOn: ['quasarScan', 'buildCertSigningRequestUtilityJAR']) {
applicationClass 'com.r3corda.node.MainKt'
archiveName 'corda.jar'
applicationSource = files(project.tasks.findByName('jar'), 'node/build/classes/main/CordaCaplet.class')
capsuleManifest {
appClassPath = ["jolokia-agent-war-${project.ext.jolokia_version}.war"]
systemProperties['log4j.configuration'] = 'log4j2.xml'
javaAgents = ["quasar-core-${quasar_version}-jdk8.jar"]
minJavaVersion = '1.8.0'
caplets = ['CordaCaplet']
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
directory "./build/nodes"
networkMap "Controller"
node {
name "Controller"
dirName "controller"
nearestCity "London"
advertisedServices = ["corda.notary.validating"]
artemisPort 10002
webPort 10003
cordapps = []
}
}
task buildCertSigningRequestUtilityJAR(type: FatCapsule, dependsOn: project.jar) {
applicationClass 'com.r3corda.node.utilities.certsigning.CertificateSignerKt'
archiveName 'certSigningRequestUtility.jar'
capsuleManifest {
systemProperties['log4j.configuration'] = 'log4j2.xml'
minJavaVersion = '1.8.0'
node {
name "Bank A"
dirName "nodea"
nearestCity "London"
advertisedServices = []
artemisPort 10004
webPort 10005
cordapps = []
}
}
task installTemplateNodes(dependsOn: 'buildCordaJAR') << {
copy {
from buildCordaJAR.outputs.getFiles()
from 'config/dev/nameservernode.conf'
into "${buildDir}/nodes/nameserver"
rename 'nameservernode.conf', 'node.conf'
}
copy {
from buildCordaJAR.outputs.getFiles()
from 'config/dev/generalnodea.conf'
into "${buildDir}/nodes/nodea"
rename 'generalnodea.conf', 'node.conf'
}
copy {
from buildCordaJAR.outputs.getFiles()
from 'config/dev/generalnodeb.conf'
into "${buildDir}/nodes/nodeb"
rename 'generalnodeb.conf', 'node.conf'
}
delete("${buildDir}/nodes/runnodes")
def jarName = buildCordaJAR.outputs.getFiles().getSingleFile().getName()
copy {
from "buildSrc/scripts/runnodes"
filter { String line -> line.replace("JAR_NAME", jarName) }
filter(org.apache.tools.ant.filters.FixCrLfFilter.class, eol: org.apache.tools.ant.filters.FixCrLfFilter.CrLf.newInstance("lf"))
into "${buildDir}/nodes"
node {
name "Bank B"
dirName "nodeb"
nearestCity "New York"
advertisedServices = []
artemisPort 10006
webPort 10007
cordapps = []
}
}
// Aliasing the publishToMavenLocal for simplicity.
task(install, dependsOn: 'publishToMavenLocal')
publishing {
publications {
corda(MavenPublication) {
artifactId 'corda'
artifact buildCordaJAR {
classifier ""
}
}
}
}

0
buildSrc/scripts/runnodes Normal file → Executable file
View File

View File

@ -1,20 +0,0 @@
import org.gradle.api.*
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.javadoc.Javadoc
/**
* A utility plugin that when applied will automatically create source and javadoc publishing tasks
*/
class DefaultPublishTasks implements Plugin<Project> {
void apply(Project project) {
project.task("sourceJar", type: Jar, dependsOn: project.classes) {
classifier = 'sources'
from project.sourceSets.main.allSource
}
project.task("javadocJar", type: Jar, dependsOn: project.javadoc) {
classifier = 'javadoc'
from project.javadoc.destinationDir
}
}
}

View File

@ -1,57 +0,0 @@
import org.gradle.api.*
import org.gradle.api.tasks.testing.Test
import org.gradle.api.tasks.JavaExec
/**
* QuasarPlugin creates a "quasar" configuration, adds quasar as a dependency and creates a "quasarScan" task that scans
* for `@Suspendable`s in the code
*/
class QuasarPlugin implements Plugin<Project> {
void apply(Project project) {
project.repositories {
mavenCentral()
}
project.configurations.create("quasar")
// To add a local .jar dependency:
// project.dependencies.add("quasar", project.files("${project.rootProject.projectDir}/lib/quasar.jar"))
project.dependencies.add("quasar", "co.paralleluniverse:quasar-core:${project.rootProject.ext.quasar_version}:jdk8@jar")
project.dependencies.add("compile", project.configurations.getByName("quasar"))
project.tasks.withType(Test) {
jvmArgs "-javaagent:${project.configurations.quasar.singleFile}"
jvmArgs "-Dco.paralleluniverse.fibers.verifyInstrumentation"
}
project.tasks.withType(JavaExec) {
jvmArgs "-javaagent:${project.configurations.quasar.singleFile}"
jvmArgs "-Dco.paralleluniverse.fibers.verifyInstrumentation"
}
project.task("quasarScan") {
inputs.files(project.sourceSets.main.output)
outputs.files(
"$project.sourceSets.main.output.resourcesDir/META-INF/suspendables",
"$project.sourceSets.main.output.resourcesDir/META-INF/suspendable-supers"
)
} << {
// These lines tell gradle to run the Quasar suspendables scanner to look for unannotated super methods
// that have @Suspendable sub implementations. These tend to cause NPEs and are not caught by the verifier
// NOTE: need to make sure the output isn't on the classpath or every other run it generates empty results, so
// we explicitly delete to avoid that happening. We also need to turn off what seems to be a spurious warning in the IDE
ant.taskdef(name:'scanSuspendables', classname:'co.paralleluniverse.fibers.instrument.SuspendablesScanner',
classpath: "${project.sourceSets.main.output.classesDir}:${project.sourceSets.main.output.resourcesDir}:${project.configurations.runtime.asPath}")
project.delete "$project.sourceSets.main.output.resourcesDir/META-INF/suspendables", "$project.sourceSets.main.output.resourcesDir/META-INF/suspendable-supers"
ant.scanSuspendables(
auto:false,
suspendablesFile: "$project.sourceSets.main.output.resourcesDir/META-INF/suspendables",
supersFile: "$project.sourceSets.main.output.resourcesDir/META-INF/suspendable-supers") {
fileset(dir: project.sourceSets.main.output.classesDir)
}
}
project.jar.dependsOn project.quasarScan
}
}

View File

@ -1,6 +1,6 @@
apply plugin: 'kotlin'
apply plugin: QuasarPlugin
apply plugin: DefaultPublishTasks
apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.publish-utils'
repositories {
mavenLocal()
@ -80,8 +80,6 @@ dependencies {
integrationTestCompile 'junit:junit:4.12'
}
quasarScan.dependsOn('classes', ':core:classes', ':contracts:classes')
task integrationTest(type: Test) {
testClassesDir = sourceSets.integrationTest.output.classesDir
classpath = sourceSets.integrationTest.runtimeClasspath

View File

@ -1,62 +0,0 @@
package com.r3corda.client
import com.r3corda.core.random63BitValue
import com.r3corda.node.driver.driver
import com.r3corda.node.services.config.configureTestSSL
import com.r3corda.node.services.messaging.ArtemisMessagingComponent.Companion.toHostAndPort
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.util.concurrent.CountDownLatch
import kotlin.concurrent.thread
class CordaRPCClientTest {
private val validUsername = "user1"
private val validPassword = "test"
private val stopDriver = CountDownLatch(1)
private var driverThread: Thread? = null
private lateinit var client: CordaRPCClient
@Before
fun start() {
val driverStarted = CountDownLatch(1)
driverThread = thread {
driver {
val driverInfo = startNode().get()
client = CordaRPCClient(toHostAndPort(driverInfo.nodeInfo.address), configureTestSSL())
driverStarted.countDown()
stopDriver.await()
}
}
driverStarted.await()
}
@After
fun stop() {
stopDriver.countDown()
driverThread?.join()
}
@Test
fun `log in with valid username and password`() {
client.start(validUsername, validPassword)
}
@Test
fun `log in with unknown user`() {
assertThatExceptionOfType(ActiveMQSecurityException::class.java).isThrownBy {
client.start(random63BitValue().toString(), validPassword)
}
}
@Test
fun `log in with incorrect password`() {
assertThatExceptionOfType(ActiveMQSecurityException::class.java).isThrownBy {
client.start(validUsername, random63BitValue().toString())
}
}
}

View File

@ -1,225 +0,0 @@
package com.r3corda.client
import com.r3corda.client.model.NodeMonitorModel
import com.r3corda.client.model.ProgressTrackingEvent
import com.r3corda.core.bufferUntilSubscribed
import com.r3corda.core.contracts.*
import com.r3corda.core.node.NodeInfo
import com.r3corda.core.node.services.NetworkMapCache
import com.r3corda.core.node.services.ServiceInfo
import com.r3corda.core.node.services.StateMachineTransactionMapping
import com.r3corda.core.node.services.Vault
import com.r3corda.core.protocols.StateMachineRunId
import com.r3corda.core.serialization.OpaqueBytes
import com.r3corda.core.transactions.SignedTransaction
import com.r3corda.node.driver.driver
import com.r3corda.node.services.config.configureTestSSL
import com.r3corda.node.services.messaging.StateMachineUpdate
import com.r3corda.node.services.transactions.SimpleNotaryService
import com.r3corda.testing.expect
import com.r3corda.testing.expectEvents
import com.r3corda.testing.sequence
import org.junit.After
import org.junit.Before
import org.junit.Test
import rx.Observable
import rx.Observer
import java.util.concurrent.CountDownLatch
import kotlin.concurrent.thread
class NodeMonitorModelTest {
lateinit var aliceNode: NodeInfo
lateinit var notaryNode: NodeInfo
val stopDriver = CountDownLatch(1)
var driverThread: Thread? = null
lateinit var stateMachineTransactionMapping: Observable<StateMachineTransactionMapping>
lateinit var stateMachineUpdates: Observable<StateMachineUpdate>
lateinit var progressTracking: Observable<ProgressTrackingEvent>
lateinit var transactions: Observable<SignedTransaction>
lateinit var vaultUpdates: Observable<Vault.Update>
lateinit var networkMapUpdates: Observable<NetworkMapCache.MapChange>
lateinit var clientToService: Observer<ClientToServiceCommand>
lateinit var newNode: (String) -> NodeInfo
@Before
fun start() {
val driverStarted = CountDownLatch(1)
driverThread = thread {
driver {
val aliceNodeFuture = startNode("Alice")
val notaryNodeFuture = startNode("Notary", advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
aliceNode = aliceNodeFuture.get().nodeInfo
notaryNode = notaryNodeFuture.get().nodeInfo
newNode = { nodeName -> startNode(nodeName).get().nodeInfo }
val monitor = NodeMonitorModel()
stateMachineTransactionMapping = monitor.stateMachineTransactionMapping.bufferUntilSubscribed()
stateMachineUpdates = monitor.stateMachineUpdates.bufferUntilSubscribed()
progressTracking = monitor.progressTracking.bufferUntilSubscribed()
transactions = monitor.transactions.bufferUntilSubscribed()
vaultUpdates = monitor.vaultUpdates.bufferUntilSubscribed()
networkMapUpdates = monitor.networkMap.bufferUntilSubscribed()
clientToService = monitor.clientToService
monitor.register(aliceNode, configureTestSSL(), "user1", "test")
driverStarted.countDown()
stopDriver.await()
}
}
driverStarted.await()
}
@After
fun stop() {
stopDriver.countDown()
driverThread?.join()
}
@Test
fun testNetworkMapUpdate() {
newNode("Bob")
newNode("Charlie")
networkMapUpdates.expectEvents(isStrict = false) {
sequence(
// TODO : Add test for remove when driver DSL support individual node shutdown.
expect { output: NetworkMapCache.MapChange ->
require(output.node.legalIdentity.name == "Alice") { output.node.legalIdentity.name }
},
expect { output: NetworkMapCache.MapChange ->
require(output.node.legalIdentity.name == "Bob") { output.node.legalIdentity.name }
},
expect { output: NetworkMapCache.MapChange ->
require(output.node.legalIdentity.name == "Charlie") { output.node.legalIdentity.name }
}
)
}
}
@Test
fun cashIssueWorksEndToEnd() {
clientToService.onNext(ClientToServiceCommand.IssueCash(
amount = Amount(100, USD),
issueRef = OpaqueBytes(ByteArray(1, { 1 })),
recipient = aliceNode.legalIdentity,
notary = notaryNode.notaryIdentity
))
vaultUpdates.expectEvents(isStrict = false) {
sequence(
// SNAPSHOT
expect { output: Vault.Update ->
require(output.consumed.size == 0) { output.consumed.size }
require(output.produced.size == 0) { output.produced.size }
},
// ISSUE
expect { output: Vault.Update ->
require(output.consumed.size == 0) { output.consumed.size }
require(output.produced.size == 1) { output.produced.size }
}
)
}
}
@Test
fun issueAndMoveWorks() {
clientToService.onNext(ClientToServiceCommand.IssueCash(
amount = Amount(100, USD),
issueRef = OpaqueBytes(ByteArray(1, { 1 })),
recipient = aliceNode.legalIdentity,
notary = notaryNode.notaryIdentity
))
clientToService.onNext(ClientToServiceCommand.PayCash(
amount = Amount(100, Issued(PartyAndReference(aliceNode.legalIdentity, OpaqueBytes(ByteArray(1, { 1 }))), USD)),
recipient = aliceNode.legalIdentity
))
var issueSmId: StateMachineRunId? = null
var moveSmId: StateMachineRunId? = null
var issueTx: SignedTransaction? = null
var moveTx: SignedTransaction? = null
stateMachineUpdates.expectEvents {
sequence(
// ISSUE
expect { add: StateMachineUpdate.Added ->
issueSmId = add.id
},
expect { remove: StateMachineUpdate.Removed ->
require(remove.id == issueSmId)
},
// MOVE
expect { add: StateMachineUpdate.Added ->
moveSmId = add.id
},
expect { remove: StateMachineUpdate.Removed ->
require(remove.id == moveSmId)
}
)
}
transactions.expectEvents {
sequence(
// ISSUE
expect { tx ->
require(tx.tx.inputs.isEmpty())
require(tx.tx.outputs.size == 1)
val signaturePubKeys = tx.sigs.map { it.by }.toSet()
// Only Alice signed
require(signaturePubKeys.size == 1)
require(signaturePubKeys.contains(aliceNode.legalIdentity.owningKey))
issueTx = tx
},
// MOVE
expect { tx ->
require(tx.tx.inputs.size == 1)
require(tx.tx.outputs.size == 1)
val signaturePubKeys = tx.sigs.map { it.by }.toSet()
// Alice and Notary signed
require(signaturePubKeys.size == 2)
require(signaturePubKeys.contains(aliceNode.legalIdentity.owningKey))
require(signaturePubKeys.contains(notaryNode.notaryIdentity.owningKey))
moveTx = tx
}
)
}
vaultUpdates.expectEvents {
sequence(
// SNAPSHOT
expect { output: Vault.Update ->
require(output.consumed.size == 0) { output.consumed.size }
require(output.produced.size == 0) { output.produced.size }
},
// ISSUE
expect { update ->
require(update.consumed.size == 0) { update.consumed.size }
require(update.produced.size == 1) { update.produced.size }
},
// MOVE
expect { update ->
require(update.consumed.size == 1) { update.consumed.size }
require(update.produced.size == 1) { update.produced.size }
}
)
}
stateMachineTransactionMapping.expectEvents {
sequence(
// ISSUE
expect { mapping ->
require(mapping.stateMachineRunId == issueSmId)
require(mapping.transactionId == issueTx!!.id)
},
// MOVE
expect { mapping ->
require(mapping.stateMachineRunId == moveSmId)
require(mapping.transactionId == moveTx!!.id)
}
)
}
}
}

View File

@ -0,0 +1,87 @@
package net.corda.client
import net.corda.core.contracts.DOLLARS
import net.corda.core.getOrThrow
import net.corda.core.node.services.ServiceInfo
import net.corda.core.random63BitValue
import net.corda.core.serialization.OpaqueBytes
import net.corda.flows.CashCommand
import net.corda.flows.CashFlow
import net.corda.node.driver.NodeInfoAndConfig
import net.corda.node.driver.driver
import net.corda.node.services.User
import net.corda.node.services.config.configureTestSSL
import net.corda.node.services.messaging.ArtemisMessagingComponent.Companion.toHostAndPort
import net.corda.node.services.messaging.startFlow
import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.ValidatingNotaryService
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.util.concurrent.CountDownLatch
import kotlin.concurrent.thread
class CordaRPCClientTest {
private val rpcUser = User("user1", "test", permissions = setOf(startFlowPermission<CashFlow>()))
private val stopDriver = CountDownLatch(1)
private var driverThread: Thread? = null
private lateinit var client: CordaRPCClient
private lateinit var driverInfo: NodeInfoAndConfig
@Before
fun start() {
val driverStarted = CountDownLatch(1)
driverThread = thread {
driver(isDebug = true) {
driverInfo = startNode(rpcUsers = listOf(rpcUser), advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type))).getOrThrow()
client = CordaRPCClient(toHostAndPort(driverInfo.nodeInfo.address), configureTestSSL())
driverStarted.countDown()
stopDriver.await()
}
}
driverStarted.await()
}
@After
fun stop() {
stopDriver.countDown()
driverThread?.join()
}
@Test
fun `log in with valid username and password`() {
client.start(rpcUser.username, rpcUser.password)
}
@Test
fun `log in with unknown user`() {
assertThatExceptionOfType(ActiveMQSecurityException::class.java).isThrownBy {
client.start(random63BitValue().toString(), rpcUser.password)
}
}
@Test
fun `log in with incorrect password`() {
assertThatExceptionOfType(ActiveMQSecurityException::class.java).isThrownBy {
client.start(rpcUser.username, random63BitValue().toString())
}
}
@Test
fun `indefinite block bug`() {
println("Starting client")
client.start(rpcUser.username, rpcUser.password)
println("Creating proxy")
val proxy = client.proxy()
println("Starting flow")
val flowHandle = proxy.startFlow(::CashFlow, CashCommand.IssueCash(20.DOLLARS, OpaqueBytes.of(0), driverInfo.nodeInfo.legalIdentity, driverInfo.nodeInfo.legalIdentity))
println("Started flow, waiting on result")
flowHandle.progress.subscribe {
println("PROGRESS $it")
}
println("Result: ${flowHandle.returnValue.toBlocking().first()}")
}
}

View File

@ -0,0 +1,236 @@
package net.corda.client
import net.corda.client.model.NodeMonitorModel
import net.corda.client.model.ProgressTrackingEvent
import net.corda.core.bufferUntilSubscribed
import net.corda.core.contracts.Amount
import net.corda.core.contracts.Issued
import net.corda.core.contracts.PartyAndReference
import net.corda.core.contracts.USD
import net.corda.core.flows.StateMachineRunId
import net.corda.core.getOrThrow
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.StateMachineTransactionMapping
import net.corda.core.node.services.Vault
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.transactions.SignedTransaction
import net.corda.flows.CashCommand
import net.corda.flows.CashFlow
import net.corda.node.driver.driver
import net.corda.node.services.User
import net.corda.node.services.config.configureTestSSL
import net.corda.node.services.messaging.ArtemisMessagingComponent
import net.corda.node.services.messaging.StateMachineUpdate
import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.testing.expect
import net.corda.testing.expectEvents
import net.corda.testing.sequence
import org.junit.After
import org.junit.Before
import org.junit.Test
import rx.Observable
import rx.Observer
import java.util.concurrent.CountDownLatch
import kotlin.concurrent.thread
class NodeMonitorModelTest {
lateinit var aliceNode: NodeInfo
lateinit var notaryNode: NodeInfo
val stopDriver = CountDownLatch(1)
var driverThread: Thread? = null
lateinit var stateMachineTransactionMapping: Observable<StateMachineTransactionMapping>
lateinit var stateMachineUpdates: Observable<StateMachineUpdate>
lateinit var progressTracking: Observable<ProgressTrackingEvent>
lateinit var transactions: Observable<SignedTransaction>
lateinit var vaultUpdates: Observable<Vault.Update>
lateinit var networkMapUpdates: Observable<NetworkMapCache.MapChange>
lateinit var clientToService: Observer<CashCommand>
lateinit var newNode: (String) -> NodeInfo
@Before
fun start() {
val driverStarted = CountDownLatch(1)
driverThread = thread {
driver {
val cashUser = User("user1", "test", permissions = setOf(startFlowPermission<CashFlow>()))
val aliceNodeFuture = startNode("Alice", rpcUsers = listOf(cashUser))
val notaryNodeFuture = startNode("Notary", advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
aliceNode = aliceNodeFuture.getOrThrow().nodeInfo
notaryNode = notaryNodeFuture.getOrThrow().nodeInfo
newNode = { nodeName -> startNode(nodeName).getOrThrow().nodeInfo }
val monitor = NodeMonitorModel()
stateMachineTransactionMapping = monitor.stateMachineTransactionMapping.bufferUntilSubscribed()
stateMachineUpdates = monitor.stateMachineUpdates.bufferUntilSubscribed()
progressTracking = monitor.progressTracking.bufferUntilSubscribed()
transactions = monitor.transactions.bufferUntilSubscribed()
vaultUpdates = monitor.vaultUpdates.bufferUntilSubscribed()
networkMapUpdates = monitor.networkMap.bufferUntilSubscribed()
clientToService = monitor.clientToService
monitor.register(ArtemisMessagingComponent.toHostAndPort(aliceNode.address), configureTestSSL(), cashUser.username, cashUser.password)
driverStarted.countDown()
stopDriver.await()
}
}
driverStarted.await()
}
@After
fun stop() {
stopDriver.countDown()
driverThread?.join()
}
@Test
fun `network map update`() {
newNode("Bob")
newNode("Charlie")
networkMapUpdates.filter { !it.node.advertisedServices.any { it.info.type.isNotary() } }
.filter { !it.node.advertisedServices.any { it.info.type == NetworkMapService.type } }
.expectEvents(isStrict = false) {
sequence(
// TODO : Add test for remove when driver DSL support individual node shutdown.
expect { output: NetworkMapCache.MapChange ->
require(output.node.legalIdentity.name == "Alice") { "Expecting : Alice, Actual : ${output.node.legalIdentity.name}" }
},
expect { output: NetworkMapCache.MapChange ->
require(output.node.legalIdentity.name == "Bob") { "Expecting : Bob, Actual : ${output.node.legalIdentity.name}" }
},
expect { output: NetworkMapCache.MapChange ->
require(output.node.legalIdentity.name == "Charlie") { "Expecting : Charlie, Actual : ${output.node.legalIdentity.name}" }
}
)
}
}
@Test
fun `cash issue works end to end`() {
clientToService.onNext(CashCommand.IssueCash(
amount = Amount(100, USD),
issueRef = OpaqueBytes(ByteArray(1, { 1 })),
recipient = aliceNode.legalIdentity,
notary = notaryNode.notaryIdentity
))
vaultUpdates.expectEvents(isStrict = false) {
sequence(
// SNAPSHOT
expect { output: Vault.Update ->
require(output.consumed.size == 0) { output.consumed.size }
require(output.produced.size == 0) { output.produced.size }
},
// ISSUE
expect { output: Vault.Update ->
require(output.consumed.size == 0) { output.consumed.size }
require(output.produced.size == 1) { output.produced.size }
}
)
}
}
@Test
fun `cash issue and move`() {
clientToService.onNext(CashCommand.IssueCash(
amount = Amount(100, USD),
issueRef = OpaqueBytes(ByteArray(1, { 1 })),
recipient = aliceNode.legalIdentity,
notary = notaryNode.notaryIdentity
))
clientToService.onNext(CashCommand.PayCash(
amount = Amount(100, Issued(PartyAndReference(aliceNode.legalIdentity, OpaqueBytes(ByteArray(1, { 1 }))), USD)),
recipient = aliceNode.legalIdentity
))
var issueSmId: StateMachineRunId? = null
var moveSmId: StateMachineRunId? = null
var issueTx: SignedTransaction? = null
var moveTx: SignedTransaction? = null
stateMachineUpdates.expectEvents {
sequence(
// ISSUE
expect { add: StateMachineUpdate.Added ->
issueSmId = add.id
},
expect { remove: StateMachineUpdate.Removed ->
require(remove.id == issueSmId)
},
// MOVE
expect { add: StateMachineUpdate.Added ->
moveSmId = add.id
},
expect { remove: StateMachineUpdate.Removed ->
require(remove.id == moveSmId)
}
)
}
transactions.expectEvents {
sequence(
// ISSUE
expect { tx ->
require(tx.tx.inputs.isEmpty())
require(tx.tx.outputs.size == 1)
val signaturePubKeys = tx.sigs.map { it.by }.toSet()
// Only Alice signed
val aliceKey = aliceNode.legalIdentity.owningKey
require(signaturePubKeys.size <= aliceKey.keys.size)
require(aliceKey.isFulfilledBy(signaturePubKeys))
issueTx = tx
},
// MOVE
expect { tx ->
require(tx.tx.inputs.size == 1)
require(tx.tx.outputs.size == 1)
val signaturePubKeys = tx.sigs.map { it.by }.toSet()
// Alice and Notary signed
require(aliceNode.legalIdentity.owningKey.isFulfilledBy(signaturePubKeys))
require(notaryNode.notaryIdentity.owningKey.isFulfilledBy(signaturePubKeys))
moveTx = tx
}
)
}
vaultUpdates.expectEvents {
sequence(
// SNAPSHOT
expect { output: Vault.Update ->
require(output.consumed.size == 0) { output.consumed.size }
require(output.produced.size == 0) { output.produced.size }
},
// ISSUE
expect { update ->
require(update.consumed.size == 0) { update.consumed.size }
require(update.produced.size == 1) { update.produced.size }
},
// MOVE
expect { update ->
require(update.consumed.size == 1) { update.consumed.size }
require(update.produced.size == 1) { update.produced.size }
}
)
}
stateMachineTransactionMapping.expectEvents {
sequence(
// ISSUE
expect { mapping ->
require(mapping.stateMachineRunId == issueSmId)
require(mapping.transactionId == issueTx!!.id)
},
// MOVE
expect { mapping ->
require(mapping.stateMachineRunId == moveSmId)
require(mapping.transactionId == moveTx!!.id)
}
)
}
}
}

View File

@ -1,294 +0,0 @@
package com.r3corda.client.impl
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.KryoException
import com.esotericsoftware.kryo.Serializer
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import com.google.common.cache.CacheBuilder
import com.r3corda.client.CordaRPCClient
import com.r3corda.core.ErrorOr
import com.r3corda.core.bufferUntilSubscribed
import com.r3corda.core.random63BitValue
import com.r3corda.core.serialization.deserialize
import com.r3corda.core.serialization.serialize
import com.r3corda.core.utilities.debug
import com.r3corda.core.utilities.trace
import com.r3corda.node.services.messaging.*
import org.apache.activemq.artemis.api.core.ActiveMQObjectClosedException
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.client.ClientConsumer
import org.apache.activemq.artemis.api.core.client.ClientMessage
import org.apache.activemq.artemis.api.core.client.ClientProducer
import org.apache.activemq.artemis.api.core.client.ClientSession
import rx.Observable
import rx.subjects.PublishSubject
import java.io.Closeable
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
import java.time.Duration
import java.util.*
import java.util.concurrent.locks.ReentrantLock
import javax.annotation.concurrent.GuardedBy
import javax.annotation.concurrent.ThreadSafe
import kotlin.concurrent.withLock
import kotlin.reflect.jvm.javaMethod
/**
* Core RPC engine implementation, you should be looking at [CordaRPCClient].
*
* @suppress
*/
class CordaRPCClientImpl(private val session: ClientSession,
private val sessionLock: ReentrantLock,
private val myAddressPrefix: String) {
companion object {
private val closeableCloseMethod = Closeable::close.javaMethod
private val autocloseableCloseMethod = AutoCloseable::close.javaMethod
}
/**
* Builds a proxy for the given type, which must descend from [RPCOps].
*
* @see CordaRPCClient.proxy for more information about how to use the proxies.
*/
fun <T : RPCOps> proxyFor(rpcInterface: Class<T>, timeout: Duration? = null, minVersion: Int = 0): T {
sessionLock.withLock {
if (producer == null)
producer = session.createProducer()
}
val proxyImpl = RPCProxyHandler(timeout)
@Suppress("UNCHECKED_CAST")
val proxy = Proxy.newProxyInstance(rpcInterface.classLoader, arrayOf(rpcInterface, Closeable::class.java), proxyImpl) as T
proxyImpl.serverProtocolVersion = proxy.protocolVersion
if (minVersion > proxyImpl.serverProtocolVersion)
throw RPCException("Requested minimum protocol version $minVersion is higher than the server's supported protocol version (${proxyImpl.serverProtocolVersion})")
return proxy
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//region RPC engine
//
// You can find docs on all this in the api doc for the proxyFor method, and in the docsite.
// Utility to quickly suck out the contents of an Artemis message. There's probably a more efficient way to
// do this.
private fun <T : Any> ClientMessage.deserialize(kryo: Kryo): T = ByteArray(bodySize).apply { bodyBuffer.readBytes(this) }.deserialize(kryo)
@GuardedBy("sessionLock")
private val addressToQueueObservables = CacheBuilder.newBuilder().build<String, QueuedObservable>()
private var producer: ClientProducer? = null
private inner class ObservableDeserializer(private val qName: String,
private val rpcName: String,
private val rpcLocation: Throwable) : Serializer<Observable<Any>>() {
override fun read(kryo: Kryo, input: Input, type: Class<Observable<Any>>): Observable<Any> {
val handle = input.readInt(true)
return sessionLock.withLock {
var ob = addressToQueueObservables.getIfPresent(qName)
if (ob == null) {
ob = QueuedObservable(qName, rpcName, rpcLocation, this)
addressToQueueObservables.put(qName, ob)
}
val result = ob.getForHandle(handle)
rpcLog.trace { "Deserializing and connecting a new observable for $rpcName on $qName: $result" }
result
}
}
override fun write(kryo: Kryo, output: Output, `object`: Observable<Any>) {
throw UnsupportedOperationException("not implemented")
}
}
/**
* The proxy class returned to the client is auto-generated on the fly by the java.lang.reflect Proxy
* infrastructure. The JDK Proxy class writes bytecode into memory for a class that implements the requested
* interfaces and then routes all method calls to the invoke method below in a conveniently reified form.
* We can then easily take the data about the method call and turn it into an RPC. This avoids the need
* for the compile-time code generation which is so common in RPC systems.
*/
@ThreadSafe
private inner class RPCProxyHandler(private val timeout: Duration?) : InvocationHandler, Closeable {
private val proxyAddress = "$myAddressPrefix.rpc.responses.${random63BitValue()}"
private val consumer: ClientConsumer
var serverProtocolVersion = 0
init {
consumer = sessionLock.withLock{
session.createTemporaryQueue(proxyAddress, proxyAddress)
session.createConsumer(proxyAddress)
}
}
@Synchronized
override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {
if (isCloseInvocation(method)) {
close()
return null
}
if (method.name == "toString" && args == null)
return "Client RPC proxy"
if (consumer.isClosed)
throw RPCException("RPC Proxy is closed")
// All invoked methods on the proxy end up here.
val location = Throwable()
rpcLog.trace {
val argStr = args?.joinToString() ?: ""
"-> RPC -> ${method.name}($argStr): ${method.returnType}"
}
checkMethodVersion(method)
// sendRequest may return a reconfigured Kryo if the method returns observables.
val kryo: Kryo = sendRequest(args, location, method) ?: createRPCKryo()
val next = receiveResponse(kryo, method, timeout)
rpcLog.trace { "<- RPC <- ${method.name} = $next" }
return unwrapOrThrow(next)
}
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
private fun unwrapOrThrow(next: ErrorOr<*>): Any? {
val ex = next.error
if (ex != null) {
// Replace the stack trace because that's an implementation detail of the server that isn't so
// helpful to the user who wants to see where the error was on their side, and serialising stack
// frame objects is a bit annoying. We slice it here to avoid the invoke() machinery being exposed.
// The resulting exception looks like it was thrown from inside the called method.
(ex as java.lang.Throwable).stackTrace = java.lang.Throwable().stackTrace.let { it.sliceArray(1..it.size - 1) }
throw ex
} else {
return next.value
}
}
private fun receiveResponse(kryo: Kryo, method: Method, timeout: Duration?): ErrorOr<*> {
val artemisMessage: ClientMessage =
if (timeout == null)
consumer.receive() ?: throw ActiveMQObjectClosedException()
else
consumer.receive(timeout.toMillis()) ?: throw RPCException.DeadlineExceeded(method.name)
artemisMessage.acknowledge()
val next = artemisMessage.deserialize<ErrorOr<*>>(kryo)
return next
}
private fun sendRequest(args: Array<out Any>?, location: Throwable, method: Method): Kryo? {
// We could of course also check the return type of the method to see if it's Observable, but I'd
// rather haved the annotation be used consistently.
val returnsObservables = method.isAnnotationPresent(RPCReturnsObservables::class.java)
sessionLock.withLock {
val msg = createMessage(method)
val kryo = if (returnsObservables) maybePrepareForObservables(location, method, msg) else null
val argsArray = args ?: Array<Any?>(0) { null }
val serializedBytes = try {
argsArray.serialize()
} catch (e: KryoException) {
throw RPCException("Could not serialize RPC arguments", e)
}
msg.writeBodyBufferBytes(serializedBytes.bits)
producer!!.send(ArtemisMessagingComponent.RPC_REQUESTS_QUEUE, msg)
return kryo
}
}
private fun maybePrepareForObservables(location: Throwable, method: Method, msg: ClientMessage): Kryo {
// Create a temporary queue just for the emissions on any observables that are returned.
val qName = "$myAddressPrefix.rpc.observations.${random63BitValue()}"
session.createTemporaryQueue(qName, qName)
msg.putStringProperty(ClientRPCRequestMessage.OBSERVATIONS_TO, qName)
// And make sure that we deserialise observable handles so that they're linked to the right
// queue. Also record a bit of metadata for debugging purposes.
return createRPCKryo(observableSerializer = ObservableDeserializer(qName, method.name, location))
}
private fun createMessage(method: Method): ClientMessage {
return session.createMessage(false).apply {
putStringProperty(ClientRPCRequestMessage.METHOD_NAME, method.name)
putStringProperty(ClientRPCRequestMessage.REPLY_TO, proxyAddress)
// Use the magic deduplication property built into Artemis as our message identity too
putStringProperty(org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID, SimpleString(UUID.randomUUID().toString()))
}
}
private fun checkMethodVersion(method: Method) {
val methodVersion = method.getAnnotation(RPCSinceVersion::class.java)?.version ?: 0
if (methodVersion > serverProtocolVersion)
throw UnsupportedOperationException("Method ${method.name} was added in RPC protocol version $methodVersion but the server is running $serverProtocolVersion")
}
private fun isCloseInvocation(method: Method) = method == closeableCloseMethod || method == autocloseableCloseMethod
override fun close() {
consumer.close()
sessionLock.withLock { session.deleteQueue(proxyAddress) }
}
override fun toString() = "Corda RPC Proxy listening on queue $proxyAddress"
}
/**
* When subscribed to, starts consuming from the given queue name and demultiplexing the observables being
* sent to it. The server queue is moved into in-memory buffers (one per attached server-side observable)
* until drained through a subscription. When the subscriptions are all gone, the server-side queue is deleted.
*/
@ThreadSafe
private inner class QueuedObservable(private val qName: String,
private val rpcName: String,
private val rpcLocation: Throwable,
private val observableDeserializer: ObservableDeserializer) {
private val root = PublishSubject.create<MarshalledObservation>()
private val rootShared = root.doOnUnsubscribe { close() }.share()
// This could be made more efficient by using a specialised IntMap
private val observables = HashMap<Int, Observable<Any>>()
private var consumer: ClientConsumer? = sessionLock.withLock { session.createConsumer(qName) }.setMessageHandler { deliver(it) }
@Synchronized
fun getForHandle(handle: Int): Observable<Any> {
return observables.getOrPut(handle) {
rootShared.filter { it.forHandle == handle }.map { it.what }.dematerialize<Any>().bufferUntilSubscribed().share()
}
}
private fun deliver(msg: ClientMessage) {
msg.acknowledge()
val kryo = createRPCKryo(observableSerializer = observableDeserializer)
val received: MarshalledObservation = msg.deserialize(kryo)
rpcLog.debug { "<- Observable [$rpcName] <- Received $received" }
synchronized(this) {
// Force creation of the buffer if it doesn't already exist.
getForHandle(received.forHandle)
root.onNext(received)
}
}
@Synchronized
fun close() {
rpcLog.debug("Closing queue observable for call to $rpcName : $qName")
consumer?.close()
consumer = null
sessionLock.withLock { session.deleteQueue(qName) }
}
@Suppress("UNUSED")
fun finalize() {
val c = synchronized(this) { consumer }
if (c != null) {
rpcLog.warn("A hot observable returned from an RPC ($rpcName) was never subscribed to or explicitly closed. " +
"This wastes server-side resources because it was queueing observations for retrieval. " +
"It is being closed now, but please adjust your code to cast the observable to AutoCloseable and then close it explicitly.", rpcLocation)
c.close()
}
}
}
//endregion
}

View File

@ -1,99 +0,0 @@
package com.r3corda.client.mock
import com.r3corda.contracts.asset.Cash
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.serialization.OpaqueBytes
import com.r3corda.core.transactions.TransactionBuilder
import java.time.Instant
/**
* [Generator]s for incoming/outgoing events to/from the [WalletMonitorService]. Internally it keeps track of owned
* state/ref pairs, but it doesn't necessarily generate "correct" events!
*/
class EventGenerator(
val parties: List<Party>,
val notary: Party
) {
private var vault = listOf<StateAndRef<Cash.State>>()
val issuerGenerator =
Generator.pickOne(parties).combine(Generator.intRange(0, 1)) { party, ref -> party.ref(ref.toByte()) }
val currencies = setOf(USD, GBP, CHF).toList() // + Currency.getAvailableCurrencies().toList().subList(0, 3).toSet()).toList()
val currencyGenerator = Generator.pickOne(currencies)
val amountIssuedGenerator =
Generator.intRange(1, 10000).combine(issuerGenerator, currencyGenerator) { amount, issuer, currency ->
Amount(amount.toLong(), Issued(issuer, currency))
}
val publicKeyGenerator = Generator.oneOf(parties.map { it.owningKey })
val partyGenerator = Generator.oneOf(parties)
val cashStateGenerator = amountIssuedGenerator.combine(publicKeyGenerator) { amount, from ->
val builder = TransactionBuilder(notary = notary)
builder.addOutputState(Cash.State(amount, from))
builder.addCommand(Command(Cash.Commands.Issue(), amount.token.issuer.party.owningKey))
builder.toWireTransaction().outRef<Cash.State>(0)
}
val consumedGenerator: Generator<Set<StateRef>> = Generator.frequency(
0.7 to Generator.pure(setOf()),
0.3 to Generator.impure { vault }.bind { states ->
Generator.sampleBernoulli(states, 0.2).map { someStates ->
val consumedSet = someStates.map { it.ref }.toSet()
vault = vault.filter { it.ref !in consumedSet }
consumedSet
}
}
)
val producedGenerator: Generator<Set<StateAndRef<ContractState>>> = Generator.frequency(
// 0.1 to Generator.pure(setOf())
0.9 to Generator.impure { vault }.bind { states ->
Generator.replicate(2, cashStateGenerator).map {
vault = states + it
it.toSet()
}
}
)
val issueRefGenerator = Generator.intRange(0, 1).map { number -> OpaqueBytes(ByteArray(1, { number.toByte() })) }
val amountGenerator = Generator.intRange(0, 10000).combine(currencyGenerator) { quantity, currency -> Amount(quantity.toLong(), currency) }
val issueCashGenerator =
amountGenerator.combine(partyGenerator, issueRefGenerator) { amount, to, issueRef ->
ClientToServiceCommand.IssueCash(
amount,
issueRef,
to,
notary
)
}
val moveCashGenerator =
amountIssuedGenerator.combine(
partyGenerator
) { amountIssued, recipient ->
ClientToServiceCommand.PayCash(
amount = amountIssued,
recipient = recipient
)
}
val exitCashGenerator =
amountIssuedGenerator.map {
ClientToServiceCommand.ExitCash(
it.withoutIssuer(),
it.token.issuer.reference
)
}
val clientToServiceCommandGenerator = Generator.frequency(
0.4 to issueCashGenerator,
0.5 to moveCashGenerator,
0.1 to exitCashGenerator
)
}

View File

@ -1,175 +0,0 @@
package com.r3corda.client.mock
import com.r3corda.core.ErrorOr
import java.util.*
/**
* This file defines a basic [Generator] library for composing random generators of objects.
*
* An object of type [Generator]<[A]> captures a generator of [A]s. Generators may be composed in several ways.
*
* [Generator.choice] picks a generator from the specified list and runs that.
* [Generator.frequency] is similar to [choice] but the probability may be specified for each generator (it is normalised before picking).
* [Generator.combine] combines two generators of A and B with a function (A, B) -> C. Variants exist for other arities.
* [Generator.bind] sequences two generators using an arbitrary A->Generator<B> function. Keep the usage of this
* function minimal as it may explode the stack, especially when using recursion.
*
* There are other utilities as well, the type of which are usually descriptive.
*
* Example:
* val birdNameGenerator = Generator.pickOne(listOf("raven", "pigeon"))
* val birdHeightGenerator = Generator.doubleRange(from = 10.0, to = 30.0)
* val birdGenerator = birdNameGenerator.combine(birdHeightGenerator) { name, height -> Bird(name, height) }
* val birdsGenerator = Generator.replicate(2, birdGenerator)
* val mammalsGenerator = Generator.sampleBernoulli(listOf(Mammal("fox"), Mammal("elephant")))
* val animalsGenerator = Generator.frequency(
* 0.2 to birdsGenerator,
* 0.8 to mammalsGenerator
* )
* val animals = animalsGenerator.generate(Random()).getOrThrow()
*
* The above will generate a random list of animals.
*/
class Generator<out A>(val generate: (Random) -> ErrorOr<A>) {
// Functor
fun <B> map(function: (A) -> B): Generator<B> =
Generator { generate(it).map(function) }
// Applicative
fun <B> product(other: Generator<(A) -> B>) =
Generator { generate(it).combine(other.generate(it)) { a, f -> f(a) } }
fun <B, R> combine(other1: Generator<B>, function: (A, B) -> R) =
product<R>(other1.product(pure({ b -> { a -> function(a, b) } })))
fun <B, C, R> combine(other1: Generator<B>, other2: Generator<C>, function: (A, B, C) -> R) =
product<R>(other1.product(other2.product(pure({ c -> { b -> { a -> function(a, b, c) } } }))))
fun <B, C, D, R> combine(other1: Generator<B>, other2: Generator<C>, other3: Generator<D>, function: (A, B, C, D) -> R) =
product<R>(other1.product(other2.product(other3.product(pure({ d -> { c -> { b -> { a -> function(a, b, c, d) } } } })))))
fun <B, C, D, E, R> combine(other1: Generator<B>, other2: Generator<C>, other3: Generator<D>, other4: Generator<E>, function: (A, B, C, D, E) -> R) =
product<R>(other1.product(other2.product(other3.product(other4.product(pure({ e -> { d -> { c -> { b -> { a -> function(a, b, c, d, e) } } } } }))))))
// Monad
fun <B> bind(function: (A) -> Generator<B>) =
Generator { generate(it).bind { a -> function(a).generate(it) } }
companion object {
fun <A> pure(value: A) = Generator { ErrorOr(value) }
fun <A> impure(valueClosure: () -> A) = Generator { ErrorOr(valueClosure()) }
fun <A> fail(error: Exception) = Generator<A> { ErrorOr.of(error) }
// Alternative
fun <A> choice(generators: List<Generator<A>>) = intRange(0, generators.size - 1).bind { generators[it] }
fun <A> success(generate: (Random) -> A) = Generator { ErrorOr(generate(it)) }
fun <A> frequency(vararg generators: Pair<Double, Generator<A>>): Generator<A> {
val ranges = mutableListOf<Pair<Double, Double>>()
var current = 0.0
generators.forEach {
val next = current + it.first
ranges.add(Pair(current, next))
current = next
}
return doubleRange(0.0, current).bind { value ->
generators[ranges.binarySearch { range ->
if (value < range.first) {
1
} else if (value < range.second) {
0
} else {
-1
}
}].second
}
}
fun <A> sequence(generators: List<Generator<A>>) = Generator<List<A>> {
val result = mutableListOf<A>()
for (generator in generators) {
val element = generator.generate(it)
val v = element.value
if (v != null) {
result.add(v)
} else {
return@Generator ErrorOr.of(element.error!!)
}
}
ErrorOr(result)
}
}
}
fun <A> Generator.Companion.oneOf(list: List<A>) = intRange(0, list.size - 1).map { list[it] }
fun <A> Generator<A>.generateOrFail(random: Random, numberOfTries: Int = 1): A {
var error: Throwable? = null
for (i in 0 .. numberOfTries - 1) {
val result = generate(random)
val v = result.value
if (v != null) {
return v
} else {
error = result.error
}
}
if (error == null) {
throw IllegalArgumentException("numberOfTries cannot be <= 0")
} else {
throw Exception("Failed to generate", error)
}
}
fun Generator.Companion.int() = Generator.success { it.nextInt() }
fun Generator.Companion.intRange(from: Int, to: Int): Generator<Int> = Generator.success {
(from + Math.abs(it.nextInt()) % (to - from + 1)).toInt()
}
fun Generator.Companion.double() = Generator.success { it.nextDouble() }
fun Generator.Companion.doubleRange(from: Double, to: Double): Generator<Double> = Generator.success {
from + it.nextDouble() % (to - from)
}
fun <A> Generator.Companion.replicate(number: Int, generator: Generator<A>): Generator<List<A>> {
val generators = mutableListOf<Generator<A>>()
for (i in 1 .. number) {
generators.add(generator)
}
return sequence(generators)
}
fun <A> Generator.Companion.replicatePoisson(meanSize: Double, generator: Generator<A>) = Generator<List<A>> {
val chance = (meanSize - 1) / meanSize
val result = mutableListOf<A>()
var finish = false
while (!finish) {
val errorOr = Generator.doubleRange(0.0, 1.0).generate(it).bind { value ->
if (value < chance) {
generator.generate(it).map { result.add(it) }
} else {
finish = true
ErrorOr(Unit)
}
}
val e = errorOr.error
if (e != null) {
return@Generator ErrorOr.of(e)
}
}
ErrorOr(result)
}
fun <A> Generator.Companion.pickOne(list: List<A>) = Generator.intRange(0, list.size - 1).map { list[it] }
fun <A> Generator.Companion.sampleBernoulli(maxRatio: Double = 1.0, vararg collection: A) =
sampleBernoulli(listOf(collection), maxRatio)
fun <A> Generator.Companion.sampleBernoulli(collection: Collection<A>, maxRatio: Double = 1.0): Generator<List<A>> =
intRange(0, (maxRatio * collection.size).toInt()).bind { howMany ->
replicate(collection.size, Generator.doubleRange(0.0, 1.0)).map { chances ->
val result = mutableListOf<A>()
collection.forEachIndexed { index, element ->
if (chances[index] < howMany.toDouble() / collection.size.toDouble()) {
result.add(element)
}
}
result
}
}

View File

@ -1,53 +0,0 @@
package com.r3corda.client.model
import com.r3corda.client.fxutils.foldToObservableList
import com.r3corda.client.fxutils.recordInSequence
import com.r3corda.contracts.asset.Cash
import com.r3corda.core.contracts.ContractState
import com.r3corda.core.contracts.StateAndRef
import com.r3corda.core.contracts.StateRef
import com.r3corda.core.node.services.Vault
import javafx.collections.ObservableList
import kotlinx.support.jdk8.collections.removeIf
import rx.Observable
data class Diff<out T : ContractState>(
val added: Collection<StateAndRef<T>>,
val removed: Collection<StateRef>
)
/**
* This model exposes the list of owned contract states.
*/
class ContractStateModel {
private val vaultUpdates: Observable<Vault.Update> by observable(NodeMonitorModel::vaultUpdates)
val contractStatesDiff: Observable<Diff<ContractState>> = vaultUpdates.map {
Diff(it.produced, it.consumed)
}
val cashStatesDiff: Observable<Diff<Cash.State>> = contractStatesDiff.map {
// We can't filter removed hashes here as we don't have type info
Diff(it.added.filterCashStateAndRefs(), it.removed)
}
val cashStates: ObservableList<StateAndRef<Cash.State>> =
cashStatesDiff.foldToObservableList(Unit) { statesDiff, _accumulator, observableList ->
observableList.removeIf { it.ref in statesDiff.removed }
observableList.addAll(statesDiff.added)
}
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>
} else {
null
}
}.filterNotNull()
}
}
}

View File

@ -1,136 +0,0 @@
package com.r3corda.client.model
import com.r3corda.client.fxutils.*
import com.r3corda.core.contracts.ContractState
import com.r3corda.core.contracts.StateAndRef
import com.r3corda.core.contracts.StateRef
import com.r3corda.client.fxutils.recordInSequence
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.node.services.StateMachineTransactionMapping
import com.r3corda.core.protocols.StateMachineRunId
import com.r3corda.core.transactions.SignedTransaction
import com.r3corda.node.services.messaging.StateMachineUpdate
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue
import javafx.collections.ObservableList
import javafx.collections.ObservableMap
import org.fxmisc.easybind.EasyBind
import org.slf4j.LoggerFactory
import rx.Observable
data class GatheredTransactionData(
val transaction: PartiallyResolvedTransaction,
val stateMachines: ObservableList<out StateMachineData>
)
/**
* [PartiallyResolvedTransaction] holds a [SignedTransaction] that has zero or more inputs resolved. The intent is
* to prepare clients for cases where an input can only be resolved in the future/cannot be resolved at all (for example
* because of permissioning)
*/
data class PartiallyResolvedTransaction(
val transaction: SignedTransaction,
val inputs: List<ObservableValue<InputResolution>>
) {
val id = transaction.id
sealed class InputResolution(val stateRef: StateRef) {
class Unresolved(stateRef: StateRef) : InputResolution(stateRef)
class Resolved(val stateAndRef: StateAndRef<ContractState>) : InputResolution(stateAndRef.ref)
}
companion object {
fun fromSignedTransaction(
transaction: SignedTransaction,
transactions: ObservableMap<SecureHash, SignedTransaction>
) = PartiallyResolvedTransaction(
transaction = transaction,
inputs = transaction.tx.inputs.map { stateRef ->
EasyBind.map(transactions.getObservableValue(stateRef.txhash)) {
if (it == null) {
InputResolution.Unresolved(stateRef)
} else {
InputResolution.Resolved(it.tx.outRef(stateRef.index))
}
}
}
)
}
}
sealed class TransactionCreateStatus(val message: String?) {
class Started(message: String?) : TransactionCreateStatus(message)
class Failed(message: String?) : TransactionCreateStatus(message)
override fun toString(): String = message ?: javaClass.simpleName
}
data class ProtocolStatus(
val status: String
)
sealed class StateMachineStatus(val stateMachineName: String) {
class Added(stateMachineName: String): StateMachineStatus(stateMachineName)
class Removed(stateMachineName: String): StateMachineStatus(stateMachineName)
override fun toString(): String = "${javaClass.simpleName}($stateMachineName)"
}
data class StateMachineData(
val id: StateMachineRunId,
val protocolStatus: ObservableValue<ProtocolStatus?>,
val stateMachineStatus: ObservableValue<StateMachineStatus>
)
/**
* This model provides an observable list of transactions and what state machines/protocols recorded them
*/
class GatheredTransactionDataModel {
private val transactions: Observable<SignedTransaction> by observable(NodeMonitorModel::transactions)
private val stateMachineUpdates: Observable<StateMachineUpdate> by observable(NodeMonitorModel::stateMachineUpdates)
private val progressTracking: Observable<ProgressTrackingEvent> by observable(NodeMonitorModel::progressTracking)
private val stateMachineTransactionMapping: Observable<StateMachineTransactionMapping> by observable(NodeMonitorModel::stateMachineTransactionMapping)
val collectedTransactions = transactions.recordInSequence()
val transactionMap = collectedTransactions.associateBy(SignedTransaction::id)
val progressEvents = progressTracking.recordAsAssociation(ProgressTrackingEvent::stateMachineId)
val stateMachineStatus: ObservableMap<StateMachineRunId, out ObservableValue<StateMachineStatus>> =
stateMachineUpdates.foldToObservableMap(Unit) { update, _unit, map: ObservableMap<StateMachineRunId, SimpleObjectProperty<StateMachineStatus>> ->
when (update) {
is StateMachineUpdate.Added -> {
val added: SimpleObjectProperty<StateMachineStatus> =
SimpleObjectProperty(StateMachineStatus.Added(update.stateMachineInfo.protocolLogicClassName))
map[update.id] = added
}
is StateMachineUpdate.Removed -> {
val added = map[update.id]
added ?: throw Exception("State machine removed with unknown id ${update.id}")
added.set(StateMachineStatus.Removed(added.value.stateMachineName))
}
}
}
val stateMachineDataList: ObservableList<StateMachineData> =
LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
StateMachineData(id, progress.map { it?.let { ProtocolStatus(it.message) } }, status)
}.getObservableValues()
val stateMachineDataMap = stateMachineDataList.associateBy(StateMachineData::id)
val smTxMappingList = stateMachineTransactionMapping.recordInSequence()
val partiallyResolvedTransactions = collectedTransactions.map {
PartiallyResolvedTransaction.fromSignedTransaction(it, transactionMap)
}
/**
* We JOIN the transaction list with state machines
*/
val gatheredTransactionDataList: ObservableList<out GatheredTransactionData> =
partiallyResolvedTransactions.leftOuterJoin(
smTxMappingList,
PartiallyResolvedTransaction::id,
StateMachineTransactionMapping::transactionId
) { transaction, mappings ->
GatheredTransactionData(
transaction,
mappings.map { mapping ->
stateMachineDataMap.getObservableValue(mapping.stateMachineRunId)
}.flatten().filterNotNull()
)
}
}

View File

@ -1,24 +0,0 @@
package com.r3corda.client.model
import com.r3corda.client.fxutils.foldToObservableList
import com.r3corda.core.node.NodeInfo
import com.r3corda.core.node.services.NetworkMapCache
import javafx.collections.ObservableList
import kotlinx.support.jdk8.collections.removeIf
import rx.Observable
class NetworkIdentityModel {
private val networkIdentityObservable: Observable<NetworkMapCache.MapChange> by observable(NodeMonitorModel::networkMap)
val networkIdentities: ObservableList<NodeInfo> =
networkIdentityObservable.foldToObservableList(Unit) { update, _accumulator, observableList ->
observableList.removeIf {
when (update.type) {
NetworkMapCache.MapChangeType.Removed -> it == update.node
NetworkMapCache.MapChangeType.Modified -> it == update.prevNodeInfo
else -> false
}
}
observableList.addAll(update.node)
}
}

View File

@ -1,107 +0,0 @@
package com.r3corda.client.model
import com.r3corda.client.CordaRPCClient
import com.r3corda.core.contracts.ClientToServiceCommand
import com.r3corda.core.node.NodeInfo
import com.r3corda.core.node.services.NetworkMapCache
import com.r3corda.core.node.services.StateMachineTransactionMapping
import com.r3corda.core.node.services.Vault
import com.r3corda.core.protocols.StateMachineRunId
import com.r3corda.core.transactions.SignedTransaction
import com.r3corda.node.services.config.NodeSSLConfiguration
import com.r3corda.node.services.messaging.ArtemisMessagingComponent.Companion.toHostAndPort
import com.r3corda.node.services.messaging.CordaRPCOps
import com.r3corda.node.services.messaging.StateMachineInfo
import com.r3corda.node.services.messaging.StateMachineUpdate
import javafx.beans.property.SimpleObjectProperty
import rx.Observable
import rx.subjects.PublishSubject
data class ProgressTrackingEvent(val stateMachineId: StateMachineRunId, val message: String) {
companion object {
fun createStreamFromStateMachineInfo(stateMachine: StateMachineInfo): Observable<ProgressTrackingEvent>? {
return stateMachine.progressTrackerStepAndUpdates?.let { pair ->
val (current, future) = pair
future.map { ProgressTrackingEvent(stateMachine.id, it) }.startWith(ProgressTrackingEvent(stateMachine.id, current))
}
}
}
}
/**
* This model exposes raw event streams to and from the node.
*/
class NodeMonitorModel {
private val stateMachineUpdatesSubject = PublishSubject.create<StateMachineUpdate>()
private val vaultUpdatesSubject = PublishSubject.create<Vault.Update>()
private val transactionsSubject = PublishSubject.create<SignedTransaction>()
private val stateMachineTransactionMappingSubject = PublishSubject.create<StateMachineTransactionMapping>()
private val progressTrackingSubject = PublishSubject.create<ProgressTrackingEvent>()
private val networkMapSubject = PublishSubject.create<NetworkMapCache.MapChange>()
val stateMachineUpdates: Observable<StateMachineUpdate> = stateMachineUpdatesSubject
val vaultUpdates: Observable<Vault.Update> = vaultUpdatesSubject
val transactions: Observable<SignedTransaction> = transactionsSubject
val stateMachineTransactionMapping: Observable<StateMachineTransactionMapping> = stateMachineTransactionMappingSubject
val progressTracking: Observable<ProgressTrackingEvent> = progressTrackingSubject
val networkMap: Observable<NetworkMapCache.MapChange> = networkMapSubject
private val clientToServiceSource = PublishSubject.create<ClientToServiceCommand>()
val clientToService: PublishSubject<ClientToServiceCommand> = clientToServiceSource
val proxyObservable = SimpleObjectProperty<CordaRPCOps?>()
/**
* Register for updates to/from a given vault.
* TODO provide an unsubscribe mechanism
*/
fun register(vaultMonitorNodeInfo: NodeInfo, sslConfig: NodeSSLConfiguration, username: String, password: String) {
val client = CordaRPCClient(toHostAndPort(vaultMonitorNodeInfo.address), sslConfig)
client.start(username, password)
val proxy = client.proxy()
val (stateMachines, stateMachineUpdates) = proxy.stateMachinesAndUpdates()
// Extract the protocol tracking stream
// TODO is there a nicer way of doing this? Stream of streams in general results in code like this...
val currentProgressTrackerUpdates = stateMachines.mapNotNull { stateMachine ->
ProgressTrackingEvent.createStreamFromStateMachineInfo(stateMachine)
}
val futureProgressTrackerUpdates = stateMachineUpdatesSubject.map { stateMachineUpdate ->
if (stateMachineUpdate is StateMachineUpdate.Added) {
ProgressTrackingEvent.createStreamFromStateMachineInfo(stateMachineUpdate.stateMachineInfo) ?: Observable.empty<ProgressTrackingEvent>()
} else {
Observable.empty<ProgressTrackingEvent>()
}
}
futureProgressTrackerUpdates.startWith(currentProgressTrackerUpdates).flatMap { it }.subscribe(progressTrackingSubject)
// Now the state machines
val currentStateMachines = stateMachines.map { StateMachineUpdate.Added(it) }
stateMachineUpdates.startWith(currentStateMachines).subscribe(stateMachineUpdatesSubject)
// Vault updates
val (vault, vaultUpdates) = proxy.vaultAndUpdates()
val initialVaultUpdate = Vault.Update(setOf(), vault.toSet())
vaultUpdates.startWith(initialVaultUpdate).subscribe(vaultUpdatesSubject)
// Transactions
val (transactions, newTransactions) = proxy.verifiedTransactions()
newTransactions.startWith(transactions).subscribe(transactionsSubject)
// SM -> TX mapping
val (smTxMappings, futureSmTxMappings) = proxy.stateMachineRecordedTransactionMapping()
futureSmTxMappings.startWith(smTxMappings).subscribe(stateMachineTransactionMappingSubject)
// Parties on network
val (parties, futurePartyUpdate) = proxy.networkMapUpdates()
futurePartyUpdate.startWith(parties.map { NetworkMapCache.MapChange(it, null, NetworkMapCache.MapChangeType.Added) }).subscribe(networkMapSubject)
// Client -> Service
clientToServiceSource.subscribe {
proxy.executeCommand(it)
}
proxyObservable.set(proxy)
}
}

View File

@ -1,12 +1,15 @@
package com.r3corda.client
package net.corda.client
import com.google.common.net.HostAndPort
import com.r3corda.client.impl.CordaRPCClientImpl
import com.r3corda.core.ThreadBox
import com.r3corda.node.services.config.NodeSSLConfiguration
import com.r3corda.node.services.messaging.ArtemisMessagingComponent
import com.r3corda.node.services.messaging.CordaRPCOps
import com.r3corda.node.services.messaging.RPCException
import net.corda.client.impl.CordaRPCClientImpl
import net.corda.core.ThreadBox
import net.corda.node.services.config.NodeSSLConfiguration
import net.corda.node.services.messaging.ArtemisMessagingComponent
import net.corda.node.services.messaging.ArtemisMessagingComponent.Companion.CLIENTS_PREFIX
import net.corda.node.services.messaging.CordaRPCOps
import net.corda.node.services.messaging.RPCException
import net.corda.node.services.messaging.rpcLog
import org.apache.activemq.artemis.api.core.ActiveMQException
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
import org.apache.activemq.artemis.api.core.client.ClientSession
@ -25,10 +28,6 @@ import kotlin.concurrent.thread
*/
@ThreadSafe
class CordaRPCClient(val host: HostAndPort, override val config: NodeSSLConfiguration) : Closeable, ArtemisMessagingComponent() {
companion object {
private val rpcLog = LoggerFactory.getLogger("com.r3corda.rpc")
}
// TODO: Certificate handling for clients needs more work.
private inner class State {
var running = false
@ -36,37 +35,28 @@ class CordaRPCClient(val host: HostAndPort, override val config: NodeSSLConfigur
lateinit var session: ClientSession
lateinit var clientImpl: CordaRPCClientImpl
}
private val state = ThreadBox(State())
/**
* An ID that we used to identify this connection on the server side: kind of like a local port number but
* it persists for the lifetime of this process and survives short TCP connection interruptions. Is -1
* until [start] is called.
*/
var myID: Int = -1
private set
private val myAddressPrefix: String get() = "${ArtemisMessagingComponent.CLIENTS_PREFIX}$myID"
/** Opens the connection to the server and registers a JVM shutdown hook to cleanly disconnect. */
@Throws(ActiveMQNotConnectedException::class)
@Throws(ActiveMQException::class)
fun start(username: String, password: String) {
state.locked {
check(!running)
checkStorePasswords() // Check the password.
checkStorePasswords()
val serverLocator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport(ConnectionDirection.OUTBOUND, host.hostText, host.port))
serverLocator.threadPoolMaxSize = 1
// TODO: Configure session reconnection, confirmation window sizes and other Artemis features.
// This will allow reconnection in case of server restart/network outages/IP address changes, etc.
// See http://activemq.apache.org/artemis/docs/1.5.0/client-reconnection.html
sessionFactory = serverLocator.createSessionFactory()
// We use our initial connection ID as the queue namespace.
myID = sessionFactory.connection.id as Int and 0x000000FFFFFF
session = sessionFactory.createSession(username, password, false, true, true, serverLocator.isPreAcknowledge, serverLocator.ackBatchSize)
session.start()
clientImpl = CordaRPCClientImpl(session, state.lock, myAddressPrefix)
clientImpl = CordaRPCClientImpl(session, state.lock, username)
running = true
// We will use the ID in strings so strip the sign bit.
}
Runtime.getRuntime().addShutdownHook(thread(start = false) {
Runtime.getRuntime().addShutdownHook(Thread {
close()
})
}
@ -91,9 +81,10 @@ class CordaRPCClient(val host: HostAndPort, override val config: NodeSSLConfigur
* calls from many threads at once you should cache one proxy per thread and reuse them. This function itself is
* thread safe though so requires no extra synchronisation.
*
* RPC sends and receives are logged on the com.r3corda.rpc logger.
* RPC sends and receives are logged on the net.corda.rpc logger.
*
* By default there are no timeouts on calls. RPCs can survive temporary losses or changes in connectivity,
* By default there are no timeouts on calls. This is deliberate, RPCs without timeouts can survive restarts,
* maintenance downtime and moves of the server. RPCs can survive temporary losses or changes in client connectivity,
* like switching between wifi networks. You can specify a timeout on the level of a proxy. If a call times
* out it will throw [RPCException.Deadline].
*

View File

@ -1,4 +1,4 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import javafx.collections.FXCollections
import javafx.collections.ListChangeListener

View File

@ -1,11 +1,11 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import com.r3corda.client.model.ExchangeRate
import com.r3corda.core.contracts.Amount
import javafx.beans.binding.Bindings
import javafx.beans.value.ObservableValue
import javafx.collections.ObservableList
import kotlinx.support.jdk8.collections.stream
import net.corda.client.model.ExchangeRate
import net.corda.core.contracts.Amount
import org.fxmisc.easybind.EasyBind
import java.util.*
import java.util.stream.Collectors
@ -44,7 +44,7 @@ object AmountBindings {
EasyBind.map(
Bindings.createLongBinding({
amounts.stream().collect(Collectors.summingLong { exchange(it) })
} , arrayOf(amounts))
}, arrayOf(amounts))
) { Amount(it.toLong(), currencyValue) }
}
}

View File

@ -1,4 +1,4 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import javafx.collections.ListChangeListener
import javafx.collections.ObservableList

View File

@ -1,4 +1,4 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import javafx.beans.Observable
import javafx.beans.value.ObservableValue
@ -22,7 +22,7 @@ import javafx.collections.ObservableListBase
*/
class ChosenList<E>(
private val chosenListObservable: ObservableValue<out ObservableList<out E>>
): ObservableListBase<E>() {
) : ObservableListBase<E>() {
private var currentList = chosenListObservable.value

View File

@ -1,4 +1,4 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import co.paralleluniverse.common.util.VisibleForTesting
import javafx.collections.ListChangeListener
@ -41,6 +41,7 @@ class ConcatenatedList<A>(sourceList: ObservableList<ObservableList<A>>) : Trans
internal val indexMap = HashMap<WrappedObservableList<out A>, Pair<Int, ListChangeListener<A>>>()
@VisibleForTesting
internal val nestedIndexOffsets = ArrayList<Int>(sourceList.size)
init {
var offset = 0
sourceList.forEachIndexed { index, observableList ->
@ -78,7 +79,7 @@ class ConcatenatedList<A>(sourceList: ObservableList<ObservableList<A>>) : Trans
permutation[i] = i
}
// Then the permuted ones.
for (i in firstTouched .. startingOffset + change.to - 1) {
for (i in firstTouched..startingOffset + change.to - 1) {
permutation[startingOffset + i] = change.getPermutation(i)
}
nextPermutation(firstTouched, startingOffset + change.to, permutation)
@ -144,7 +145,7 @@ class ConcatenatedList<A>(sourceList: ObservableList<ObservableList<A>>) : Trans
val newSubNestedIndexOffsets = IntArray(change.to - change.from)
val firstTouched = if (change.from == 0) 0 else nestedIndexOffsets[change.from - 1]
var currentOffset = firstTouched
for (i in 0 .. change.to - change.from - 1) {
for (i in 0..change.to - change.from - 1) {
currentOffset += source[change.from + i].size
newSubNestedIndexOffsets[i] = currentOffset
}
@ -152,24 +153,24 @@ class ConcatenatedList<A>(sourceList: ObservableList<ObservableList<A>>) : Trans
val concatenatedPermutation = IntArray(newSubNestedIndexOffsets.last())
// Set the non-permuted part
var offset = 0
for (i in 0 .. change.from - 1) {
for (i in 0..change.from - 1) {
val nestedList = source[i]
for (j in offset .. offset + nestedList.size - 1) {
for (j in offset..offset + nestedList.size - 1) {
concatenatedPermutation[j] = j
}
offset += nestedList.size
}
// Now the permuted part
for (i in 0 .. newSubNestedIndexOffsets.size - 1) {
for (i in 0..newSubNestedIndexOffsets.size - 1) {
val startingOffset = startingOffsetOf(change.from + i)
val permutedListIndex = change.getPermutation(change.from + i)
val permutedOffset = (if (permutedListIndex == 0) 0 else newSubNestedIndexOffsets[permutedListIndex - 1])
for (j in 0 .. source[permutedListIndex].size - 1) {
for (j in 0..source[permutedListIndex].size - 1) {
concatenatedPermutation[startingOffset + j] = permutedOffset + j
}
}
// Record permuted offsets
for (i in 0 .. newSubNestedIndexOffsets.size - 1) {
for (i in 0..newSubNestedIndexOffsets.size - 1) {
nestedIndexOffsets[change.from + i] = newSubNestedIndexOffsets[i]
}
nextPermutation(firstTouched, newSubNestedIndexOffsets.last(), concatenatedPermutation)
@ -229,6 +230,7 @@ class ConcatenatedList<A>(sourceList: ObservableList<ObservableList<A>>) : Trans
// Tracks the first position where the *nested* offset is invalid
private var firstInvalidatedPosition = sourceList.size
private fun invalidateOffsets(index: Int) {
firstInvalidatedPosition = Math.min(firstInvalidatedPosition, index)
}
@ -237,7 +239,7 @@ class ConcatenatedList<A>(sourceList: ObservableList<ObservableList<A>>) : Trans
if (firstInvalidatedPosition < source.size) {
val firstInvalid = firstInvalidatedPosition
var offset = if (firstInvalid == 0) 0 else nestedIndexOffsets[firstInvalid - 1]
for (i in firstInvalid .. source.size - 1) {
for (i in firstInvalid..source.size - 1) {
offset += source[i].size
if (i < nestedIndexOffsets.size) {
nestedIndexOffsets[i] = offset

View File

@ -1,12 +1,10 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import javafx.beans.InvalidationListener
import javafx.beans.value.ChangeListener
import javafx.beans.value.ObservableValue
import javafx.collections.ListChangeListener
import javafx.collections.ObservableList
import javafx.collections.transformation.TransformationList
import org.eclipse.jetty.server.Authentication
import java.util.*
import kotlin.test.assertEquals
@ -30,7 +28,9 @@ class FlattenedList<A>(val sourceList: ObservableList<out ObservableValue<out A>
class WrappedObservableValue<A>(
val observableValue: ObservableValue<A>
)
val indexMap = HashMap<WrappedObservableValue<out A>, Pair<Int, ChangeListener<A>>>()
init {
sourceList.forEachIndexed { index, observableValue ->
val wrappedObservableValue = WrappedObservableValue(observableValue)

View File

@ -1,8 +1,9 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue
import javafx.collections.*
import javafx.collections.MapChangeListener
import javafx.collections.ObservableMap
/**
* [LeftOuterJoinedMap] implements a special case of a left outer join where we're matching on primary keys of both

View File

@ -1,4 +1,4 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import javafx.collections.FXCollections
import javafx.collections.MapChangeListener

View File

@ -1,4 +1,4 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import javafx.collections.ListChangeListener
import javafx.collections.ObservableList

View File

@ -1,4 +1,4 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import javafx.application.Platform
import javafx.beans.property.SimpleObjectProperty

View File

@ -1,6 +1,7 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import javafx.beans.binding.Bindings
import javafx.beans.binding.BooleanBinding
import javafx.beans.property.ReadOnlyObjectWrapper
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue
@ -10,9 +11,7 @@ import javafx.collections.ObservableList
import javafx.collections.ObservableMap
import javafx.collections.transformation.FilteredList
import org.fxmisc.easybind.EasyBind
import org.slf4j.LoggerFactory
import java.util.function.Predicate
import kotlin.concurrent.thread
/**
* Here follows utility extension functions that help reduce the visual load when developing RX code. Each function should
@ -49,18 +48,22 @@ fun <A, B> ObservableList<out A>.map(cached: Boolean = true, function: (A) -> B)
* val aliceHeightPlus2 = ::sumHeight.lift(aliceHeight, 2L.lift())
*/
fun <A> A.lift(): ObservableValue<A> = ReadOnlyObjectWrapper(this)
fun <A, R> ((A) -> R).lift(
arg0: ObservableValue<A>
): ObservableValue<R> = EasyBind.map(arg0, this)
fun <A, B, R> ((A, B) -> R).lift(
arg0: ObservableValue<A>,
arg1: ObservableValue<B>
): ObservableValue<R> = EasyBind.combine(arg0, arg1, this)
fun <A, B, C, R> ((A, B, C) -> R).lift(
arg0: ObservableValue<A>,
arg1: ObservableValue<B>,
arg2: ObservableValue<C>
): ObservableValue<R> = EasyBind.combine(arg0, arg1, arg2, this)
fun <A, B, C, D, R> ((A, B, C, D) -> R).lift(
arg0: ObservableValue<A>,
arg1: ObservableValue<B>,
@ -75,6 +78,7 @@ fun <A, B, C, D, R> ((A, B, C, D) -> R).lift(
*/
fun <A, B> ObservableValue<out A>.bind(function: (A) -> ObservableValue<B>): ObservableValue<B> =
EasyBind.monadic(this).flatMap(function)
/**
* A variant of [bind] that has out variance on the output type. This is sometimes useful when kotlin is too eager to
* propagate variance constraints and type inference fails.
@ -265,9 +269,11 @@ fun <A : Any, B : Any, K : Any> ObservableList<A>.leftOuterJoin(
fun <A> ObservableList<A>.getValueAt(index: Int): ObservableValue<A?> {
return Bindings.valueAt(this, index)
}
fun <A> ObservableList<A>.first(): ObservableValue<A?> {
return getValueAt(0)
}
fun <A> ObservableList<A>.last(): ObservableValue<A?> {
return Bindings.createObjectBinding({
if (size > 0) {
@ -277,3 +283,27 @@ fun <A> ObservableList<A>.last(): ObservableValue<A?> {
}
}, arrayOf(this))
}
fun <T : Any> ObservableList<T>.unique(): ObservableList<T> {
return AggregatedList(this, { it }, { key, _list -> key })
}
fun ObservableValue<*>.isNotNull(): BooleanBinding {
return Bindings.createBooleanBinding({ this.value != null }, arrayOf(this))
}
/**
* Return first element of the observable list as observable value.
* Return provided default value if the list is empty.
*/
fun <A> ObservableList<A>.firstOrDefault(default: ObservableValue<A?>, predicate: (A) -> Boolean): ObservableValue<A?> {
return Bindings.createObjectBinding({ this.firstOrNull(predicate) ?: default.value }, arrayOf(this, default))
}
/**
* Return first element of the observable list as observable value.
* Return ObservableValue(null) if the list is empty.
*/
fun <A> ObservableList<A>.firstOrNullObservable(predicate: (A) -> Boolean): ObservableValue<A?> {
return Bindings.createObjectBinding({ this.firstOrNull(predicate) }, arrayOf(this))
}

View File

@ -1,4 +1,4 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import com.sun.javafx.collections.MapListenerHelper
import javafx.beans.InvalidationListener

View File

@ -1,4 +1,4 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import javafx.collections.ListChangeListener
import javafx.collections.ObservableList
@ -25,7 +25,7 @@ class ReplayedList<A>(sourceList: ObservableList<A>) : TransformationList<A, A>(
val permutation = IntArray(to, { c.getPermutation(it) })
val permutedSubList = ArrayList<A?>(to - from)
permutedSubList.addAll(Collections.nCopies(to - from, null))
for (i in 0 .. (to - from - 1)) {
for (i in 0..(to - from - 1)) {
permutedSubList[permutation[from + i]] = replayedList[from + i]
}
permutedSubList.forEachIndexed { i, element ->
@ -33,14 +33,14 @@ class ReplayedList<A>(sourceList: ObservableList<A>) : TransformationList<A, A>(
}
nextPermutation(from, to, permutation)
} else if (c.wasUpdated()) {
for (i in c.from .. c.to - 1) {
for (i in c.from..c.to - 1) {
replayedList[i] = c.list[i]
nextUpdate(i)
}
} else {
if (c.wasRemoved()) {
val removePosition = c.from
for (i in 0 .. c.removedSize - 1) {
for (i in 0..c.removedSize - 1) {
replayedList.removeAt(removePosition)
}
nextRemove(c.from, c.removed)
@ -48,7 +48,7 @@ class ReplayedList<A>(sourceList: ObservableList<A>) : TransformationList<A, A>(
if (c.wasAdded()) {
val addStart = c.from
val addEnd = c.to
for (i in addStart .. addEnd - 1) {
for (i in addStart..addEnd - 1) {
replayedList.add(i, c.list[i])
}
nextAdd(addStart, addEnd)

View File

@ -0,0 +1,336 @@
package net.corda.client.impl
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.KryoException
import com.esotericsoftware.kryo.Serializer
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import com.google.common.cache.CacheBuilder
import net.corda.client.CordaRPCClient
import net.corda.core.ErrorOr
import net.corda.core.bufferUntilSubscribed
import net.corda.core.random63BitValue
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.utilities.debug
import net.corda.node.services.messaging.*
import org.apache.activemq.artemis.api.core.ActiveMQObjectClosedException
import org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.client.ClientConsumer
import org.apache.activemq.artemis.api.core.client.ClientMessage
import org.apache.activemq.artemis.api.core.client.ClientProducer
import org.apache.activemq.artemis.api.core.client.ClientSession
import rx.Observable
import rx.subjects.PublishSubject
import java.io.Closeable
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
import java.time.Duration
import java.util.*
import java.util.concurrent.locks.ReentrantLock
import javax.annotation.concurrent.GuardedBy
import javax.annotation.concurrent.ThreadSafe
import kotlin.concurrent.withLock
import kotlin.reflect.jvm.javaMethod
/**
* Core RPC engine implementation, to learn how to use RPC you should be looking at [CordaRPCClient].
*
* # Design notes
*
* The way RPCs are handled is fairly standard except for the handling of observables. When an RPC might return
* an [Observable] it is specially tagged. This causes the client to create a new transient queue for the
* receiving of observables and their observations with a random ID in the name. This ID is sent to the server in
* a message header. All observations are sent via this single queue.
*
* The reason for doing it this way and not the more obvious approach of one-queue-per-observable is that we want
* the queues to be *transient*, meaning their lifetime in the broker is tied to the session that created them.
* A server side observable and its associated queue is not a cost-free thing, let alone the memory and resources
* needed to actually generate the observations themselves, therefore we want to ensure these cannot leak. A
* transient queue will be deleted automatically if the client session terminates, which by default happens on
* disconnect but can also be configured to happen after a short delay (this allows clients to e.g. switch IP
* address). On the server the deletion of the observations queue triggers unsubscription from the associated
* observables, which in turn may then be garbage collected.
*
* Creating a transient queue requires a roundtrip to the broker and thus doing an RPC that could return
* observables takes two server roundtrips instead of one. That's why we require RPCs to be marked with
* [RPCReturnsObservables] as needing this special treatment instead of always doing it.
*
* If the Artemis/JMS APIs allowed us to create transient queues assigned to someone else then we could
* potentially use a different design in which the node creates new transient queues (one per observable) on the
* fly. The client would then have to watch out for this and start consuming those queues as they were created.
*
* We use one queue per RPC because we don't know ahead of time how many observables the server might return and
* often the server doesn't know either, which pushes towards a single queue design, but at the same time the
* processing of observations returned by an RPC might be striped across multiple threads and we'd like
* backpressure management to not be scoped per client process but with more granularity. So we end up with
* a compromise where the unit of backpressure management is the response to a single RPC.
*
* TODO: Backpressure isn't propagated all the way through the MQ broker at the moment.
*/
class CordaRPCClientImpl(private val session: ClientSession,
private val sessionLock: ReentrantLock,
private val username: String) {
companion object {
private val closeableCloseMethod = Closeable::close.javaMethod
private val autocloseableCloseMethod = AutoCloseable::close.javaMethod
}
/**
* Builds a proxy for the given type, which must descend from [RPCOps].
*
* @see CordaRPCClient.proxy for more information about how to use the proxies.
*/
fun <T : RPCOps> proxyFor(rpcInterface: Class<T>, timeout: Duration? = null, minVersion: Int = 0): T {
sessionLock.withLock {
if (producer == null)
producer = session.createProducer()
}
val proxyImpl = RPCProxyHandler(timeout)
@Suppress("UNCHECKED_CAST")
val proxy = Proxy.newProxyInstance(rpcInterface.classLoader, arrayOf(rpcInterface, Closeable::class.java), proxyImpl) as T
proxyImpl.serverProtocolVersion = proxy.protocolVersion
if (minVersion > proxyImpl.serverProtocolVersion)
throw RPCException("Requested minimum protocol version $minVersion is higher than the server's supported protocol version (${proxyImpl.serverProtocolVersion})")
return proxy
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//region RPC engine
//
// You can find docs on all this in the api doc for the proxyFor method, and in the docsite.
// Utility to quickly suck out the contents of an Artemis message. There's probably a more efficient way to
// do this.
private fun <T : Any> ClientMessage.deserialize(kryo: Kryo): T = ByteArray(bodySize).apply { bodyBuffer.readBytes(this) }.deserialize(kryo)
@GuardedBy("sessionLock")
private val addressToQueueObservables = CacheBuilder.newBuilder().build<String, QueuedObservable>()
private var producer: ClientProducer? = null
private inner class ObservableDeserializer(private val qName: String,
private val rpcName: String,
private val rpcLocation: Throwable) : Serializer<Observable<Any>>() {
override fun read(kryo: Kryo, input: Input, type: Class<Observable<Any>>): Observable<Any> {
val handle = input.readInt(true)
val ob = sessionLock.withLock {
addressToQueueObservables.getIfPresent(qName) ?: QueuedObservable(qName, rpcName, rpcLocation, this).apply {
addressToQueueObservables.put(qName, this)
}
}
val result = ob.getForHandle(handle)
rpcLog.debug { "Deserializing and connecting a new observable for $rpcName on $qName: $result" }
return result
}
override fun write(kryo: Kryo, output: Output, `object`: Observable<Any>) {
throw UnsupportedOperationException("not implemented")
}
}
/**
* The proxy class returned to the client is auto-generated on the fly by the java.lang.reflect Proxy
* infrastructure. The JDK Proxy class writes bytecode into memory for a class that implements the requested
* interfaces and then routes all method calls to the invoke method below in a conveniently reified form.
* We can then easily take the data about the method call and turn it into an RPC. This avoids the need
* for the compile-time code generation which is so common in RPC systems.
*/
@ThreadSafe
private inner class RPCProxyHandler(private val timeout: Duration?) : InvocationHandler, Closeable {
private val proxyId = random63BitValue()
private val consumer: ClientConsumer
var serverProtocolVersion = 0
init {
val proxyAddress = constructAddress(proxyId)
consumer = sessionLock.withLock {
session.createTemporaryQueue(proxyAddress, proxyAddress)
session.createConsumer(proxyAddress)
}
}
private fun constructAddress(addressId: Long) = "${ArtemisMessagingComponent.CLIENTS_PREFIX}$username.rpc.$addressId"
@Synchronized
override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {
if (isCloseInvocation(method)) {
close()
return null
}
if (method.name == "toString" && args == null)
return "Client RPC proxy"
if (consumer.isClosed)
throw RPCException("RPC Proxy is closed")
// All invoked methods on the proxy end up here.
val location = Throwable()
rpcLog.debug {
val argStr = args?.joinToString() ?: ""
"-> RPC -> ${method.name}($argStr): ${method.returnType}"
}
checkMethodVersion(method)
// sendRequest may return a reconfigured Kryo if the method returns observables.
val kryo: Kryo = sendRequest(args, location, method) ?: createRPCKryo()
val next: ErrorOr<*> = receiveResponse(kryo, method, timeout)
rpcLog.debug { "<- RPC <- ${method.name} = $next" }
return unwrapOrThrow(next)
}
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
private fun unwrapOrThrow(next: ErrorOr<*>): Any? {
val ex = next.error
if (ex != null) {
// Replace the stack trace because that's an implementation detail of the server that isn't so
// helpful to the user who wants to see where the error was on their side, and serialising stack
// frame objects is a bit annoying. We slice it here to avoid the invoke() machinery being exposed.
// The resulting exception looks like it was thrown from inside the called method.
(ex as java.lang.Throwable).stackTrace = java.lang.Throwable().stackTrace.let { it.sliceArray(1..it.size - 1) }
throw ex
} else {
return next.value
}
}
private fun receiveResponse(kryo: Kryo, method: Method, timeout: Duration?): ErrorOr<*> {
val artemisMessage: ClientMessage =
if (timeout == null)
consumer.receive() ?: throw ActiveMQObjectClosedException()
else
consumer.receive(timeout.toMillis()) ?: throw RPCException.DeadlineExceeded(method.name)
artemisMessage.acknowledge()
val next = artemisMessage.deserialize<ErrorOr<*>>(kryo)
return next
}
private fun sendRequest(args: Array<out Any>?, location: Throwable, method: Method): Kryo? {
// We could of course also check the return type of the method to see if it's Observable, but I'd
// rather haved the annotation be used consistently.
val returnsObservables = method.isAnnotationPresent(RPCReturnsObservables::class.java)
sessionLock.withLock {
val msg: ClientMessage = createMessage(method)
val kryo = if (returnsObservables) maybePrepareForObservables(location, method, msg) else null
val serializedArgs = try {
(args ?: emptyArray<Any?>()).serialize(createRPCKryo())
} catch (e: KryoException) {
throw RPCException("Could not serialize RPC arguments", e)
}
msg.writeBodyBufferBytes(serializedArgs.bytes)
producer!!.send(ArtemisMessagingComponent.RPC_REQUESTS_QUEUE, msg)
return kryo
}
}
private fun maybePrepareForObservables(location: Throwable, method: Method, msg: ClientMessage): Kryo {
// Create a temporary queue just for the emissions on any observables that are returned.
val observationsId = random63BitValue()
val observationsQueueName = constructAddress(observationsId)
session.createTemporaryQueue(observationsQueueName, observationsQueueName)
msg.putLongProperty(ClientRPCRequestMessage.OBSERVATIONS_TO, observationsId)
// And make sure that we deserialise observable handles so that they're linked to the right
// queue. Also record a bit of metadata for debugging purposes.
return createRPCKryo(observableSerializer = ObservableDeserializer(observationsQueueName, method.name, location))
}
private fun createMessage(method: Method): ClientMessage {
return session.createMessage(false).apply {
putStringProperty(ClientRPCRequestMessage.METHOD_NAME, method.name)
putLongProperty(ClientRPCRequestMessage.REPLY_TO, proxyId)
// Use the magic deduplication property built into Artemis as our message identity too
putStringProperty(HDR_DUPLICATE_DETECTION_ID, SimpleString(UUID.randomUUID().toString()))
}
}
private fun checkMethodVersion(method: Method) {
val methodVersion = method.getAnnotation(RPCSinceVersion::class.java)?.version ?: 0
if (methodVersion > serverProtocolVersion)
throw UnsupportedOperationException("Method ${method.name} was added in RPC protocol version $methodVersion but the server is running $serverProtocolVersion")
}
private fun isCloseInvocation(method: Method) = method == closeableCloseMethod || method == autocloseableCloseMethod
override fun close() {
consumer.close()
sessionLock.withLock { session.deleteQueue(constructAddress(proxyId)) }
}
override fun toString() = "Corda RPC Proxy listening on queue ${constructAddress(proxyId)}"
}
/**
* When subscribed to, starts consuming from the given queue name and demultiplexing the observables being
* sent to it. The server queue is moved into in-memory buffers (one per attached server-side observable)
* until drained through a subscription. When the subscriptions are all gone, the server-side queue is deleted.
*/
@ThreadSafe
private inner class QueuedObservable(private val qName: String,
private val rpcName: String,
private val rpcLocation: Throwable,
private val observableDeserializer: ObservableDeserializer) {
private val root = PublishSubject.create<MarshalledObservation>()
private val rootShared = root.doOnUnsubscribe { close() }.share()
// This could be made more efficient by using a specialised IntMap
private val observables = HashMap<Int, Observable<Any>>()
private var consumer: ClientConsumer? = sessionLock.withLock { session.createConsumer(qName) }.setMessageHandler { deliver(it) }
@Synchronized
fun getForHandle(handle: Int): Observable<Any> {
return observables.getOrPut(handle) {
/**
* Note that the order of bufferUntilSubscribed() -> dematerialize() is very important here.
*
* In particular doing it the other way around may result in the following edge case:
* The RPC returns two (or more) Observables. The first Observable unsubscribes *during serialisation*,
* before the second one is hit, causing the [rootShared] to unsubscribe and consequently closing
* the underlying artemis queue, even though the second Observable was not even registered.
*
* The buffer -> dematerialize order ensures that the Observable may not unsubscribe until the caller
* subscribes, which must be after full deserialisation and registering of all top level Observables.
*/
rootShared.filter { it.forHandle == handle }.map { it.what }.bufferUntilSubscribed().dematerialize<Any>().share()
}
}
private fun deliver(msg: ClientMessage) {
msg.acknowledge()
val kryo = createRPCKryo(observableSerializer = observableDeserializer)
val received: MarshalledObservation = msg.deserialize(kryo)
rpcLog.debug { "<- Observable [$rpcName] <- Received $received" }
synchronized(this) {
// Force creation of the buffer if it doesn't already exist.
getForHandle(received.forHandle)
root.onNext(received)
}
}
@Synchronized
fun close() {
rpcLog.debug("Closing queue observable for call to $rpcName : $qName")
consumer?.close()
consumer = null
sessionLock.withLock { session.deleteQueue(qName) }
}
@Suppress("UNUSED")
fun finalize() {
val c = synchronized(this) { consumer }
if (c != null) {
rpcLog.warn("A hot observable returned from an RPC ($rpcName) was never subscribed to or explicitly closed. " +
"This wastes server-side resources because it was queueing observations for retrieval. " +
"It is being closed now, but please adjust your code to cast the observable to AutoCloseable and then close it explicitly.", rpcLocation)
c.close()
}
}
}
//endregion
}

View File

@ -0,0 +1,100 @@
package net.corda.client.mock
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.*
import net.corda.core.crypto.Party
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.transactions.TransactionBuilder
import net.corda.flows.CashCommand
/**
* [Generator]s for incoming/outgoing events to/from the [WalletMonitorService]. Internally it keeps track of owned
* state/ref pairs, but it doesn't necessarily generate "correct" events!
*/
class EventGenerator(
val parties: List<Party>,
val notary: Party
) {
private var vault = listOf<StateAndRef<Cash.State>>()
val issuerGenerator =
Generator.pickOne(parties).combine(Generator.intRange(0, 1)) { party, ref -> party.ref(ref.toByte()) }
val currencies = setOf(USD, GBP, CHF).toList() // + Currency.getAvailableCurrencies().toList().subList(0, 3).toSet()).toList()
val currencyGenerator = Generator.pickOne(currencies)
val issuedGenerator = issuerGenerator.combine(currencyGenerator) { issuer, currency -> Issued(issuer, currency) }
val amountIssuedGenerator = generateAmount(1, 10000, issuedGenerator)
val publicKeyGenerator = Generator.pickOne(parties.map { it.owningKey })
val partyGenerator = Generator.pickOne(parties)
val cashStateGenerator = amountIssuedGenerator.combine(publicKeyGenerator) { amount, from ->
val builder = TransactionBuilder(notary = notary)
builder.addOutputState(Cash.State(amount, from))
builder.addCommand(Command(Cash.Commands.Issue(), amount.token.issuer.party.owningKey))
builder.toWireTransaction().outRef<Cash.State>(0)
}
val consumedGenerator: Generator<Set<StateRef>> = Generator.frequency(
0.7 to Generator.pure(setOf()),
0.3 to Generator.impure { vault }.bind { states ->
Generator.sampleBernoulli(states, 0.2).map { someStates ->
val consumedSet = someStates.map { it.ref }.toSet()
vault = vault.filter { it.ref !in consumedSet }
consumedSet
}
}
)
val producedGenerator: Generator<Set<StateAndRef<ContractState>>> = Generator.frequency(
// 0.1 to Generator.pure(setOf())
0.9 to Generator.impure { vault }.bind { states ->
Generator.replicate(2, cashStateGenerator).map {
vault = states + it
it.toSet()
}
}
)
val issueRefGenerator = Generator.intRange(0, 1).map { number -> OpaqueBytes(ByteArray(1, { number.toByte() })) }
val amountGenerator = Generator.intRange(0, 10000).combine(currencyGenerator) { quantity, currency -> Amount(quantity.toLong(), currency) }
val issueCashGenerator =
amountGenerator.combine(partyGenerator, issueRefGenerator) { amount, to, issueRef ->
CashCommand.IssueCash(
amount,
issueRef,
to,
notary
)
}
val moveCashGenerator =
amountIssuedGenerator.combine(
partyGenerator
) { amountIssued, recipient ->
CashCommand.PayCash(
amount = amountIssued,
recipient = recipient
)
}
val exitCashGenerator =
amountIssuedGenerator.map {
CashCommand.ExitCash(
it.withoutIssuer(),
it.token.issuer.reference
)
}
val clientCommandGenerator = Generator.frequency(
1.0 to moveCashGenerator
)
val bankOfCordaCommandGenerator = Generator.frequency(
0.6 to issueCashGenerator,
0.4 to exitCashGenerator
)
}

View File

@ -0,0 +1,211 @@
package net.corda.client.mock
import net.corda.client.mock.Generator.Companion.choice
import net.corda.core.ErrorOr
import java.util.*
/**
* This file defines a basic [Generator] library for composing random generators of objects.
*
* An object of type [Generator]<[A]> captures a generator of [A]s. Generators may be composed in several ways.
*
* [Generator.choice] picks a generator from the specified list and runs that.
* [Generator.frequency] is similar to [choice] but the probability may be specified for each generator (it is normalised before picking).
* [Generator.combine] combines two generators of A and B with a function (A, B) -> C. Variants exist for other arities.
* [Generator.bind] sequences two generators using an arbitrary A->Generator<B> function. Keep the usage of this
* function minimal as it may explode the stack, especially when using recursion.
*
* There are other utilities as well, the type of which are usually descriptive.
*
* Example:
* val birdNameGenerator = Generator.pickOne(listOf("raven", "pigeon"))
* val birdHeightGenerator = Generator.doubleRange(from = 10.0, to = 30.0)
* val birdGenerator = birdNameGenerator.combine(birdHeightGenerator) { name, height -> Bird(name, height) }
* val birdsGenerator = Generator.replicate(2, birdGenerator)
* val mammalsGenerator = Generator.sampleBernoulli(listOf(Mammal("fox"), Mammal("elephant")))
* val animalsGenerator = Generator.frequency(
* 0.2 to birdsGenerator,
* 0.8 to mammalsGenerator
* )
* val animals = animalsGenerator.generate(SplittableRandom()).getOrThrow()
*
* The above will generate a random list of animals.
*/
class Generator<out A : Any>(val generate: (SplittableRandom) -> ErrorOr<A>) {
// Functor
fun <B : Any> map(function: (A) -> B): Generator<B> =
Generator { generate(it).map(function) }
// Applicative
fun <B : Any> product(other: Generator<(A) -> B>) =
Generator { generate(it).combine(other.generate(it)) { a, f -> f(a) } }
fun <B : Any, R : Any> combine(other1: Generator<B>, function: (A, B) -> R) =
product<R>(other1.product(pure({ b -> { a -> function(a, b) } })))
fun <B : Any, C : Any, R : Any> combine(other1: Generator<B>, other2: Generator<C>, function: (A, B, C) -> R) =
product<R>(other1.product(other2.product(pure({ c -> { b -> { a -> function(a, b, c) } } }))))
fun <B : Any, C : Any, D : Any, R : Any> combine(other1: Generator<B>, other2: Generator<C>, other3: Generator<D>, function: (A, B, C, D) -> R) =
product<R>(other1.product(other2.product(other3.product(pure({ d -> { c -> { b -> { a -> function(a, b, c, d) } } } })))))
fun <B : Any, C : Any, D : Any, E : Any, R : Any> combine(other1: Generator<B>, other2: Generator<C>, other3: Generator<D>, other4: Generator<E>, function: (A, B, C, D, E) -> R) =
product<R>(other1.product(other2.product(other3.product(other4.product(pure({ e -> { d -> { c -> { b -> { a -> function(a, b, c, d, e) } } } } }))))))
// Monad
fun <B : Any> bind(function: (A) -> Generator<B>) =
Generator { generate(it).bind { a -> function(a).generate(it) } }
companion object {
fun <A : Any> pure(value: A) = Generator { ErrorOr(value) }
fun <A : Any> impure(valueClosure: () -> A) = Generator { ErrorOr(valueClosure()) }
fun <A : Any> fail(error: Exception) = Generator<A> { ErrorOr.of(error) }
// Alternative
fun <A : Any> choice(generators: List<Generator<A>>) = intRange(0, generators.size - 1).bind { generators[it] }
fun <A : Any> success(generate: (SplittableRandom) -> A) = Generator { ErrorOr(generate(it)) }
fun <A : Any> frequency(generators: List<Pair<Double, Generator<A>>>): Generator<A> {
val ranges = mutableListOf<Pair<Double, Double>>()
var current = 0.0
generators.forEach {
val next = current + it.first
ranges.add(Pair(current, next))
current = next
}
return doubleRange(0.0, current).bind { value ->
generators[ranges.binarySearch { range ->
if (value < range.first) {
1
} else if (value < range.second) {
0
} else {
-1
}
}].second
}
}
fun <A : Any> sequence(generators: List<Generator<A>>) = Generator<List<A>> {
val result = mutableListOf<A>()
for (generator in generators) {
val element = generator.generate(it)
val v = element.value
if (v != null) {
result.add(v)
} else {
return@Generator ErrorOr.of(element.error!!)
}
}
ErrorOr(result)
}
}
}
fun <A : Any> Generator.Companion.frequency(vararg generators: Pair<Double, Generator<A>>) = frequency(generators.toList())
fun <A : Any> Generator<A>.generateOrFail(random: SplittableRandom, numberOfTries: Int = 1): A {
var error: Throwable? = null
for (i in 0..numberOfTries - 1) {
val result = generate(random)
val v = result.value
if (v != null) {
return v
} else {
error = result.error
}
}
if (error == null) {
throw IllegalArgumentException("numberOfTries cannot be <= 0")
} else {
throw Exception("Failed to generate", error)
}
}
fun Generator.Companion.int() = Generator.success(SplittableRandom::nextInt)
fun Generator.Companion.bytes(size: Int): Generator<ByteArray> = Generator.success { random ->
ByteArray(size) { random.nextInt().toByte() }
}
fun Generator.Companion.intRange(range: IntRange) = intRange(range.first, range.last)
fun Generator.Companion.intRange(from: Int, to: Int): Generator<Int> = Generator.success {
(from + Math.abs(it.nextInt()) % (to - from + 1)).toInt()
}
fun Generator.Companion.longRange(range: LongRange) = longRange(range.first, range.last)
fun Generator.Companion.longRange(from: Long, to: Long): Generator<Long> = Generator.success {
(from + Math.abs(it.nextLong()) % (to - from + 1)).toLong()
}
fun Generator.Companion.double() = Generator.success { it.nextDouble() }
fun Generator.Companion.doubleRange(from: Double, to: Double): Generator<Double> = Generator.success {
from + it.nextDouble() * (to - from)
}
fun <A : Any> Generator.Companion.replicate(number: Int, generator: Generator<A>): Generator<List<A>> {
val generators = mutableListOf<Generator<A>>()
for (i in 1..number) {
generators.add(generator)
}
return sequence(generators)
}
fun <A : Any> Generator.Companion.replicatePoisson(meanSize: Double, generator: Generator<A>) = Generator<List<A>> {
val chance = (meanSize - 1) / meanSize
val result = mutableListOf<A>()
var finish = false
while (!finish) {
val errorOr = Generator.doubleRange(0.0, 1.0).generate(it).bind { value ->
if (value < chance) {
generator.generate(it).map { result.add(it) }
} else {
finish = true
ErrorOr(Unit)
}
}
val e = errorOr.error
if (e != null) {
return@Generator ErrorOr.of(e)
}
}
ErrorOr(result)
}
fun <A : Any> Generator.Companion.pickOne(list: List<A>) = Generator.intRange(0, list.size - 1).map { list[it] }
fun <A : Any> Generator.Companion.pickN(number: Int, list: List<A>) = Generator<List<A>> {
val mask = BitSet(list.size)
for (i in 0..Math.min(list.size, number) - 1) {
mask[i] = 1
}
for (i in 0..mask.size() - 1) {
val byte = mask[i]
val swapIndex = i + it.nextInt(mask.size() - i)
mask[i] = mask[swapIndex]
mask[swapIndex] = byte
}
val resultList = ArrayList<A>()
list.forEachIndexed { index, a ->
if (mask[index]) {
resultList.add(a)
}
}
ErrorOr(resultList)
}
fun <A> Generator.Companion.sampleBernoulli(maxRatio: Double = 1.0, vararg collection: A) =
sampleBernoulli(listOf(collection), maxRatio)
fun <A> Generator.Companion.sampleBernoulli(collection: Collection<A>, maxRatio: Double = 1.0): Generator<List<A>> =
intRange(0, (maxRatio * collection.size).toInt()).bind { howMany ->
replicate(collection.size, Generator.doubleRange(0.0, 1.0)).map { chances ->
val result = mutableListOf<A>()
collection.forEachIndexed { index, element ->
if (chances[index] < howMany.toDouble() / collection.size.toDouble()) {
result.add(element)
}
}
result
}
}

View File

@ -0,0 +1,21 @@
package net.corda.client.mock
import net.corda.core.contracts.Amount
import net.corda.core.serialization.OpaqueBytes
import java.util.*
fun generateCurrency(): Generator<Currency> {
return Generator.pickOne(Currency.getAvailableCurrencies().toList())
}
fun <T : Any> generateAmount(min: Long, max: Long, tokenGenerator: Generator<T>): Generator<Amount<T>> {
return Generator.longRange(min, max).combine(tokenGenerator) { quantity, token -> Amount(quantity, token) }
}
fun generateCurrencyAmount(min: Long, max: Long): Generator<Amount<Currency>> {
return generateAmount(min, max, generateCurrency())
}
fun generateIssueRef(size: Int): Generator<OpaqueBytes> {
return Generator.bytes(size).map { OpaqueBytes(it) }
}

View File

@ -0,0 +1,54 @@
package net.corda.client.model
import javafx.collections.ObservableList
import kotlinx.support.jdk8.collections.removeIf
import net.corda.client.fxutils.foldToObservableList
import net.corda.client.fxutils.map
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.node.services.Vault
import rx.Observable
data class Diff<out T : ContractState>(
val added: Collection<StateAndRef<T>>,
val removed: Collection<StateRef>
)
/**
* This model exposes the list of owned contract states.
*/
class ContractStateModel {
private val vaultUpdates: Observable<Vault.Update> by observable(NodeMonitorModel::vaultUpdates)
private val contractStatesDiff: Observable<Diff<ContractState>> = vaultUpdates.map {
Diff(it.produced, it.consumed)
}
private val cashStatesDiff: Observable<Diff<Cash.State>> = contractStatesDiff.map {
// We can't filter removed hashes here as we don't have type info
Diff(it.added.filterCashStateAndRefs(), it.removed)
}
val cashStates: ObservableList<StateAndRef<Cash.State>> =
cashStatesDiff.foldToObservableList(Unit) { statesDiff, _accumulator, observableList ->
observableList.removeIf { it.ref in statesDiff.removed }
observableList.addAll(statesDiff.added)
}
val cash = cashStates.map { it.state.data.amount }
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>
} else {
null
}
}.filterNotNull()
}
}
}

View File

@ -1,16 +1,18 @@
package com.r3corda.client.model
package net.corda.client.model
import com.r3corda.core.contracts.Amount
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue
import net.corda.core.contracts.Amount
import java.util.*
interface ExchangeRate {
fun rate(from: Currency, to: Currency): Double
}
fun ExchangeRate.exchangeAmount(amount: Amount<Currency>, to: Currency) =
Amount(exchangeDouble(amount, to).toLong(), to)
fun ExchangeRate.exchangeDouble(amount: Amount<Currency>, to: Currency) =
rate(amount.token, to) * amount.quantity

View File

@ -0,0 +1,115 @@
package net.corda.client.model
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue
import javafx.collections.ObservableList
import javafx.collections.ObservableMap
import net.corda.client.fxutils.*
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.StateMachineRunId
import net.corda.core.transactions.SignedTransaction
import net.corda.node.services.messaging.StateMachineUpdate
import org.fxmisc.easybind.EasyBind
data class GatheredTransactionData(
val transaction: PartiallyResolvedTransaction,
val stateMachines: ObservableList<out StateMachineData>
)
/**
* [PartiallyResolvedTransaction] holds a [SignedTransaction] that has zero or more inputs resolved. The intent is
* to prepare clients for cases where an input can only be resolved in the future/cannot be resolved at all (for example
* because of permissioning)
*/
data class PartiallyResolvedTransaction(
val transaction: SignedTransaction,
val inputs: List<ObservableValue<InputResolution>>) {
val id = transaction.id
sealed class InputResolution(val stateRef: StateRef) {
class Unresolved(stateRef: StateRef) : InputResolution(stateRef)
class Resolved(val stateAndRef: StateAndRef<ContractState>) : InputResolution(stateAndRef.ref)
}
companion object {
fun fromSignedTransaction(
transaction: SignedTransaction,
transactions: ObservableMap<SecureHash, SignedTransaction>
) = PartiallyResolvedTransaction(
transaction = transaction,
inputs = transaction.tx.inputs.map { stateRef ->
EasyBind.map(transactions.getObservableValue(stateRef.txhash)) {
if (it == null) {
InputResolution.Unresolved(stateRef)
} else {
InputResolution.Resolved(it.tx.outRef(stateRef.index))
}
}
}
)
}
}
sealed class TransactionCreateStatus(val message: String?) {
class Started(message: String?) : TransactionCreateStatus(message)
class Failed(message: String?) : TransactionCreateStatus(message)
override fun toString(): String = message ?: javaClass.simpleName
}
data class FlowStatus(
val status: String
)
sealed class StateMachineStatus(val stateMachineName: String) {
class Added(stateMachineName: String) : StateMachineStatus(stateMachineName)
class Removed(stateMachineName: String) : StateMachineStatus(stateMachineName)
override fun toString(): String = "${javaClass.simpleName}($stateMachineName)"
}
data class StateMachineData(
val id: StateMachineRunId,
val flowStatus: ObservableValue<FlowStatus?>,
val stateMachineStatus: ObservableValue<StateMachineStatus>
)
/**
* This model provides an observable list of transactions and what state machines/flows recorded them
*/
class GatheredTransactionDataModel {
private val transactions by observable(NodeMonitorModel::transactions)
private val stateMachineUpdates by observable(NodeMonitorModel::stateMachineUpdates)
private val progressTracking by observable(NodeMonitorModel::progressTracking)
private val stateMachineTransactionMapping by observable(NodeMonitorModel::stateMachineTransactionMapping)
private val collectedTransactions = transactions.recordInSequence()
private val transactionMap = collectedTransactions.associateBy(SignedTransaction::id)
private val progressEvents = progressTracking.recordAsAssociation(ProgressTrackingEvent::stateMachineId)
private val stateMachineStatus = stateMachineUpdates.foldToObservableMap(Unit) { update, _unit, map: ObservableMap<StateMachineRunId, SimpleObjectProperty<StateMachineStatus>> ->
when (update) {
is StateMachineUpdate.Added -> {
val added: SimpleObjectProperty<StateMachineStatus> =
SimpleObjectProperty(StateMachineStatus.Added(update.stateMachineInfo.flowLogicClassName))
map[update.id] = added
}
is StateMachineUpdate.Removed -> {
val added = map[update.id]
added ?: throw Exception("State machine removed with unknown id ${update.id}")
added.set(StateMachineStatus.Removed(added.value.stateMachineName))
}
}
}
private val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
StateMachineData(id, progress.map { it?.let { FlowStatus(it.message) } }, status)
}.getObservableValues()
// TODO : Create a new screen for state machines.
private val stateMachineDataMap = stateMachineDataList.associateBy(StateMachineData::id)
private val smTxMappingList = stateMachineTransactionMapping.recordInSequence()
val partiallyResolvedTransactions = collectedTransactions.map {
PartiallyResolvedTransaction.fromSignedTransaction(it, transactionMap)
}
}

View File

@ -1,4 +1,4 @@
package com.r3corda.client.model
package net.corda.client.model
import javafx.beans.property.ObjectProperty
import javafx.beans.value.ObservableValue
@ -106,7 +106,7 @@ object Models {
private val dependencyGraph = HashMap<KClass<*>, MutableSet<KClass<*>>>()
fun <M : Any> initModel(klass: KClass<M>) = modelStore.getOrPut(klass) { klass.java.newInstance() }
fun <M : Any> get(klass: KClass<M>, origin: KClass<*>) : M {
fun <M : Any> get(klass: KClass<M>, origin: KClass<*>): M {
dependencyGraph.getOrPut(origin) { mutableSetOf<KClass<*>>() }.add(klass)
val model = initModel(klass)
if (model.javaClass != klass.java) {
@ -116,57 +116,69 @@ object Models {
@Suppress("UNCHECKED_CAST")
return model as M
}
inline fun <reified M : Any> get(origin: KClass<*>) : M = get(M::class, origin)
inline fun <reified M : Any> get(origin: KClass<*>): M = get(M::class, origin)
}
sealed class TrackedDelegate<M : Any>(val klass: KClass<M>) {
init { Models.initModel(klass) }
init {
Models.initModel(klass)
}
class ObservableDelegate<M : Any, T> (klass: KClass<M>, val observableProperty: (M) -> Observable<T>) : TrackedDelegate<M>(klass) {
class ObservableDelegate<M : Any, T>(klass: KClass<M>, val observableProperty: (M) -> Observable<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): Observable<T> {
return observableProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class ObserverDelegate<M : Any, T> (klass: KClass<M>, val observerProperty: (M) -> Observer<T>) : TrackedDelegate<M>(klass) {
class ObserverDelegate<M : Any, T>(klass: KClass<M>, val observerProperty: (M) -> Observer<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): Observer<T> {
return observerProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class SubjectDelegate<M : Any, T> (klass: KClass<M>, val subjectProperty: (M) -> Subject<T, T>) : TrackedDelegate<M>(klass) {
class SubjectDelegate<M : Any, T>(klass: KClass<M>, val subjectProperty: (M) -> Subject<T, T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): Subject<T, T> {
return subjectProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class EventStreamDelegate<M : Any, T> (klass: KClass<M>, val eventStreamProperty: (M) -> org.reactfx.EventStream<T>) : TrackedDelegate<M>(klass) {
class EventStreamDelegate<M : Any, T>(klass: KClass<M>, val eventStreamProperty: (M) -> org.reactfx.EventStream<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): org.reactfx.EventStream<T> {
return eventStreamProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class EventSinkDelegate<M : Any, T> (klass: KClass<M>, val eventSinkProperty: (M) -> org.reactfx.EventSink<T>) : TrackedDelegate<M>(klass) {
class EventSinkDelegate<M : Any, T>(klass: KClass<M>, val eventSinkProperty: (M) -> org.reactfx.EventSink<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): org.reactfx.EventSink<T> {
return eventSinkProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class ObservableValueDelegate<M : Any, T>(klass: KClass<M>, val observableValueProperty: (M) -> ObservableValue<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): ObservableValue<T> {
return observableValueProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class WritableValueDelegate<M : Any, T>(klass: KClass<M>, val writableValueProperty: (M) -> WritableValue<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): WritableValue<T> {
return writableValueProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class ObservableListDelegate<M : Any, T>(klass: KClass<M>, val observableListProperty: (M) -> ObservableList<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): ObservableList<T> {
return observableListProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class ObservableListReadOnlyDelegate<M : Any, out T>(klass: KClass<M>, val observableListReadOnlyProperty: (M) -> ObservableList<out T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): ObservableList<out T> {
return observableListReadOnlyProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class ObjectPropertyDelegate<M : Any, T>(klass: KClass<M>, val objectPropertyProperty: (M) -> ObjectProperty<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): ObjectProperty<T> {
return objectPropertyProperty(Models.get(klass, thisRef.javaClass.kotlin))

View File

@ -0,0 +1,49 @@
package net.corda.client.model
import javafx.beans.value.ObservableValue
import javafx.collections.ObservableList
import kotlinx.support.jdk8.collections.removeIf
import net.corda.client.fxutils.firstOrDefault
import net.corda.client.fxutils.firstOrNullObservable
import net.corda.client.fxutils.foldToObservableList
import net.corda.client.fxutils.map
import net.corda.core.crypto.CompositeKey
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache.MapChange
import net.corda.node.services.network.NetworkMapService
import java.security.PublicKey
class NetworkIdentityModel {
private val networkIdentityObservable by observable(NodeMonitorModel::networkMap)
val networkIdentities: ObservableList<NodeInfo> =
networkIdentityObservable.foldToObservableList(Unit) { update, _accumulator, observableList ->
observableList.removeIf {
when (update) {
is MapChange.Removed -> it == update.node
is MapChange.Modified -> it == update.previousNode
else -> false
}
}
observableList.addAll(update.node)
}
private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable)
val parties: ObservableList<NodeInfo> = networkIdentities.filtered { !it.isCordaService() }
val notaries: ObservableList<NodeInfo> = networkIdentities.filtered { it.advertisedServices.any { it.info.type.isNotary() } }
val myIdentity = rpcProxy.map { it?.nodeIdentity() }
private fun NodeInfo.isCordaService(): Boolean {
// TODO: better way to identify Corda service?
return advertisedServices.any { it.info.type == NetworkMapService.type || it.info.type.isNotary() }
}
fun lookup(compositeKey: CompositeKey): ObservableValue<NodeInfo?> = parties.firstOrDefault(notaries.firstOrNullObservable { it.notaryIdentity.owningKey == compositeKey }) {
it.legalIdentity.owningKey == compositeKey
}
fun lookup(publicKey: PublicKey): ObservableValue<NodeInfo?> = parties.firstOrDefault(notaries.firstOrNullObservable { it.notaryIdentity.owningKey.keys.any { it == publicKey } }) {
it.legalIdentity.owningKey.keys.any { it == publicKey }
}
}

View File

@ -0,0 +1,107 @@
package net.corda.client.model
import com.google.common.net.HostAndPort
import javafx.beans.property.SimpleObjectProperty
import net.corda.client.CordaRPCClient
import net.corda.core.flows.StateMachineRunId
import net.corda.core.node.services.NetworkMapCache.MapChange
import net.corda.core.node.services.StateMachineTransactionMapping
import net.corda.core.node.services.Vault
import net.corda.core.transactions.SignedTransaction
import net.corda.flows.CashCommand
import net.corda.flows.CashFlow
import net.corda.node.services.config.NodeSSLConfiguration
import net.corda.node.services.messaging.CordaRPCOps
import net.corda.node.services.messaging.StateMachineInfo
import net.corda.node.services.messaging.StateMachineUpdate
import net.corda.node.services.messaging.startFlow
import rx.Observable
import rx.subjects.PublishSubject
data class ProgressTrackingEvent(val stateMachineId: StateMachineRunId, val message: String) {
companion object {
fun createStreamFromStateMachineInfo(stateMachine: StateMachineInfo): Observable<ProgressTrackingEvent>? {
return stateMachine.progressTrackerStepAndUpdates?.let { pair ->
val (current, future) = pair
future.map { ProgressTrackingEvent(stateMachine.id, it) }.startWith(ProgressTrackingEvent(stateMachine.id, current))
}
}
}
}
/**
* This model exposes raw event streams to and from the node.
*/
class NodeMonitorModel {
private val stateMachineUpdatesSubject = PublishSubject.create<StateMachineUpdate>()
private val vaultUpdatesSubject = PublishSubject.create<Vault.Update>()
private val transactionsSubject = PublishSubject.create<SignedTransaction>()
private val stateMachineTransactionMappingSubject = PublishSubject.create<StateMachineTransactionMapping>()
private val progressTrackingSubject = PublishSubject.create<ProgressTrackingEvent>()
private val networkMapSubject = PublishSubject.create<MapChange>()
val stateMachineUpdates: Observable<StateMachineUpdate> = stateMachineUpdatesSubject
val vaultUpdates: Observable<Vault.Update> = vaultUpdatesSubject
val transactions: Observable<SignedTransaction> = transactionsSubject
val stateMachineTransactionMapping: Observable<StateMachineTransactionMapping> = stateMachineTransactionMappingSubject
val progressTracking: Observable<ProgressTrackingEvent> = progressTrackingSubject
val networkMap: Observable<MapChange> = networkMapSubject
private val clientToServiceSource = PublishSubject.create<CashCommand>()
val clientToService: PublishSubject<CashCommand> = clientToServiceSource
val proxyObservable = SimpleObjectProperty<CordaRPCOps?>()
/**
* Register for updates to/from a given vault.
* TODO provide an unsubscribe mechanism
*/
fun register(nodeHostAndPort: HostAndPort, sslConfig: NodeSSLConfiguration, username: String, password: String) {
val client = CordaRPCClient(nodeHostAndPort, sslConfig)
client.start(username, password)
val proxy = client.proxy()
val (stateMachines, stateMachineUpdates) = proxy.stateMachinesAndUpdates()
// Extract the flow tracking stream
// TODO is there a nicer way of doing this? Stream of streams in general results in code like this...
val currentProgressTrackerUpdates = stateMachines.mapNotNull { stateMachine ->
ProgressTrackingEvent.createStreamFromStateMachineInfo(stateMachine)
}
val futureProgressTrackerUpdates = stateMachineUpdatesSubject.map { stateMachineUpdate ->
if (stateMachineUpdate is StateMachineUpdate.Added) {
ProgressTrackingEvent.createStreamFromStateMachineInfo(stateMachineUpdate.stateMachineInfo) ?: Observable.empty<ProgressTrackingEvent>()
} else {
Observable.empty<ProgressTrackingEvent>()
}
}
futureProgressTrackerUpdates.startWith(currentProgressTrackerUpdates).flatMap { it }.subscribe(progressTrackingSubject)
// Now the state machines
val currentStateMachines = stateMachines.map { StateMachineUpdate.Added(it) }
stateMachineUpdates.startWith(currentStateMachines).subscribe(stateMachineUpdatesSubject)
// Vault updates
val (vault, vaultUpdates) = proxy.vaultAndUpdates()
val initialVaultUpdate = Vault.Update(setOf(), vault.toSet())
vaultUpdates.startWith(initialVaultUpdate).subscribe(vaultUpdatesSubject)
// Transactions
val (transactions, newTransactions) = proxy.verifiedTransactions()
newTransactions.startWith(transactions).subscribe(transactionsSubject)
// SM -> TX mapping
val (smTxMappings, futureSmTxMappings) = proxy.stateMachineRecordedTransactionMapping()
futureSmTxMappings.startWith(smTxMappings).subscribe(stateMachineTransactionMappingSubject)
// Parties on network
val (parties, futurePartyUpdate) = proxy.networkMapUpdates()
futurePartyUpdate.startWith(parties.map { MapChange.Added(it) }).subscribe(networkMapSubject)
// Client -> Service
clientToServiceSource.subscribe {
proxy.startFlow(::CashFlow, it)
}
proxyObservable.set(proxy)
}
}

View File

@ -1,202 +0,0 @@
package com.r3corda.client
import com.r3corda.client.impl.CordaRPCClientImpl
import com.r3corda.core.serialization.SerializedBytes
import com.r3corda.core.utilities.LogHelper
import com.r3corda.node.services.messaging.*
import com.r3corda.node.utilities.AffinityExecutor
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 org.apache.activemq.artemis.api.core.client.ClientProducer
import org.apache.activemq.artemis.api.core.client.ClientSession
import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl
import org.apache.activemq.artemis.core.remoting.impl.invm.InVMAcceptorFactory
import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnectorFactory
import org.apache.activemq.artemis.core.server.embedded.EmbeddedActiveMQ
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import rx.Observable
import rx.subjects.PublishSubject
import java.io.Closeable
import java.util.*
import java.util.concurrent.CountDownLatch
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.locks.ReentrantLock
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class ClientRPCInfrastructureTests {
// TODO: Test that timeouts work
lateinit var artemis: EmbeddedActiveMQ
lateinit var serverSession: ClientSession
lateinit var clientSession: ClientSession
lateinit var producer: ClientProducer
lateinit var serverThread: AffinityExecutor.ServiceAffinityExecutor
lateinit var proxy: ITestOps
@Before
fun setup() {
// Set up an in-memory Artemis with an RPC requests queue.
artemis = EmbeddedActiveMQ()
artemis.setConfiguration(ConfigurationImpl().apply {
acceptorConfigurations = setOf(TransportConfiguration(InVMAcceptorFactory::class.java.name))
isSecurityEnabled = false
isPersistenceEnabled = false
})
artemis.start()
val serverLocator = ActiveMQClient.createServerLocatorWithoutHA(TransportConfiguration(InVMConnectorFactory::class.java.name))
val sessionFactory = serverLocator.createSessionFactory()
serverSession = sessionFactory.createSession()
serverSession.start()
serverSession.createTemporaryQueue(ArtemisMessagingComponent.RPC_REQUESTS_QUEUE, ArtemisMessagingComponent.RPC_REQUESTS_QUEUE)
producer = serverSession.createProducer()
val dispatcher = object : RPCDispatcher(TestOps()) {
override fun send(bits: SerializedBytes<*>, toAddress: String) {
val msg = serverSession.createMessage(false).apply {
writeBodyBufferBytes(bits.bits)
// Use the magic deduplication property built into Artemis as our message identity too
putStringProperty(org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID, SimpleString(UUID.randomUUID().toString()))
}
producer.send(toAddress, msg)
}
}
serverThread = AffinityExecutor.ServiceAffinityExecutor("unit-tests-rpc-dispatch-thread", 1)
val serverConsumer = serverSession.createConsumer(ArtemisMessagingComponent.RPC_REQUESTS_QUEUE)
serverSession.createTemporaryQueue("activemq.notifications", "rpc.qremovals", "_AMQ_NotifType = 'BINDING_REMOVED'")
val serverNotifConsumer = serverSession.createConsumer("rpc.qremovals")
dispatcher.start(serverConsumer, serverNotifConsumer, serverThread)
clientSession = sessionFactory.createSession()
clientSession.start()
LogHelper.setLevel("+com.r3corda.rpc"/*, "+org.apache.activemq"*/)
proxy = CordaRPCClientImpl(clientSession, ReentrantLock(), "tests").proxyFor(ITestOps::class.java)
}
@After
fun shutdown() {
(proxy as Closeable).close()
clientSession.stop()
serverSession.stop()
artemis.stop()
serverThread.shutdownNow()
}
interface ITestOps : RPCOps {
@Throws(IllegalArgumentException::class)
fun barf()
fun void()
fun someCalculation(str: String, num: Int): String
@RPCReturnsObservables
fun makeObservable(): Observable<Int>
@RPCReturnsObservables
fun makeComplicatedObservable(): Observable<Pair<String, Observable<String>>>
@RPCSinceVersion(2)
fun addedLater()
}
lateinit var complicatedObservable: Observable<Pair<String, Observable<String>>>
inner class TestOps : ITestOps {
override val protocolVersion = 1
override fun barf() {
throw IllegalArgumentException("Barf!")
}
override fun void() { }
override fun someCalculation(str: String, num: Int) = "$str $num"
override fun makeObservable(): Observable<Int> {
return Observable.just(1, 2, 3, 4)
}
override fun makeComplicatedObservable() = complicatedObservable
override fun addedLater() {
throw UnsupportedOperationException("not implemented")
}
}
@Test
fun simpleRPCs() {
// Does nothing, doesn't throw.
proxy.void()
assertEquals("Barf!", assertFailsWith<IllegalArgumentException> {
proxy.barf()
}.message)
assertEquals("hi 5", proxy.someCalculation("hi", 5))
}
@Test
fun simpleObservable() {
// This tests that the observations are transmitted correctly, also completion is transmitted.
val observations = proxy.makeObservable().toBlocking().toIterable().toList()
assertEquals(listOf(1, 2, 3, 4), observations)
}
@Test
fun complexObservables() {
// This checks that we can return an object graph with complex usage of observables, like an observable
// that emits objects that contain more observables.
val serverQuotes = PublishSubject.create<Pair<String, Observable<String>>>()
val unsubscribeLatch = CountDownLatch(1)
complicatedObservable = serverQuotes.asObservable().doOnUnsubscribe { unsubscribeLatch.countDown() }
val twainQuotes = "Mark Twain" to Observable.just(
"I have never let my schooling interfere with my education.",
"Clothes make the man. Naked people have little or no influence on society."
)
val wildeQuotes = "Oscar Wilde" to Observable.just(
"I can resist everything except temptation.",
"Always forgive your enemies - nothing annoys them so much."
)
val clientQuotes = LinkedBlockingQueue<String>()
val clientObs = proxy.makeComplicatedObservable()
val subscription = clientObs.subscribe {
val name = it.first
it.second.subscribe {
clientQuotes += "Quote by $name: $it"
}
}
assertEquals(1, clientSession.addressQuery(SimpleString("tests.rpc.observations.#")).queueNames.size)
assertThat(clientQuotes).isEmpty()
serverQuotes.onNext(twainQuotes)
assertEquals("Quote by Mark Twain: I have never let my schooling interfere with my education.", clientQuotes.take())
assertEquals("Quote by Mark Twain: Clothes make the man. Naked people have little or no influence on society.", clientQuotes.take())
serverQuotes.onNext(wildeQuotes)
assertEquals("Quote by Oscar Wilde: I can resist everything except temptation.", clientQuotes.take())
assertEquals("Quote by Oscar Wilde: Always forgive your enemies - nothing annoys them so much.", clientQuotes.take())
assertTrue(serverQuotes.hasObservers())
subscription.unsubscribe()
unsubscribeLatch.await()
assertEquals(0, clientSession.addressQuery(SimpleString("tests.rpc.observations.#")).queueNames.size)
}
@Test
fun versioning() {
assertFailsWith<UnsupportedOperationException> { proxy.addedLater() }
}
}

View File

@ -0,0 +1,221 @@
package net.corda.client
import net.corda.client.impl.CordaRPCClientImpl
import net.corda.core.serialization.SerializedBytes
import net.corda.core.utilities.LogHelper
import net.corda.node.services.RPCUserService
import net.corda.node.services.User
import net.corda.node.services.messaging.*
import net.corda.node.services.messaging.ArtemisMessagingComponent.Companion.RPC_REQUESTS_QUEUE
import net.corda.node.utilities.AffinityExecutor
import org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID
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 org.apache.activemq.artemis.api.core.client.ClientMessage
import org.apache.activemq.artemis.api.core.client.ClientProducer
import org.apache.activemq.artemis.api.core.client.ClientSession
import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl
import org.apache.activemq.artemis.core.remoting.impl.invm.InVMAcceptorFactory
import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnectorFactory
import org.apache.activemq.artemis.core.server.embedded.EmbeddedActiveMQ
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import rx.Observable
import rx.subjects.PublishSubject
import java.io.Closeable
import java.util.*
import java.util.concurrent.CountDownLatch
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.locks.ReentrantLock
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class ClientRPCInfrastructureTests {
// TODO: Test that timeouts work
lateinit var artemis: EmbeddedActiveMQ
lateinit var serverSession: ClientSession
lateinit var clientSession: ClientSession
lateinit var producer: ClientProducer
lateinit var serverThread: AffinityExecutor.ServiceAffinityExecutor
lateinit var proxy: TestOps
private val authenticatedUser = User("test", "password", permissions = setOf())
@Before
fun setup() {
// Set up an in-memory Artemis with an RPC requests queue.
artemis = EmbeddedActiveMQ()
artemis.setConfiguration(ConfigurationImpl().apply {
acceptorConfigurations = setOf(TransportConfiguration(InVMAcceptorFactory::class.java.name))
isSecurityEnabled = false
isPersistenceEnabled = false
})
artemis.start()
val serverLocator = ActiveMQClient.createServerLocatorWithoutHA(TransportConfiguration(InVMConnectorFactory::class.java.name))
val sessionFactory = serverLocator.createSessionFactory()
serverSession = sessionFactory.createSession()
serverSession.start()
serverSession.createTemporaryQueue(RPC_REQUESTS_QUEUE, RPC_REQUESTS_QUEUE)
producer = serverSession.createProducer()
val userService = object : RPCUserService {
override fun getUser(username: String): User? = throw UnsupportedOperationException()
override val users: List<User> get() = throw UnsupportedOperationException()
}
val dispatcher = object : RPCDispatcher(TestOpsImpl(), userService) {
override fun send(data: SerializedBytes<*>, toAddress: String) {
val msg = serverSession.createMessage(false).apply {
writeBodyBufferBytes(data.bytes)
// Use the magic deduplication property built into Artemis as our message identity too
putStringProperty(HDR_DUPLICATE_DETECTION_ID, SimpleString(UUID.randomUUID().toString()))
}
producer.send(toAddress, msg)
}
override fun getUser(message: ClientMessage): User = authenticatedUser
}
serverThread = AffinityExecutor.ServiceAffinityExecutor("unit-tests-rpc-dispatch-thread", 1)
val serverConsumer = serverSession.createConsumer(RPC_REQUESTS_QUEUE)
serverSession.createTemporaryQueue("activemq.notifications", "rpc.qremovals", "_AMQ_NotifType = 'BINDING_REMOVED'")
val serverNotifConsumer = serverSession.createConsumer("rpc.qremovals")
dispatcher.start(serverConsumer, serverNotifConsumer, serverThread)
clientSession = sessionFactory.createSession()
clientSession.start()
LogHelper.setLevel("+net.corda.rpc")
proxy = CordaRPCClientImpl(clientSession, ReentrantLock(), authenticatedUser.username).proxyFor(TestOps::class.java)
}
@After
fun shutdown() {
(proxy as Closeable?)?.close()
clientSession.stop()
serverSession.stop()
artemis.stop()
serverThread.shutdownNow()
}
interface TestOps : RPCOps {
@Throws(IllegalArgumentException::class)
fun barf()
fun void()
fun someCalculation(str: String, num: Int): String
@RPCReturnsObservables
fun makeObservable(): Observable<Int>
@RPCReturnsObservables
fun makeComplicatedObservable(): Observable<Pair<String, Observable<String>>>
@RPCSinceVersion(2)
fun addedLater()
fun captureUser(): String
}
lateinit var complicatedObservable: Observable<Pair<String, Observable<String>>>
inner class TestOpsImpl : TestOps {
override val protocolVersion = 1
override fun barf(): Unit = throw IllegalArgumentException("Barf!")
override fun void() {
}
override fun someCalculation(str: String, num: Int) = "$str $num"
override fun makeObservable(): Observable<Int> = Observable.just(1, 2, 3, 4)
override fun makeComplicatedObservable() = complicatedObservable
override fun addedLater(): Unit = throw UnsupportedOperationException("not implemented")
override fun captureUser(): String = CURRENT_RPC_USER.get().username
}
@Test
fun `simple RPCs`() {
// Does nothing, doesn't throw.
proxy.void()
assertEquals("Barf!", assertFailsWith<IllegalArgumentException> {
proxy.barf()
}.message)
assertEquals("hi 5", proxy.someCalculation("hi", 5))
}
@Test
fun `simple observable`() {
// This tests that the observations are transmitted correctly, also completion is transmitted.
val observations = proxy.makeObservable().toBlocking().toIterable().toList()
assertEquals(listOf(1, 2, 3, 4), observations)
}
@Test
fun `complex observables`() {
// This checks that we can return an object graph with complex usage of observables, like an observable
// that emits objects that contain more observables.
val serverQuotes = PublishSubject.create<Pair<String, Observable<String>>>()
val unsubscribeLatch = CountDownLatch(1)
complicatedObservable = serverQuotes.asObservable().doOnUnsubscribe { unsubscribeLatch.countDown() }
val twainQuotes = "Mark Twain" to Observable.just(
"I have never let my schooling interfere with my education.",
"Clothes make the man. Naked people have little or no influence on society."
)
val wildeQuotes = "Oscar Wilde" to Observable.just(
"I can resist everything except temptation.",
"Always forgive your enemies - nothing annoys them so much."
)
val clientQuotes = LinkedBlockingQueue<String>()
val clientObs = proxy.makeComplicatedObservable()
val subscription = clientObs.subscribe {
val name = it.first
it.second.subscribe {
clientQuotes += "Quote by $name: $it"
}
}
val rpcQueuesQuery = SimpleString("clients.${authenticatedUser.username}.rpc.*")
assertEquals(2, clientSession.addressQuery(rpcQueuesQuery).queueNames.size)
assertThat(clientQuotes).isEmpty()
serverQuotes.onNext(twainQuotes)
assertEquals("Quote by Mark Twain: I have never let my schooling interfere with my education.", clientQuotes.take())
assertEquals("Quote by Mark Twain: Clothes make the man. Naked people have little or no influence on society.", clientQuotes.take())
serverQuotes.onNext(wildeQuotes)
assertEquals("Quote by Oscar Wilde: I can resist everything except temptation.", clientQuotes.take())
assertEquals("Quote by Oscar Wilde: Always forgive your enemies - nothing annoys them so much.", clientQuotes.take())
assertTrue(serverQuotes.hasObservers())
subscription.unsubscribe()
unsubscribeLatch.await()
assertEquals(1, clientSession.addressQuery(rpcQueuesQuery).queueNames.size)
}
@Test
fun versioning() {
assertFailsWith<UnsupportedOperationException> { proxy.addedLater() }
}
@Test
fun `authenticated user is available to RPC`() {
assertThat(proxy.captureUser()).isEqualTo(authenticatedUser.username)
}
}

View File

@ -1,4 +1,4 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import javafx.collections.FXCollections
import javafx.collections.ObservableList

View File

@ -1,4 +1,4 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import javafx.collections.FXCollections
import javafx.collections.ObservableList

View File

@ -1,4 +1,4 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import javafx.collections.FXCollections
import javafx.collections.ObservableList
@ -24,7 +24,7 @@ class ConcatenatedListTest {
fun <A> ConcatenatedList<A>.checkInvariants() {
assertEquals(nestedIndexOffsets.size, source.size)
var currentOffset = 0
for (i in 0 .. source.size - 1) {
for (i in 0..source.size - 1) {
currentOffset += source[i].size
assertEquals(nestedIndexOffsets[i], currentOffset)
}

View File

@ -1,4 +1,4 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import javafx.beans.property.SimpleObjectProperty
import javafx.collections.FXCollections

View File

@ -1,10 +1,9 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import org.junit.Before
import org.junit.Test
import java.util.*
import kotlin.test.assertEquals
class LeftOuterJoinedMapTest {
@ -23,7 +22,7 @@ class LeftOuterJoinedMapTest {
dogs = FXCollections.observableArrayList<Dog>(Dog("Scruffy", owner = "Bob"))
joinedList = people.leftOuterJoin(dogs, Person::name, Dog::owner) { person, dogs -> Pair(person, dogs) }
// We replay the nested observable as well
replayedList = ReplayedList(joinedList.map { Pair(it.first, ReplayedList(it.second)) })
replayedList = ReplayedList(joinedList.map { Pair(it.first, ReplayedList(it.second)) })
}
// TODO perhaps these are too brittle because they test indices that are not stable. Use Expect dsl?

View File

@ -1,4 +1,4 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import javafx.collections.FXCollections
import javafx.collections.ObservableList

View File

@ -1,4 +1,4 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import javafx.collections.FXCollections
import org.junit.Before

View File

@ -1,4 +1,4 @@
package com.r3corda.client.fxutils
package net.corda.client.fxutils
import javafx.collections.MapChangeListener
import javafx.collections.ObservableMap

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<restrict>
<http>
<method>post</method>
<method>get</method>
</http>
<commands>
<command>read</command>
<command>list</command>
</commands>
<!-- allow anyone to force a garbage collection -->
<allow>
<mbean>
<name>java.lang:type=Memory</name>
<operation>gc</operation>
</mbean>
</allow>
<!-- in case we ever end up using c3pio connection pooling, this example from the docs prevents the password being exported -->
<deny>
<mbean>
<name>com.mchange.v2.c3p0:type=PooledDataSource,*</name>
<attribute>properties</attribute>
</mbean>
</deny>
</restrict>

View File

@ -4,7 +4,8 @@
<Properties>
<Property name="log-path">logs</Property>
<Property name="log-name">node-${hostName}</Property>
<Property name="archive">${log-path}/archive</Property>
<Property name="archive">${sys:log-path}/archive</Property>
<Property name="consoleLogLevel">error</Property>
</Properties>
<ThresholdFilter level="trace"/>
@ -21,7 +22,7 @@
<!-- Will generate up to 10 log files for a given day. During every rollover it will delete
those that are older than 60 days, but keep the most recent 10 GB -->
<RollingFile name="RollingFile-Appender"
fileName="${log-path}/${log-name}.log"
fileName="${sys:log-path}/${log-name}.log"
filePattern="${archive}/${log-name}.%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="[%-5level] %d{ISO8601}{GMT+0} [%t] %c{1} - %msg%n"/>
@ -47,13 +48,12 @@
<Loggers>
<Root level="info">
<AppenderRef ref="Console-Appender"/>
<AppenderRef ref="RollingFile-Appender"/>
<AppenderRef ref="Console-Appender" level="${sys:consoleLogLevel}"/>
<AppenderRef ref="RollingFile-Appender" level="info"/>
</Root>
<Logger name="com.r3corda" level="info" additivity="false">
<AppenderRef ref="Console-Appender"/>
<Logger name="net.corda" level="info" additivity="false">
<AppenderRef ref="Console-Appender" level="${sys:consoleLogLevel}"/>
<AppenderRef ref="RollingFile-Appender"/>
</Logger>
</Loggers>
</Configuration>

View File

@ -13,8 +13,8 @@
<Root level="info">
<AppenderRef ref="Console-Appender"/>
</Root>
<Logger name="com.r3corda" level="info" additivity="false">
<Logger name="net.corda" level="info" additivity="false">
<AppenderRef ref="Console-Appender"/>
</Logger>
</Loggers>
</Configuration>
</Configuration>

View File

@ -1,42 +0,0 @@
apply plugin: 'kotlin'
apply plugin: CanonicalizerPlugin
apply plugin: DefaultPublishTasks
repositories {
mavenLocal()
mavenCentral()
maven {
url 'http://oss.sonatype.org/content/repositories/snapshots'
}
jcenter()
maven {
url 'https://dl.bintray.com/kotlin/exposed'
}
}
dependencies {
compile project(':core')
testCompile project(':test-utils')
testCompile 'junit:junit:4.12'
}
sourceSets {
test {
resources {
srcDir "../config/test"
}
}
}
publishing {
publications {
contracts(MavenPublication) {
from components.java
artifactId 'contracts'
artifact sourceJar
artifact javadocJar
}
}
}

View File

@ -1,46 +0,0 @@
/*
* Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members
* pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms
* set forth therein.
*
* All other rights reserved.
*/
package com.r3corda.contracts.isolated
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.transactions.TransactionBuilder
import java.security.PublicKey
// The dummy contract doesn't do anything useful. It exists for testing purposes.
val ANOTHER_DUMMY_PROGRAM_ID = AnotherDummyContract()
class AnotherDummyContract : Contract, com.r3corda.core.node.DummyContractBackdoor {
data class State(val magicNumber: Int = 0) : ContractState {
override val contract = ANOTHER_DUMMY_PROGRAM_ID
override val participants: List<PublicKey>
get() = emptyList()
}
interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands
}
override fun verify(tx: TransactionForContract) {
// Always accepts.
}
// The "empty contract"
override val legalContractReference: SecureHash = SecureHash.sha256("https://anotherdummy.org")
override fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber)
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Create(), owner.party.owningKey))
}
override fun inspectState(state: ContractState): Int = (state as State).magicNumber
}

View File

@ -1,12 +0,0 @@
package com.r3corda.core.node
import com.r3corda.core.contracts.ContractState
import com.r3corda.core.contracts.PartyAndReference
import com.r3corda.core.transactions.TransactionBuilder
import com.r3corda.core.crypto.Party
interface DummyContractBackdoor {
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder
fun inspectState(state: ContractState): Int
}

View File

@ -1,87 +0,0 @@
package com.r3corda.contracts.testing
import com.pholser.junit.quickcheck.generator.GenerationStatus
import com.pholser.junit.quickcheck.generator.Generator
import com.pholser.junit.quickcheck.generator.java.util.ArrayListGenerator
import com.pholser.junit.quickcheck.random.SourceOfRandomness
import com.r3corda.contracts.asset.Cash
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.NullSignature
import com.r3corda.core.testing.*
import com.r3corda.core.transactions.SignedTransaction
import com.r3corda.core.transactions.WireTransaction
/**
* This file contains generators for quickcheck style testing. The idea is that we can write random instance generators
* for each type we have in the code and test against those instead of predefined mock data. This style of testing can
* catch corner case bugs and test algebraic properties of the code, for example deserialize(serialize(generatedThing)) == generatedThing
*
* TODO add combinators for easier Generator writing
*/
class ContractStateGenerator : Generator<ContractState>(ContractState::class.java) {
override fun generate(random: SourceOfRandomness, status: GenerationStatus): ContractState {
return Cash.State(
amount = AmountGenerator(IssuedGenerator(CurrencyGenerator())).generate(random, status),
owner = PublicKeyGenerator().generate(random, status)
)
}
}
class MoveGenerator : Generator<Cash.Commands.Move>(Cash.Commands.Move::class.java) {
override fun generate(random: SourceOfRandomness, status: GenerationStatus): Cash.Commands.Move {
return Cash.Commands.Move(SecureHashGenerator().generate(random, status))
}
}
class IssueGenerator : Generator<Cash.Commands.Issue>(Cash.Commands.Issue::class.java) {
override fun generate(random: SourceOfRandomness, status: GenerationStatus): Cash.Commands.Issue {
return Cash.Commands.Issue(random.nextLong())
}
}
class ExitGenerator : Generator<Cash.Commands.Exit>(Cash.Commands.Exit::class.java) {
override fun generate(random: SourceOfRandomness, status: GenerationStatus): Cash.Commands.Exit {
return Cash.Commands.Exit(AmountGenerator(IssuedGenerator(CurrencyGenerator())).generate(random, status))
}
}
class CommandDataGenerator : Generator<CommandData>(CommandData::class.java) {
override fun generate(random: SourceOfRandomness, status: GenerationStatus): CommandData {
val generators = listOf(MoveGenerator(), IssueGenerator(), ExitGenerator())
return generators[random.nextInt(0, generators.size - 1)].generate(random, status)
}
}
class CommandGenerator : Generator<Command>(Command::class.java) {
override fun generate(random: SourceOfRandomness, status: GenerationStatus): Command {
val signersGenerator = ArrayListGenerator()
signersGenerator.addComponentGenerators(listOf(PublicKeyGenerator()))
return Command(CommandDataGenerator().generate(random, status), PublicKeyGenerator().generate(random, status))
}
}
class WiredTransactionGenerator: Generator<WireTransaction>(WireTransaction::class.java) {
override fun generate(random: SourceOfRandomness, status: GenerationStatus): WireTransaction {
val commands = CommandGenerator().generateList(random, status) + listOf(CommandGenerator().generate(random, status))
return WireTransaction(
inputs = StateRefGenerator().generateList(random, status),
attachments = SecureHashGenerator().generateList(random, status),
outputs = TransactionStateGenerator(ContractStateGenerator()).generateList(random, status),
commands = commands,
notary = PartyGenerator().generate(random, status),
signers = commands.flatMap { it.signers },
type = TransactionType.General(),
timestamp = TimestampGenerator().generate(random, status)
)
}
}
class SignedTransactionGenerator: Generator<SignedTransaction>(SignedTransaction::class.java) {
override fun generate(random: SourceOfRandomness, status: GenerationStatus): SignedTransaction {
val wireTransaction = WiredTransactionGenerator().generate(random, status)
return SignedTransaction(
txBits = wireTransaction.serialized,
sigs = listOf(NullSignature)
)
}
}

View File

@ -1,265 +0,0 @@
package com.r3corda.protocols
import co.paralleluniverse.fibers.Suspendable
import com.r3corda.contracts.asset.Cash
import com.r3corda.contracts.asset.sumCashBy
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.DigitalSignature
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.signWithECDSA
import com.r3corda.core.node.NodeInfo
import com.r3corda.core.node.services.ServiceType
import com.r3corda.core.protocols.ProtocolLogic
import com.r3corda.core.seconds
import com.r3corda.core.transactions.SignedTransaction
import com.r3corda.core.transactions.TransactionBuilder
import com.r3corda.core.transactions.WireTransaction
import com.r3corda.core.utilities.ProgressTracker
import com.r3corda.core.utilities.trace
import java.security.KeyPair
import java.security.PublicKey
import java.util.*
/**
* This asset trading protocol implements a "delivery vs payment" type swap. It has two parties (B and S for buyer
* and seller) and the following steps:
*
* 1. S sends the [StateAndRef] pointing to what they want to sell to B, along with info about the price they require
* B to pay. For example this has probably been agreed on an exchange.
* 2. B sends to S a [SignedTransaction] that includes the state as input, B's cash as input, the state with the new
* owner key as output, and any change cash as output. It contains a single signature from B but isn't valid because
* it lacks a signature from S authorising movement of the asset.
* 3. S signs it and hands the now finalised SignedWireTransaction back to B.
*
* Assuming no malicious termination, they both end the protocol being in posession of a valid, signed transaction
* that represents an atomic asset swap.
*
* Note that it's the *seller* who initiates contact with the buyer, not vice-versa as you might imagine.
*
* To initiate the protocol, use either the [runBuyer] or [runSeller] methods, depending on which side of the trade
* your node is taking. These methods return a future which will complete once the trade is over and a fully signed
* transaction is available: you can either block your thread waiting for the protocol to complete by using
* [ListenableFuture.get] or more usefully, register a callback that will be invoked when the time comes.
*
* To see an example of how to use this class, look at the unit tests.
*/
// TODO: Common elements in multi-party transaction consensus and signing should be refactored into a superclass of this
// and [AbstractStateReplacementProtocol].
object TwoPartyTradeProtocol {
class UnacceptablePriceException(val givenPrice: Amount<Currency>) : Exception("Unacceptable price: $givenPrice")
class AssetMismatchException(val expectedTypeName: String, val typeName: String) : Exception() {
override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName"
}
// This object is serialised to the network and is the first protocol message the seller sends to the buyer.
data class SellerTradeInfo(
val assetForSale: StateAndRef<OwnableState>,
val price: Amount<Currency>,
val sellerOwnerKey: PublicKey
)
data class SignaturesFromSeller(val sellerSig: DigitalSignature.WithKey,
val notarySig: DigitalSignature.LegallyIdentifiable)
open class Seller(val otherParty: Party,
val notaryNode: NodeInfo,
val assetToSell: StateAndRef<OwnableState>,
val price: Amount<Currency>,
val myKeyPair: KeyPair,
override val progressTracker: ProgressTracker = Seller.tracker()) : ProtocolLogic<SignedTransaction>() {
companion object {
object AWAITING_PROPOSAL : ProgressTracker.Step("Awaiting transaction proposal")
object VERIFYING : ProgressTracker.Step("Verifying transaction proposal")
object SIGNING : ProgressTracker.Step("Signing transaction")
object NOTARY : ProgressTracker.Step("Getting notary signature")
object SENDING_SIGS : ProgressTracker.Step("Sending transaction signatures to buyer")
fun tracker() = ProgressTracker(AWAITING_PROPOSAL, VERIFYING, SIGNING, NOTARY, SENDING_SIGS)
}
@Suspendable
override fun call(): SignedTransaction {
val partialTX: SignedTransaction = receiveAndCheckProposedTransaction()
// These two steps could be done in parallel, in theory. Our framework doesn't support that yet though.
val ourSignature = calculateOurSignature(partialTX)
val allPartySignedTx = partialTX + ourSignature
val notarySignature = getNotarySignature(allPartySignedTx)
return sendSignatures(allPartySignedTx, ourSignature, notarySignature)
}
@Suspendable
private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.LegallyIdentifiable {
progressTracker.currentStep = NOTARY
return subProtocol(NotaryProtocol.Client(stx))
}
@Suspendable
private fun receiveAndCheckProposedTransaction(): SignedTransaction {
progressTracker.currentStep = AWAITING_PROPOSAL
// Make the first message we'll send to kick off the protocol.
val hello = SellerTradeInfo(assetToSell, price, myKeyPair.public)
val maybeSTX = sendAndReceive<SignedTransaction>(otherParty, hello)
progressTracker.currentStep = VERIFYING
maybeSTX.unwrap {
progressTracker.nextStep()
// Check that the tx proposed by the buyer is valid.
val wtx: WireTransaction = it.verifySignatures(myKeyPair.public, notaryNode.notaryIdentity.owningKey)
logger.trace { "Received partially signed transaction: ${it.id}" }
// Download and check all the things that this transaction depends on and verify it is contract-valid,
// even though it is missing signatures.
subProtocol(ResolveTransactionsProtocol(wtx, otherParty))
if (wtx.outputs.map { it.data }.sumCashBy(myKeyPair.public).withoutIssuer() != price)
throw IllegalArgumentException("Transaction is not sending us the right amount of cash")
// There are all sorts of funny games a malicious secondary might play here, we should fix them:
//
// - This tx may attempt to send some assets we aren't intending to sell to the secondary, if
// we're reusing keys! So don't reuse keys!
// - This tx may include output states that impose odd conditions on the movement of the cash,
// once we implement state pairing.
//
// but the goal of this code is not to be fully secure (yet), but rather, just to find good ways to
// express protocol state machines on top of the messaging layer.
return it
}
}
open fun calculateOurSignature(partialTX: SignedTransaction): DigitalSignature.WithKey {
progressTracker.currentStep = SIGNING
return myKeyPair.signWithECDSA(partialTX.txBits)
}
@Suspendable
private fun sendSignatures(allPartySignedTx: SignedTransaction, ourSignature: DigitalSignature.WithKey,
notarySignature: DigitalSignature.LegallyIdentifiable): SignedTransaction {
progressTracker.currentStep = SENDING_SIGS
val fullySigned = allPartySignedTx + notarySignature
logger.trace { "Built finished transaction, sending back to secondary!" }
send(otherParty, SignaturesFromSeller(ourSignature, notarySignature))
return fullySigned
}
}
open class Buyer(val otherParty: Party,
val notary: Party,
val acceptablePrice: Amount<Currency>,
val typeToBuy: Class<out OwnableState>) : ProtocolLogic<SignedTransaction>() {
object RECEIVING : ProgressTracker.Step("Waiting for seller trading info")
object VERIFYING : ProgressTracker.Step("Verifying seller assets")
object SIGNING : ProgressTracker.Step("Generating and signing transaction proposal")
object SWAPPING_SIGNATURES : ProgressTracker.Step("Swapping signatures with the seller")
override val progressTracker = ProgressTracker(RECEIVING, VERIFYING, SIGNING, SWAPPING_SIGNATURES)
@Suspendable
override fun call(): SignedTransaction {
val tradeRequest = receiveAndValidateTradeRequest()
progressTracker.currentStep = SIGNING
val (ptx, cashSigningPubKeys) = assembleSharedTX(tradeRequest)
val stx = signWithOurKeys(cashSigningPubKeys, ptx)
val signatures = swapSignaturesWithSeller(stx)
logger.trace { "Got signatures from seller, verifying ... " }
val fullySigned = stx + signatures.sellerSig + signatures.notarySig
fullySigned.verifySignatures()
logger.trace { "Signatures received are valid. Trade complete! :-)" }
return fullySigned
}
@Suspendable
private fun receiveAndValidateTradeRequest(): SellerTradeInfo {
progressTracker.currentStep = RECEIVING
// Wait for a trade request to come in from the other side
val maybeTradeRequest = receive<SellerTradeInfo>(otherParty)
progressTracker.currentStep = VERIFYING
maybeTradeRequest.unwrap {
// What is the seller trying to sell us?
val asset = it.assetForSale.state.data
val assetTypeName = asset.javaClass.name
logger.trace { "Got trade request for a $assetTypeName: ${it.assetForSale}" }
if (it.price > acceptablePrice)
throw UnacceptablePriceException(it.price)
if (!typeToBuy.isInstance(asset))
throw AssetMismatchException(typeToBuy.name, assetTypeName)
// Check the transaction that contains the state which is being resolved.
// We only have a hash here, so if we don't know it already, we have to ask for it.
subProtocol(ResolveTransactionsProtocol(setOf(it.assetForSale.ref.txhash), otherParty))
return it
}
}
@Suspendable
private fun swapSignaturesWithSeller(stx: SignedTransaction): SignaturesFromSeller {
progressTracker.currentStep = SWAPPING_SIGNATURES
logger.trace { "Sending partially signed transaction to seller" }
// TODO: Protect against the seller terminating here and leaving us in the lurch without the final tx.
return sendAndReceive<SignaturesFromSeller>(otherParty, stx).unwrap { it }
}
private fun signWithOurKeys(cashSigningPubKeys: List<PublicKey>, ptx: TransactionBuilder): SignedTransaction {
// Now sign the transaction with whatever keys we need to move the cash.
for (k in cashSigningPubKeys) {
val priv = serviceHub.keyManagementService.toPrivate(k)
ptx.signWith(KeyPair(k, priv))
}
return ptx.toSignedTransaction(checkSufficientSignatures = false)
}
private fun assembleSharedTX(tradeRequest: SellerTradeInfo): Pair<TransactionBuilder, List<PublicKey>> {
val ptx = TransactionType.General.Builder(notary)
// Add input and output states for the movement of cash, by using the Cash contract to generate the states
val (tx, cashSigningPubKeys) = serviceHub.vaultService.generateSpend(ptx, tradeRequest.price, tradeRequest.sellerOwnerKey)
// Add inputs/outputs/a command for the movement of the asset.
tx.addInputState(tradeRequest.assetForSale)
// Just pick some new public key for now. This won't be linked with our identity in any way, which is what
// we want for privacy reasons: the key is here ONLY to manage and control ownership, it is not intended to
// reveal who the owner actually is. The key management service is expected to derive a unique key from some
// initial seed in order to provide privacy protection.
val freshKey = serviceHub.keyManagementService.freshKey()
val (command, state) = tradeRequest.assetForSale.state.data.withNewOwner(freshKey.public)
tx.addOutputState(state, tradeRequest.assetForSale.state.notary)
tx.addCommand(command, tradeRequest.assetForSale.state.data.owner)
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
// to have one.
val currentTime = serviceHub.clock.instant()
tx.setTime(currentTime, 30.seconds)
return Pair(tx, cashSigningPubKeys)
}
}
}

View File

@ -1,7 +1,6 @@
apply plugin: 'kotlin'
apply plugin: QuasarPlugin
// Applying the maven plugin means this will get installed locally when running "gradle install"
apply plugin: DefaultPublishTasks
apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.publish-utils'
buildscript {
repositories {
@ -44,6 +43,7 @@ dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "org.jetbrains.kotlinx:kotlinx-support-jdk8:0.2"
compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
// Thread safety annotations
@ -85,6 +85,9 @@ dependencies {
// JPA 2.1 annotations.
compile "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final"
// RS API: Response type and codes for ApiUtils.
compile "javax.ws.rs:javax.ws.rs-api:2.0"
}
publishing {
@ -97,6 +100,4 @@ publishing {
artifact javadocJar
}
}
}
quasarScan.dependsOn('classes')
}

View File

@ -1,4 +1,4 @@
package com.r3corda.core.crypto;
package net.corda.core.crypto;
public class AddressFormatException extends IllegalArgumentException {
public AddressFormatException() {

View File

@ -1,4 +1,4 @@
package com.r3corda.core.crypto;
package net.corda.core.crypto;
import java.math.*;
import java.util.*;
@ -128,6 +128,7 @@ public class Base58 {
* removed from the returned data.
*
* @param input the base58-encoded string to decode (which should include the checksum)
* @return the original data bytes less the last 4 bytes (the checksum).
* @throws AddressFormatException if the input is not base 58 or the checksum does not validate.
*/
public static byte[] decodeChecked(String input) throws AddressFormatException {
@ -136,7 +137,7 @@ public class Base58 {
throw new AddressFormatException("Input too short");
byte[] data = Arrays.copyOfRange(decoded, 0, decoded.length - 4);
byte[] checksum = Arrays.copyOfRange(decoded, decoded.length - 4, decoded.length);
byte[] actualChecksum = Arrays.copyOfRange(SecureHash.sha256Twice(data).getBits(), 0, 4);
byte[] actualChecksum = Arrays.copyOfRange(SecureHash.sha256Twice(data).getBytes(), 0, 4);
if (!Arrays.equals(checksum, actualChecksum))
throw new AddressFormatException("Checksum does not validate");
return data;

View File

@ -1,298 +0,0 @@
package com.r3corda.core
import com.google.common.base.Function
import com.google.common.base.Throwables
import com.google.common.io.ByteStreams
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors
import com.google.common.util.concurrent.SettableFuture
import com.r3corda.core.crypto.newSecureRandom
import org.slf4j.Logger
import rx.Observable
import rx.subjects.UnicastSubject
import java.io.BufferedInputStream
import java.io.InputStream
import java.math.BigDecimal
import java.nio.file.Files
import java.nio.file.LinkOption
import java.nio.file.Path
import java.time.Duration
import java.time.temporal.Temporal
import java.util.concurrent.ExecutionException
import java.util.concurrent.Executor
import java.util.concurrent.locks.ReentrantLock
import java.util.zip.ZipInputStream
import kotlin.concurrent.withLock
import kotlin.reflect.KProperty
val Int.days: Duration get() = Duration.ofDays(this.toLong())
@Suppress("unused") // It's here for completeness
val Int.hours: Duration get() = Duration.ofHours(this.toLong())
val Int.minutes: Duration get() = Duration.ofMinutes(this.toLong())
val Int.seconds: Duration get() = Duration.ofSeconds(this.toLong())
// TODO: Review by EOY2016 if we ever found these utilities helpful.
val Int.bd: BigDecimal get() = BigDecimal(this)
val Double.bd: BigDecimal get() = BigDecimal(this)
val String.bd: BigDecimal get() = BigDecimal(this)
val Long.bd: BigDecimal get() = BigDecimal(this)
fun String.abbreviate(maxWidth: Int): String = if (length <= maxWidth) this else take(maxWidth - 1) + ""
/** Like the + operator but throws an exception in case of integer overflow. */
infix fun Int.checkedAdd(b: Int) = Math.addExact(this, b)
/** Like the + operator but throws an exception in case of integer overflow. */
@Suppress("unused")
infix fun Long.checkedAdd(b: Long) = Math.addExact(this, b)
/**
* Returns a random positive long generated using a secure RNG. This function sacrifies a bit of entropy in order to
* avoid potential bugs where the value is used in a context where negative numbers are not expected.
*/
fun random63BitValue(): Long = Math.abs(newSecureRandom().nextLong())
// Some utilities for working with Guava listenable futures.
fun <T> ListenableFuture<T>.then(executor: Executor, body: () -> Unit) = addListener(Runnable(body), executor)
fun <T> ListenableFuture<T>.success(executor: Executor, body: (T) -> Unit) = then(executor) {
val r = try {
get()
} catch(e: Throwable) {
return@then
}
body(r)
}
fun <T> ListenableFuture<T>.failure(executor: Executor, body: (Throwable) -> Unit) = then(executor) {
try {
get()
} catch (e: ExecutionException) {
body(e.cause!!)
} catch (t: Throwable) {
body(t)
}
}
infix fun <T> ListenableFuture<T>.then(body: () -> Unit): ListenableFuture<T> = apply { then(RunOnCallerThread, body) }
infix fun <T> ListenableFuture<T>.success(body: (T) -> Unit): ListenableFuture<T> = apply { success(RunOnCallerThread, body) }
infix fun <T> ListenableFuture<T>.failure(body: (Throwable) -> Unit): ListenableFuture<T> = apply { failure(RunOnCallerThread, body) }
infix fun <F, T> ListenableFuture<F>.map(mapper: (F) -> T): ListenableFuture<T> = Futures.transform(this, Function { mapper(it!!) })
infix fun <F, T> ListenableFuture<F>.flatMap(mapper: (F) -> ListenableFuture<T>): ListenableFuture<T> = Futures.transformAsync(this) { mapper(it!!) }
/** Executes the given block and sets the future to either the result, or any exception that was thrown. */
inline fun <T> SettableFuture<T>.catch(block: () -> T) {
try {
set(block())
} catch (t: Throwable) {
setException(t)
}
}
fun <R> Path.use(block: (InputStream) -> R): R = Files.newInputStream(this).use(block)
fun Path.exists(vararg options: LinkOption): Boolean = Files.exists(this, *options)
// Simple infix function to add back null safety that the JDK lacks: timeA until timeB
infix fun Temporal.until(endExclusive: Temporal) = Duration.between(this, endExclusive)
/** Returns the index of the given item or throws [IllegalArgumentException] if not found. */
fun <T> List<T>.indexOfOrThrow(item: T): Int {
val i = indexOf(item)
require(i != -1)
return i
}
/**
* Returns the single element matching the given [predicate], or `null` if element was not found,
* or throws if more than one element was found.
*/
fun <T> Iterable<T>.noneOrSingle(predicate: (T) -> Boolean): T? {
var single: T? = null
for (element in this) {
if (predicate(element)) {
if (single == null) {
single = element
} else throw IllegalArgumentException("Collection contains more than one matching element.")
}
}
return single
}
/** Returns single element, or `null` if element was not found, or throws if more than one element was found. */
fun <T> Iterable<T>.noneOrSingle(): T? {
var single: T? = null
for (element in this) {
if (single == null) {
single = element
} else throw IllegalArgumentException("Collection contains more than one matching element.")
}
return single
}
// An alias that can sometimes make code clearer to read.
val RunOnCallerThread = MoreExecutors.directExecutor()
// TODO: Add inline back when a new Kotlin version is released and check if the java.lang.VerifyError
// returns in the IRSSimulationTest. If not, commit the inline back.
fun <T> logElapsedTime(label: String, logger: Logger? = null, body: () -> T): T {
val now = System.currentTimeMillis()
val r = body()
val elapsed = System.currentTimeMillis() - now
if (logger != null)
logger.info("$label took $elapsed msec")
else
println("$label took $elapsed msec")
return r
}
/**
* A threadbox is a simple utility that makes it harder to forget to take a lock before accessing some shared state.
* Simply define a private class to hold the data that must be grouped under the same lock, and then pass the only
* instance to the ThreadBox constructor. You can now use the [locked] method with a lambda to take the lock in a
* way that ensures it'll be released if there's an exception.
*
* Note that this technique is not infallible: if you capture a reference to the fields in another lambda which then
* gets stored and invoked later, there may still be unsafe multi-threaded access going on, so watch out for that.
* This is just a simple guard rail that makes it harder to slip up.
*
* Example:
*
* private class MutableState { var i = 5 }
* private val state = ThreadBox(MutableState())
*
* val ii = state.locked { i }
*/
class ThreadBox<out T>(val content: T, val lock: ReentrantLock = ReentrantLock()) {
inline fun <R> locked(body: T.() -> R): R = lock.withLock { body(content) }
inline fun <R> alreadyLocked(body: T.() -> R): R {
check(lock.isHeldByCurrentThread, { "Expected $lock to already be locked." })
return body(content)
}
fun checkNotLocked() = check(!lock.isHeldByCurrentThread)
}
/**
* This represents a transient exception or condition that might no longer be thrown if the operation is re-run or called
* again.
*
* We avoid the use of the word transient here to hopefully reduce confusion with the term in relation to (Java) serialization.
*/
abstract class RetryableException(message: String) : Exception(message)
/**
* A simple wrapper that enables the use of Kotlin's "val x by TransientProperty { ... }" syntax. Such a property
* will not be serialized to disk, and if it's missing (or the first time it's accessed), the initializer will be
* used to set it up. Note that the initializer will be called with the TransientProperty object locked.
*/
class TransientProperty<out T>(private val initializer: () -> T) {
@Transient private var v: T? = null
@Synchronized
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
if (v == null)
v = initializer()
return v!!
}
}
/**
* Given a path to a zip file, extracts it to the given directory.
*/
fun extractZipFile(zipPath: Path, toPath: Path) {
val normalisedToPath = toPath.normalize()
if (!Files.exists(normalisedToPath))
Files.createDirectories(normalisedToPath)
ZipInputStream(BufferedInputStream(Files.newInputStream(zipPath))).use { zip ->
while (true) {
val e = zip.nextEntry ?: break
val outPath = normalisedToPath.resolve(e.name)
// Security checks: we should reject a zip that contains tricksy paths that try to escape toPath.
if (!outPath.normalize().startsWith(normalisedToPath))
throw IllegalStateException("ZIP contained a path that resolved incorrectly: ${e.name}")
if (e.isDirectory) {
Files.createDirectories(outPath)
continue
}
Files.newOutputStream(outPath).use { out ->
ByteStreams.copy(zip, out)
}
zip.closeEntry()
}
}
}
// TODO: Generic csv printing utility for clases.
val Throwable.rootCause: Throwable get() = Throwables.getRootCause(this)
/** Allows you to write code like: Paths.get("someDir") / "subdir" / "filename" but using the Paths API to avoid platform separator problems. */
operator fun Path.div(other: String): Path = resolve(other)
/** Representation of an operation that may have thrown an error. */
data class ErrorOr<out A> private constructor(val value: A?, val error: Throwable?) {
constructor(value: A) : this(value, null)
companion object {
/** Runs the given lambda and wraps the result. */
inline fun <T> catch(body: () -> T): ErrorOr<T> = try { ErrorOr(body()) } catch (t: Throwable) { ErrorOr.of(t) }
fun of(t: Throwable) = ErrorOr(null, t)
}
fun <T> match(onValue: (A) -> T, onError: (Throwable) -> T): T {
if (value != null) {
return onValue(value)
} else {
return onError(error!!)
}
}
fun getOrThrow(): A {
if (value != null) {
return value
} else {
throw error!!
}
}
// Functor
fun <B> map(function: (A) -> B) = ErrorOr(value?.let(function), error)
// Applicative
fun <B, C> combine(other: ErrorOr<B>, function: (A, B) -> C): ErrorOr<C> {
return ErrorOr(value?.let { a -> other.value?.let { b -> function(a, b) } }, error ?: other.error)
}
// Monad
fun <B> bind(function: (A) -> ErrorOr<B>) = value?.let(function) ?: ErrorOr.of(error!!)
}
/**
* Returns an observable that buffers events until subscribed.
*
* @see UnicastSubject
*/
fun <T> Observable<T>.bufferUntilSubscribed(): Observable<T> {
val subject = UnicastSubject.create<T>()
val subscription = subscribe(subject)
return subject.doOnUnsubscribe { subscription.unsubscribe() }
}
/**
* Determine if an iterable data type's contents are ordered and unique, based on their [Comparable].compareTo
* function.
*/
fun <T, I: Comparable<I>> Iterable<T>.isOrderedAndUnique(extractId: T.() -> I): Boolean {
var last: I? = null
return all { it ->
val lastLast = last
last = extractId(it)
if (lastLast == null) {
true
} else {
lastLast < extractId(it)
}
}
}

View File

@ -1,49 +0,0 @@
package com.r3corda.core.contracts
import com.r3corda.core.crypto.Party
import com.r3corda.core.serialization.OpaqueBytes
import java.security.PublicKey
import java.util.*
/**
* A command from the monitoring client, to the node.
*
* @param id ID used to tag event(s) resulting from a command.
*/
sealed class ClientToServiceCommand(val id: UUID) {
/**
* Issue cash state objects.
*
* @param amount the amount of currency to issue on to the ledger.
* @param issueRef the reference to specify on the issuance, used to differentiate pools of cash. Convention is
* to use the single byte "0x01" as a default.
* @param recipient the party to issue the cash to.
* @param notary the notary to use for this transaction.
* @param id the ID to be provided in events resulting from this request.
*/
class IssueCash(val amount: Amount<Currency>,
val issueRef: OpaqueBytes,
val recipient: Party,
val notary: Party,
id: UUID = UUID.randomUUID()) : ClientToServiceCommand(id)
/**
* Pay cash to someone else.
*
* @param amount the amount of currency to issue on to the ledger.
* @param recipient the party to issue the cash to.
* @param id the ID to be provided in events resulting from this request.
*/
class PayCash(val amount: Amount<Issued<Currency>>, val recipient: Party,
id: UUID = UUID.randomUUID()) : ClientToServiceCommand(id)
/**
* Exit cash from the ledger.
*
* @param amount the amount of currency to exit from the ledger.
* @param issueRef the reference previously specified on the issuance.
* @param id the ID to be provided in events resulting from this request.
*/
class ExitCash(val amount: Amount<Currency>, val issueRef: OpaqueBytes,
id: UUID = UUID.randomUUID()) : ClientToServiceCommand(id)
}

View File

@ -1,69 +0,0 @@
package com.r3corda.core.contracts
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.transactions.TransactionBuilder
import java.security.PublicKey
// The dummy contract doesn't do anything useful. It exists for testing purposes.
val DUMMY_PROGRAM_ID = DummyContract()
class DummyContract : Contract {
interface State : ContractState {
val magicNumber: Int
}
data class SingleOwnerState(override val magicNumber: Int = 0, override val owner: PublicKey) : OwnableState, State {
override val contract = DUMMY_PROGRAM_ID
override val participants: List<PublicKey>
get() = listOf(owner)
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
}
/**
* Alternative state with multiple owners. This exists primarily to provide a dummy state with multiple
* participants, and could in theory be merged with [SingleOwnerState] by putting the additional participants
* in a different field, however this is a good example of a contract with multiple states.
*/
data class MultiOwnerState(override val magicNumber: Int = 0,
val owners: List<PublicKey>) : ContractState, State {
override val contract = DUMMY_PROGRAM_ID
override val participants: List<PublicKey>
get() = owners
}
interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands
class Move : TypeOnlyCommandData(), Commands
}
override fun verify(tx: TransactionForContract) {
// Always accepts.
}
// The "empty contract"
override val legalContractReference: SecureHash = SecureHash.sha256("")
companion object {
@JvmStatic
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = SingleOwnerState(magicNumber, owner.party.owningKey)
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Create(), owner.party.owningKey))
}
fun move(prior: StateAndRef<DummyContract.SingleOwnerState>, newOwner: PublicKey) = move(listOf(prior), newOwner)
fun move(priors: List<StateAndRef<DummyContract.SingleOwnerState>>, newOwner: PublicKey): TransactionBuilder {
require(priors.size > 0)
val priorState = priors[0].state.data
val (cmd, state) = priorState.withNewOwner(newOwner)
return TransactionType.General.Builder(notary = priors[0].state.notary).withItems(
/* INPUTS */ *priors.toTypedArray(),
/* COMMAND */ Command(cmd, priorState.owner),
/* OUTPUT */ state
)
}
}
}

View File

@ -1,12 +0,0 @@
package com.r3corda.core.contracts
import java.security.PublicKey
/**
* Dummy state for use in testing. Not part of any contract, not even the [DummyContract].
*/
data class DummyState(val magicNumber: Int = 0) : ContractState {
override val contract = DUMMY_PROGRAM_ID
override val participants: List<PublicKey>
get() = emptyList()
}

View File

@ -1,145 +0,0 @@
package com.r3corda.core.contracts
import com.r3corda.core.crypto.Party
import com.r3corda.core.transactions.LedgerTransaction
import com.r3corda.core.transactions.TransactionBuilder
import java.security.PublicKey
/** Defines transaction build & validation logic for a specific transaction type */
sealed class TransactionType {
override fun equals(other: Any?) = other?.javaClass == javaClass
override fun hashCode() = javaClass.name.hashCode()
/**
* Check that the transaction is valid based on:
* - General platform rules
* - Rules for the specific transaction type
*
* Note: Presence of _signatures_ is not checked, only the public keys to be signed for.
*/
fun verify(tx: LedgerTransaction) {
require(tx.notary != null || tx.timestamp == null) { "Transactions with timestamps must be notarised." }
val missing = verifySigners(tx)
if (missing.isNotEmpty()) throw TransactionVerificationException.SignersMissing(tx, missing.toList())
verifyTransaction(tx)
}
/** Check that the list of signers includes all the necessary keys */
fun verifySigners(tx: LedgerTransaction): Set<PublicKey> {
val notaryKey = tx.inputs.map { it.state.notary.owningKey }.toSet()
if (notaryKey.size > 1) throw TransactionVerificationException.MoreThanOneNotary(tx)
val requiredKeys = getRequiredSigners(tx) + notaryKey
val missing = requiredKeys - tx.mustSign
return missing
}
/**
* Return the list of public keys that that require signatures for the transaction type.
* Note: the notary key is checked separately for all transactions and need not be included.
*/
abstract fun getRequiredSigners(tx: LedgerTransaction): Set<PublicKey>
/** Implement type specific transaction validation logic */
abstract fun verifyTransaction(tx: LedgerTransaction)
/** A general transaction type where transaction validity is determined by custom contract code */
class General : TransactionType() {
/** Just uses the default [TransactionBuilder] with no special logic */
class Builder(notary: Party?) : TransactionBuilder(General(), notary) {}
/**
* Check the transaction is contract-valid by running the verify() for each input and output state contract.
* If any contract fails to verify, the whole transaction is considered to be invalid.
*/
override fun verifyTransaction(tx: LedgerTransaction) {
// Make sure the notary has stayed the same. As we can't tell how inputs and outputs connect, if there
// are any inputs, all outputs must have the same notary.
// TODO: Is that the correct set of restrictions? May need to come back to this, see if we can be more
// flexible on output notaries.
if (tx.notary != null
&& tx.inputs.isNotEmpty()) {
tx.outputs.forEach {
if (it.notary != tx.notary) {
throw TransactionVerificationException.NotaryChangeInWrongTransactionType(tx, it.notary)
}
}
}
val ctx = tx.toTransactionForContract()
// TODO: This will all be replaced in future once the sandbox and contract constraints work is done.
val contracts = (ctx.inputs.map { it.contract } + ctx.outputs.map { it.contract }).toSet()
for (contract in contracts) {
try {
contract.verify(ctx)
} catch(e: Throwable) {
throw TransactionVerificationException.ContractRejection(tx, contract, e)
}
}
// Validate that all encumbrances exist within the set of input states.
tx.inputs.filter { it.state.data.encumbrance != null }.forEach {
encumberedInput ->
if (tx.inputs.none { it.ref.txhash == encumberedInput.ref.txhash &&
it.ref.index == encumberedInput.state.data.encumbrance }) {
throw TransactionVerificationException.TransactionMissingEncumbranceException(
tx, encumberedInput.state.data.encumbrance!!,
TransactionVerificationException.Direction.INPUT
)
}
}
// Check that, in the outputs, an encumbered state does not refer to itself as the encumbrance,
// and that the number of outputs can contain the encumbrance.
for ((i, output) in tx.outputs.withIndex() ) {
val encumbranceIndex = output.data.encumbrance ?: continue
if (encumbranceIndex == i || encumbranceIndex >= tx.outputs.size) {
throw TransactionVerificationException.TransactionMissingEncumbranceException(
tx, encumbranceIndex,
TransactionVerificationException.Direction.OUTPUT)
}
}
}
override fun getRequiredSigners(tx: LedgerTransaction) = tx.commands.flatMap { it.signers }.toSet()
}
/**
* A special transaction type for reassigning a notary for a state. Validation does not involve running
* any contract code, it just checks that the states are unmodified apart from the notary field.
*/
class NotaryChange : TransactionType() {
/**
* A transaction builder that automatically sets the transaction type to [NotaryChange]
* and adds the list of participants to the signers set for every input state.
*/
class Builder(notary: Party) : TransactionBuilder(NotaryChange(), notary) {
override fun addInputState(stateAndRef: StateAndRef<*>) {
signers.addAll(stateAndRef.state.data.participants)
super.addInputState(stateAndRef)
}
}
/**
* Check that the difference between inputs and outputs is only the notary field, and that all required signing
* public keys are present.
*
* @throws TransactionVerificationException.InvalidNotaryChange if the validity check fails.
*/
override fun verifyTransaction(tx: LedgerTransaction) {
try {
for ((input, output) in tx.inputs.zip(tx.outputs)) {
check(input.state.data == output.data)
check(input.state.notary != output.notary)
}
check(tx.commands.isEmpty())
} catch (e: IllegalStateException) {
throw TransactionVerificationException.InvalidNotaryChange(tx)
}
}
override fun getRequiredSigners(tx: LedgerTransaction) = tx.inputs.flatMap { it.state.data.participants }.toSet()
}
}

View File

@ -1,25 +0,0 @@
@file:JvmName("ClauseVerifier")
package com.r3corda.core.contracts.clauses
import com.r3corda.core.contracts.*
/**
* Verify a transaction against the given list of clauses.
*
* @param tx transaction to be verified.
* @param clauses the clauses to verify.
* @param commands commands extracted from the transaction, which are relevant to the
* clauses.
*/
fun <C: CommandData> verifyClause(tx: TransactionForContract,
clause: Clause<ContractState, C, Unit>,
commands: List<AuthenticatedObject<C>>) {
if (Clause.log.isTraceEnabled) {
clause.getExecutionPath(commands).forEach {
Clause.log.trace("Tx ${tx.origHash} clause: ${clause}")
}
}
val matchedCommands = clause.verify(tx, tx.inputs, tx.outputs, commands, null)
check(matchedCommands.containsAll(commands.map { it.value })) { "The following commands were not matched at the end of execution: " + (commands - matchedCommands) }
}

View File

@ -1,17 +0,0 @@
package com.r3corda.core.contracts.clauses
import com.r3corda.core.contracts.AuthenticatedObject
import com.r3corda.core.contracts.CommandData
import com.r3corda.core.contracts.ContractState
/**
* Abstract supertype for clauses which compose other clauses together in some logical manner.
*/
abstract class CompositeClause<in S : ContractState, C: CommandData, in K : Any>: Clause<S, C, K>() {
/** List of clauses under this composite clause */
abstract val clauses: List<Clause<S, C, K>>
override fun getExecutionPath(commands: List<AuthenticatedObject<C>>): List<Clause<*, *, *>>
= matchedClauses(commands).flatMap { it.getExecutionPath(commands) }
/** Determine which clauses are matched by the supplied commands */
abstract fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>>
}

View File

@ -1,129 +0,0 @@
package com.r3corda.core.crypto
import com.r3corda.core.serialization.OpaqueBytes
import net.i2p.crypto.eddsa.EdDSAEngine
import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey
import net.i2p.crypto.eddsa.KeyPairGenerator
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
import java.math.BigInteger
import java.security.*
fun newSecureRandom(): SecureRandom {
if (System.getProperty("os.name") == "Linux") {
return SecureRandom.getInstance("NativePRNGNonBlocking")
} else {
return SecureRandom.getInstanceStrong()
}
}
/**
* A wrapper around a digital signature. The covering field is a generic tag usable by whatever is interpreting the
* signature. It isn't used currently, but experience from Bitcoin suggests such a feature is useful, especially when
* building partially signed transactions.
*/
open class DigitalSignature(bits: ByteArray) : OpaqueBytes(bits) {
/** A digital signature that identifies who the public key is owned by. */
open class WithKey(val by: PublicKey, bits: ByteArray) : DigitalSignature(bits) {
fun verifyWithECDSA(content: ByteArray) = by.verifyWithECDSA(content, this)
fun verifyWithECDSA(content: OpaqueBytes) = by.verifyWithECDSA(content.bits, this)
}
class LegallyIdentifiable(val signer: Party, bits: ByteArray) : WithKey(signer.owningKey, bits)
}
object NullPublicKey : PublicKey, Comparable<PublicKey> {
override fun getAlgorithm() = "NULL"
override fun getEncoded() = byteArrayOf(0)
override fun getFormat() = "NULL"
override fun compareTo(other: PublicKey): Int = if (other == NullPublicKey) 0 else -1
override fun toString() = "NULL_KEY"
}
// TODO: Clean up this duplication between Null and Dummy public key
class DummyPublicKey(val s: String) : PublicKey, Comparable<PublicKey> {
override fun getAlgorithm() = "DUMMY"
override fun getEncoded() = s.toByteArray()
override fun getFormat() = "ASN.1"
override fun compareTo(other: PublicKey): Int = BigInteger(encoded).compareTo(BigInteger(other.encoded))
override fun equals(other: Any?) = other is DummyPublicKey && other.s == s
override fun hashCode(): Int = s.hashCode()
override fun toString() = "PUBKEY[$s]"
}
/** A signature with a key and value of zero. Useful when you want a signature object that you know won't ever be used. */
object NullSignature : DigitalSignature.WithKey(NullPublicKey, ByteArray(32))
/** Utility to simplify the act of signing a byte array */
fun PrivateKey.signWithECDSA(bits: ByteArray): DigitalSignature {
val signer = EdDSAEngine()
signer.initSign(this)
signer.update(bits)
val sig = signer.sign()
return DigitalSignature(sig)
}
fun PrivateKey.signWithECDSA(bitsToSign: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
return DigitalSignature.WithKey(publicKey, signWithECDSA(bitsToSign).bits)
}
val ed25519Curve = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)
fun parsePublicKeyBase58(base58String: String) = EdDSAPublicKey(EdDSAPublicKeySpec(Base58.decode(base58String), ed25519Curve))
fun PublicKey.toBase58String() = Base58.encode((this as EdDSAPublicKey).abyte)
fun KeyPair.signWithECDSA(bitsToSign: ByteArray) = private.signWithECDSA(bitsToSign, public)
fun KeyPair.signWithECDSA(bitsToSign: OpaqueBytes) = private.signWithECDSA(bitsToSign.bits, public)
fun KeyPair.signWithECDSA(bitsToSign: OpaqueBytes, party: Party) = signWithECDSA(bitsToSign.bits, party)
fun KeyPair.signWithECDSA(bitsToSign: ByteArray, party: Party): DigitalSignature.LegallyIdentifiable {
check(public == party.owningKey)
val sig = signWithECDSA(bitsToSign)
return DigitalSignature.LegallyIdentifiable(party, sig.bits)
}
/** Utility to simplify the act of verifying a signature */
fun PublicKey.verifyWithECDSA(content: ByteArray, signature: DigitalSignature) {
val verifier = EdDSAEngine()
verifier.initVerify(this)
verifier.update(content)
if (verifier.verify(signature.bits) == false)
throw SignatureException("Signature did not match")
}
/** Render a public key to a string, using a short form if it's an elliptic curve public key */
fun PublicKey.toStringShort(): String {
return (this as? EdDSAPublicKey)?.let { key ->
"DL" + Base58.encode(key.abyte) // DL -> Distributed Ledger
} ?: toString()
}
fun Iterable<PublicKey>.toStringsShort(): String = map { it.toStringShort() }.toString()
/** Creates a [PublicKeyTree] with a single leaf node containing the public key */
val PublicKey.tree: PublicKeyTree get() = PublicKeyTree.Leaf(this)
/** Returns the set of all [PublicKey]s of the signatures */
fun Iterable<DigitalSignature.WithKey>.byKeys() = map { it.by }.toSet()
// Allow Kotlin destructuring: val (private, public) = keyPair
operator fun KeyPair.component1() = this.private
operator fun KeyPair.component2() = this.public
/** A simple wrapper that will make it easier to swap out the EC algorithm we use in future */
fun generateKeyPair(): KeyPair = KeyPairGenerator().generateKeyPair()
/**
* Returns a key pair derived from the given private key entropy. This is useful for unit tests and other cases where
* you want hard-coded private keys.
*/
fun entropyToKeyPair(entropy: BigInteger): KeyPair {
val params = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)
val bits = entropy.toByteArray().copyOf(params.curve.field.getb() / 8)
val priv = EdDSAPrivateKeySpec(bits, params)
val pub = EdDSAPublicKeySpec(priv.a, params)
val key = KeyPair(EdDSAPublicKey(pub), EdDSAPrivateKey(priv))
return key
}

View File

@ -1,13 +0,0 @@
package com.r3corda.core.crypto
import com.r3corda.core.contracts.PartyAndReference
import com.r3corda.core.serialization.OpaqueBytes
import java.security.PublicKey
/** A [Party] is well known (name, pubkey) pair. In a real system this would probably be an X.509 certificate. */
data class Party(val name: String, val owningKey: PublicKey) {
override fun toString() = name
fun ref(bytes: OpaqueBytes) = PartyAndReference(this, bytes)
fun ref(vararg bytes: Byte) = ref(OpaqueBytes.of(*bytes))
}

View File

@ -1,137 +0,0 @@
package com.r3corda.core.crypto
import com.r3corda.core.crypto.PublicKeyTree.Leaf
import com.r3corda.core.crypto.PublicKeyTree.Node
import com.r3corda.core.serialization.deserialize
import com.r3corda.core.serialization.serialize
import java.security.PublicKey
/**
* A tree data structure that enables the representation of composite public keys.
*
* In the simplest case it may just contain a single node encapsulating a [PublicKey] a [Leaf].
*
* For more complex scenarios, such as *"Both Alice and Bob need to sign to consume a state S"*, we can represent
* the requirement by creating a tree with a root [Node], and Alice and Bob as children [Leaf]s.
* The root node would specify *weights* for each of its children and a *threshold* the minimum total weight required
* (e.g. the minimum number of child signatures required) to satisfy the tree signature requirement.
*
* Using these constructs we can express e.g. 1 of N (OR) or N of N (AND) signature requirements. By nesting we can
* create multi-level requirements such as *"either the CEO or 3 of 5 of his assistants need to sign"*.
*/
sealed class PublicKeyTree {
/** Checks whether [keys] match a sufficient amount of leaf nodes */
abstract fun isFulfilledBy(keys: Iterable<PublicKey>): Boolean
fun isFulfilledBy(key: PublicKey) = isFulfilledBy(setOf(key))
/** Returns all [PublicKey]s contained within the tree leaves */
abstract fun getKeys(): Set<PublicKey>
/** Checks whether any of the given [keys] matches a leaf on the tree */
fun containsAny(keys: Iterable<PublicKey>) = getKeys().intersect(keys).isNotEmpty()
// TODO: implement a proper encoding/decoding mechanism
fun toBase58String(): String = Base58.encode(this.serialize().bits)
companion object {
fun parseFromBase58(encoded: String) = Base58.decode(encoded).deserialize<PublicKeyTree>()
}
/** The leaf node of the public key tree a wrapper around a [PublicKey] primitive */
class Leaf(val publicKey: PublicKey) : PublicKeyTree() {
override fun isFulfilledBy(keys: Iterable<PublicKey>) = publicKey in keys
override fun getKeys(): Set<PublicKey> = setOf(publicKey)
// Auto-generated. TODO: remove once data class inheritance is enabled
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as Leaf
if (publicKey != other.publicKey) return false
return true
}
override fun hashCode() = publicKey.hashCode()
}
/**
* Represents a node in the [PublicKeyTree]. It maintains a list of child nodes sub-trees, and associated
* [weights] carried by child node signatures.
*
* The [threshold] specifies the minimum total weight required (in the simple case the minimum number of child
* signatures required) to satisfy the public key sub-tree rooted at this node.
*/
class Node(val threshold: Int,
val children: List<PublicKeyTree>,
val weights: List<Int>) : PublicKeyTree() {
override fun isFulfilledBy(keys: Iterable<PublicKey>): Boolean {
val totalWeight = children.mapIndexed { i, childNode ->
if (childNode.isFulfilledBy(keys)) weights[i] else 0
}.sum()
return totalWeight >= threshold
}
override fun getKeys(): Set<PublicKey> = children.flatMap { it.getKeys() }.toSet()
// Auto-generated. TODO: remove once data class inheritance is enabled
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as Node
if (threshold != other.threshold) return false
if (weights != other.weights) return false
if (children != other.children) return false
return true
}
override fun hashCode(): Int {
var result = threshold
result = 31 * result + weights.hashCode()
result = 31 * result + children.hashCode()
return result
}
}
/** A helper class for building a [PublicKeyTree.Node]. */
class Builder() {
private val children: MutableList<PublicKeyTree> = mutableListOf()
private val weights: MutableList<Int> = mutableListOf()
/** Adds a child [PublicKeyTree] node. Specifying a [weight] for the child is optional and will default to 1. */
fun addKey(publicKey: PublicKeyTree, weight: Int = 1): Builder {
children.add(publicKey)
weights.add(weight)
return this
}
fun addKeys(vararg publicKeys: PublicKeyTree): Builder {
publicKeys.forEach { addKey(it) }
return this
}
fun addLeaves(publicKeys: List<PublicKey>): Builder = addLeaves(*publicKeys.toTypedArray())
fun addLeaves(vararg publicKeys: PublicKey) = addKeys(*publicKeys.map { it.tree }.toTypedArray())
/**
* Builds the [PublicKeyTree.Node]. If [threshold] is not specified, it will default to
* the size of the children, effectively generating an "N of N" requirement.
*/
fun build(threshold: Int? = null): PublicKeyTree {
return if (children.size == 1) children.first()
else Node(threshold ?: children.size, children.toList(), weights.toList())
}
}
}
/** Returns the set of all [PublicKey]s contained in the leaves of the [PublicKeyTree]s */
fun Iterable<PublicKeyTree>.getKeys() = flatMap { it.getKeys() }.toSet()

View File

@ -1,44 +0,0 @@
package com.r3corda.core.crypto
import com.google.common.io.BaseEncoding
import com.r3corda.core.serialization.OpaqueBytes
import java.security.MessageDigest
/**
* Container for a cryptographically secure hash value.
* Provides utilities for generating a cryptographic hash using different algorithms (currently only SHA-256 supported).
*/
sealed class SecureHash(bits: ByteArray) : OpaqueBytes(bits) {
/** SHA-256 is part of the SHA-2 hash function family. Generated hash is fixed size, 256-bits (32-bytes) */
class SHA256(bits: ByteArray) : SecureHash(bits) {
init {
require(bits.size == 32)
}
}
override fun toString() = BaseEncoding.base16().encode(bits)
fun prefixChars(prefixLen: Int = 6) = toString().substring(0, prefixLen)
// Like static methods in Java, except the 'companion' is a singleton that can have state.
companion object {
@JvmStatic
fun parse(str: String) = BaseEncoding.base16().decode(str.toUpperCase()).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(bits: ByteArray) = SHA256(MessageDigest.getInstance("SHA-256").digest(bits))
@JvmStatic fun sha256Twice(bits: ByteArray) = sha256(sha256(bits).bits)
@JvmStatic fun sha256(str: String) = sha256(str.toByteArray())
@JvmStatic fun randomSHA256() = sha256(newSecureRandom().generateSeed(32))
}
// In future, maybe SHA3, truncated hashes etc.
}
fun ByteArray.sha256(): SecureHash.SHA256 = SecureHash.sha256(this)
fun OpaqueBytes.sha256(): SecureHash.SHA256 = SecureHash.sha256(this.bits)

View File

@ -1,38 +0,0 @@
package com.r3corda.core.node
/**
* Implement this interface on a class advertised in a META-INF/services/com.r3corda.core.node.CordaPluginRegistry file
* to extend a Corda node with additional application services.
*/
abstract class CordaPluginRegistry {
/**
* List of JAX-RS classes inside the contract jar. They are expected to have a single parameter constructor that takes a ServiceHub as input.
* These are listed as Class<*>, because in the future they will be instantiated inside a ClassLoader so that
* Cordapp code can be loaded dynamically.
*/
open val webApis: List<Class<*>> = emptyList()
/**
* Map of static serving endpoints to the matching resource directory. All endpoints will be prefixed with "/web" and postfixed with "\*.
* Resource directories can be either on disk directories (especially when debugging) in the form "a/b/c". Serving from a JAR can
* be specified with: javaClass.getResource("<folder-in-jar>").toExternalForm()
*/
open val staticServeDirs: Map<String, String> = emptyMap()
/**
* A Map with an entry for each consumed protocol used by the webAPIs.
* The key of each map entry should contain the ProtocolLogic<T> class name.
* The associated map values are the union of all concrete class names passed to the protocol constructor.
* Standard java.lang.* and kotlin.* types do not need to be included explicitly.
* This is used to extend the white listed protocols that can be initiated from the ServiceHub invokeProtocolAsync method.
*/
open val requiredProtocols: Map<String, Set<String>> = emptyMap()
/**
* List of additional long lived services to be hosted within the node.
* They are expected to have a single parameter constructor that takes a ServiceHubInternal as input.
* The ServiceHubInternal will be fully constructed before the plugin service is created and will
* allow access to the protocol factory and protocol initiation entry points there.
*/
open val servicePlugins: List<Class<*>> = emptyList()
}

View File

@ -1,23 +0,0 @@
package com.r3corda.core.node
import com.r3corda.core.crypto.Party
import com.r3corda.core.messaging.SingleMessageRecipient
import com.r3corda.core.node.services.ServiceInfo
import com.r3corda.core.node.services.ServiceType
/**
* Information for an advertised service including the service specific identity information.
* The identity can be used in protocols and is distinct from the Node's legalIdentity
*/
data class ServiceEntry(val info: ServiceInfo, val identity: Party)
/**
* Info about a network node that acts on behalf of some form of contract party.
*/
data class NodeInfo(val address: SingleMessageRecipient,
val legalIdentity: Party,
var advertisedServices: List<ServiceEntry> = emptyList(),
val physicalLocation: PhysicalLocation? = null) {
val notaryIdentity: Party get() = advertisedServices.single { it.info.type.isNotary() }.identity
fun serviceIdentities(type: ServiceType): List<Party> = advertisedServices.filter { it.info.type.isSubTypeOf(type) }.map { it.identity }
}

View File

@ -1,81 +0,0 @@
package com.r3corda.core.node
import com.google.common.util.concurrent.ListenableFuture
import com.r3corda.core.transactions.SignedTransaction
import com.r3corda.core.contracts.StateRef
import com.r3corda.core.contracts.TransactionResolutionException
import com.r3corda.core.contracts.TransactionState
import com.r3corda.core.messaging.MessagingService
import com.r3corda.core.node.services.*
import com.r3corda.core.protocols.ProtocolLogic
import java.security.KeyPair
import java.time.Clock
/**
* A service hub simply vends references to the other services a node has. Some of those services may be missing or
* mocked out. This class is useful to pass to chunks of pluggable code that might have need of many different kinds of
* functionality and you don't want to hard-code which types in the interface.
*
* Any services exposed to protocols (public view) need to implement [SerializeAsToken] or similar to avoid their internal
* state from being serialized in checkpoints.
*/
interface ServiceHub {
val vaultService: VaultService
val keyManagementService: KeyManagementService
val identityService: IdentityService
val storageService: StorageService
val networkService: MessagingService
val networkMapCache: NetworkMapCache
val schedulerService: SchedulerService
val clock: Clock
val myInfo: NodeInfo
/**
* Given a list of [SignedTransaction]s, writes them to the local storage for validated transactions and then
* sends them to the vault for further processing.
*
* @param txs The transactions to record.
*/
fun recordTransactions(txs: Iterable<SignedTransaction>)
/**
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
*
* @throws TransactionResolutionException if the [StateRef] points to a non-existent transaction.
*/
fun loadState(stateRef: StateRef): TransactionState<*> {
val definingTx = storageService.validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
return definingTx.tx.outputs[stateRef.index]
}
/**
* Will check [logicType] and [args] against a whitelist and if acceptable then construct and initiate the protocol.
*
* @throws IllegalProtocolLogicException or IllegalArgumentException if there are problems with the [logicType] or [args].
*/
fun <T : Any> invokeProtocolAsync(logicType: Class<out ProtocolLogic<T>>, vararg args: Any?): ListenableFuture<T>
/**
* Helper property to shorten code for fetching the Node's KeyPair associated with the
* public legalIdentity Party from the key management service.
* Typical use is during signing in protocols and for unit test signing.
*/
val legalIdentityKey: KeyPair get() = this.keyManagementService.toKeyPair(this.myInfo.legalIdentity.owningKey)
/**
* Helper property to shorten code for fetching the Node's KeyPair associated with the
* public notaryIdentity Party from the key management service. It is assumed that this is only
* used in contexts where the Node knows it is hosting a Notary Service. Otherwise, it will throw
* an IllegalArgumentException.
* Typical use is during signing in protocols and for unit test signing.
*/
val notaryIdentityKey: KeyPair get() = this.keyManagementService.toKeyPair(this.myInfo.notaryIdentity.owningKey)
}
/**
* Given some [SignedTransaction]s, writes them to the local storage for validated transactions and then
* sends them to the vault for further processing.
*
* @param txs The transactions to record.
*/
fun ServiceHub.recordTransactions(vararg txs: SignedTransaction) = recordTransactions(txs.toList())

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