From 7a4f1766c313814b16a0da34a4550fe4bacec2ac Mon Sep 17 00:00:00 2001 From: Ashutosh Meher Date: Tue, 9 Jul 2019 13:33:44 +0530 Subject: [PATCH 01/20] DvP NonFungible Token --- non-fungible-token-dvp/.gitignore | 78 ++++++++ non-fungible-token-dvp/LICENCE | 13 ++ non-fungible-token-dvp/README.md | 142 +++++++++++++++ non-fungible-token-dvp/TRADEMARK | 4 + non-fungible-token-dvp/build.gradle | 147 +++++++++++++++ non-fungible-token-dvp/clients/build.gradle | 47 +++++ .../tokenSDK/samples/contracts/Client.java | 36 ++++ .../contracts/webserver/Controller.java | 27 +++ .../webserver/NodeRPCConnection.java | 48 +++++ .../samples/contracts/webserver/Starter.java | 23 +++ .../clients/src/main/resources/static/app.js | 3 + .../src/main/resources/static/index.html | 10 + non-fungible-token-dvp/config/dev/log4j2.xml | 59 ++++++ non-fungible-token-dvp/config/test/log4j2.xml | 20 ++ non-fungible-token-dvp/contracts/build.gradle | 27 +++ .../samples/contracts/HouseContract.java | 27 +++ .../tokenSDK/samples/states/HouseState.java | 83 +++++++++ .../contracts/contracts/ContractTests.java | 13 ++ non-fungible-token-dvp/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54333 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + non-fungible-token-dvp/gradlew | 172 ++++++++++++++++++ non-fungible-token-dvp/gradlew.bat | 84 +++++++++ non-fungible-token-dvp/settings.gradle | 3 + non-fungible-token-dvp/workflows/build.gradle | 61 +++++++ .../samples/contracts/DriverBasedTest.java | 48 +++++ .../flows/FiatCurrencyIssueFlow.java | 35 ++++ .../flows/HouseSaleInitiatorFlow.java | 68 +++++++ .../flows/HouseSaleResponderFlow.java | 50 +++++ .../flows/HouseTokenCreateAndIssueFlow.java | 59 ++++++ .../samples/contracts/ContractTests.java | 13 ++ .../tokenSDK/samples/contracts/FlowTests.java | 29 +++ .../samples/contracts/NodeDriver.java | 39 ++++ 33 files changed, 1477 insertions(+) create mode 100644 non-fungible-token-dvp/.gitignore create mode 100644 non-fungible-token-dvp/LICENCE create mode 100644 non-fungible-token-dvp/README.md create mode 100644 non-fungible-token-dvp/TRADEMARK create mode 100644 non-fungible-token-dvp/build.gradle create mode 100644 non-fungible-token-dvp/clients/build.gradle create mode 100644 non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/Client.java create mode 100644 non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/Controller.java create mode 100644 non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/NodeRPCConnection.java create mode 100644 non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/Starter.java create mode 100644 non-fungible-token-dvp/clients/src/main/resources/static/app.js create mode 100644 non-fungible-token-dvp/clients/src/main/resources/static/index.html create mode 100644 non-fungible-token-dvp/config/dev/log4j2.xml create mode 100644 non-fungible-token-dvp/config/test/log4j2.xml create mode 100644 non-fungible-token-dvp/contracts/build.gradle create mode 100644 non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/contracts/HouseContract.java create mode 100644 non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/states/HouseState.java create mode 100644 non-fungible-token-dvp/contracts/src/test/java/corda/tokenSDK/samples/contracts/contracts/ContractTests.java create mode 100644 non-fungible-token-dvp/gradle.properties create mode 100644 non-fungible-token-dvp/gradle/wrapper/gradle-wrapper.jar create mode 100644 non-fungible-token-dvp/gradle/wrapper/gradle-wrapper.properties create mode 100755 non-fungible-token-dvp/gradlew create mode 100644 non-fungible-token-dvp/gradlew.bat create mode 100644 non-fungible-token-dvp/settings.gradle create mode 100644 non-fungible-token-dvp/workflows/build.gradle create mode 100644 non-fungible-token-dvp/workflows/src/integrationTest/java/corda/tokenSDK/samples/contracts/DriverBasedTest.java create mode 100644 non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/FiatCurrencyIssueFlow.java create mode 100644 non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleInitiatorFlow.java create mode 100644 non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleResponderFlow.java create mode 100644 non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseTokenCreateAndIssueFlow.java create mode 100644 non-fungible-token-dvp/workflows/src/test/java/corda/tokenSDK/samples/contracts/ContractTests.java create mode 100644 non-fungible-token-dvp/workflows/src/test/java/corda/tokenSDK/samples/contracts/FlowTests.java create mode 100644 non-fungible-token-dvp/workflows/src/test/java/corda/tokenSDK/samples/contracts/NodeDriver.java diff --git a/non-fungible-token-dvp/.gitignore b/non-fungible-token-dvp/.gitignore new file mode 100644 index 000000000..4202532ed --- /dev/null +++ b/non-fungible-token-dvp/.gitignore @@ -0,0 +1,78 @@ +# Eclipse, ctags, Mac metadata, log files +.classpath +.project +.settings +tags +.DS_Store +*.log +*.log.gz +*.orig + +.gradle + +# General build files +**/build/* +!docs/build/* + +lib/dokka.jar + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio + +*.iml + +## Directory-based project format: +#.idea + +# if you remove the above rule, at least ignore the following: + +# Specific files to avoid churn +.idea/*.xml +.idea/copyright +.idea/jsLibraryMappings.xml + +# User-specific stuff: +.idea/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/dataSources.ids +.idea/dataSources.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ +/workflows/out/ +/contracts/out/ +clients/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties + +# docs related +docs/virtualenv/ + +# if you use the installQuasar task +lib \ No newline at end of file diff --git a/non-fungible-token-dvp/LICENCE b/non-fungible-token-dvp/LICENCE new file mode 100644 index 000000000..3ff572d11 --- /dev/null +++ b/non-fungible-token-dvp/LICENCE @@ -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. \ No newline at end of file diff --git a/non-fungible-token-dvp/README.md b/non-fungible-token-dvp/README.md new file mode 100644 index 000000000..12b3231c0 --- /dev/null +++ b/non-fungible-token-dvp/README.md @@ -0,0 +1,142 @@ +

+ Corda +

+ +# CorDapp Template - Java + +Welcome to the Java CorDapp template. The CorDapp template is a stubbed-out CorDapp that you can use to bootstrap +your own CorDapps. + +**This is the Java version of the CorDapp template. The Kotlin equivalent is +[here](https://github.com/corda/cordapp-template-kotlin/).** + +# Pre-Requisites + +See https://docs.corda.net/getting-set-up.html. + +# Usage + +## Running tests inside IntelliJ + +We recommend editing your IntelliJ preferences so that you use the Gradle runner - this means that the quasar utils +plugin will make sure that some flags (like ``-javaagent`` - see below) are +set for you. + +To switch to using the Gradle runner: + +* Navigate to ``Build, Execution, Deployment -> Build Tools -> Gradle -> Runner`` (or search for `runner`) + * Windows: this is in "Settings" + * MacOS: this is in "Preferences" +* Set "Delegate IDE build/run actions to gradle" to true +* Set "Run test using:" to "Gradle Test Runner" + +If you would prefer to use the built in IntelliJ JUnit test runner, you can run ``gradlew installQuasar`` which will +copy your quasar JAR file to the lib directory. You will then need to specify ``-javaagent:lib/quasar.jar`` +and set the run directory to the project root directory for each test. + +## Running the nodes + +See https://docs.corda.net/tutorial-cordapp.html#running-the-example-cordapp. + +## Interacting with the nodes + +### Shell + +When started via the command line, each node will display an interactive shell: + + Welcome to the Corda interactive shell. + Useful commands include 'help' to see what is available, and 'bye' to shut down the node. + + Tue Nov 06 11:58:13 GMT 2018>>> + +You can use this shell to interact with your node. For example, enter `run networkMapSnapshot` to see a list of +the other nodes on the network: + + Tue Nov 06 11:58:13 GMT 2018>>> run networkMapSnapshot + [ + { + "addresses" : [ "localhost:10002" ], + "legalIdentitiesAndCerts" : [ "O=Notary, L=London, C=GB" ], + "platformVersion" : 3, + "serial" : 1541505484825 + }, + { + "addresses" : [ "localhost:10005" ], + "legalIdentitiesAndCerts" : [ "O=PartyA, L=London, C=GB" ], + "platformVersion" : 3, + "serial" : 1541505382560 + }, + { + "addresses" : [ "localhost:10008" ], + "legalIdentitiesAndCerts" : [ "O=PartyB, L=New York, C=US" ], + "platformVersion" : 3, + "serial" : 1541505384742 + } + ] + + Tue Nov 06 12:30:11 GMT 2018>>> + +You can find out more about the node shell [here](https://docs.corda.net/shell.html). + +### Client + +`clients/src/main/java/com/template/Client.java` defines a simple command-line client that connects to a node via RPC +and prints a list of the other nodes on the network. + +#### Running the client + +##### Via the command line + +Run the `runTemplateClient` Gradle task. By default, it connects to the node with RPC address `localhost:10006` with +the username `user1` and the password `test`. + +##### Via IntelliJ + +Run the `Run Template Client` run configuration. By default, it connects to the node with RPC address `localhost:10006` +with the username `user1` and the password `test`. + +### Webserver + +`clients/src/main/java/com/template/webserver/` defines a simple Spring webserver that connects to a node via RPC and +allows you to interact with the node over HTTP. + +The API endpoints are defined here: + + clients/src/main/java/com/template/webserver/Controller.java + +And a static webpage is defined here: + + clients/src/main/resources/static/ + +#### Running the webserver + +##### Via the command line + +Run the `runTemplateServer` Gradle task. By default, it connects to the node with RPC address `localhost:10006` with +the username `user1` and the password `test`, and serves the webserver on port `localhost:10050`. + +##### Via IntelliJ + +Run the `Run Template Server` run configuration. By default, it connects to the node with RPC address `localhost:10006` +with the username `user1` and the password `test`, and serves the webserver on port `localhost:10050`. + +#### Interacting with the webserver + +The static webpage is served on: + + http://localhost:10050 + +While the sole template endpoint is served on: + + http://localhost:10050/templateendpoint + +# Extending the template + +You should extend this template as follows: + +* Add your own state and contract definitions under `contracts/src/main/java/` +* Add your own flow definitions under `workflows/src/main/java/` +* Extend or replace the client and webserver under `clients/src/main/java/` + +For a guided example of how to extend this template, see the Hello, World! tutorial +[here](https://docs.corda.net/hello-world-introduction.html). diff --git a/non-fungible-token-dvp/TRADEMARK b/non-fungible-token-dvp/TRADEMARK new file mode 100644 index 000000000..d2e056b5f --- /dev/null +++ b/non-fungible-token-dvp/TRADEMARK @@ -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 at +https://www.r3.com/trademark-policy/. \ No newline at end of file diff --git a/non-fungible-token-dvp/build.gradle b/non-fungible-token-dvp/build.gradle new file mode 100644 index 000000000..5a7f0ea70 --- /dev/null +++ b/non-fungible-token-dvp/build.gradle @@ -0,0 +1,147 @@ +buildscript { + ext { + corda_release_group = 'net.corda' + corda_release_version = '4.1' + tokens_release_group = 'com.r3.corda.lib.tokens' + tokens_release_version = '1.0-RC03' + corda_gradle_plugins_version = '4.0.42' + junit_version = '4.12' + quasar_version = '0.7.10' + spring_boot_version = '2.0.2.RELEASE' + spring_boot_gradle_plugin_version = '2.0.2.RELEASE' + slf4j_version = '1.7.25' + log4j_version = '2.11.2' + corda_platform_version = '4' + } + + repositories { + mavenLocal() + mavenCentral() + jcenter() + maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } + } + + dependencies { + classpath "net.corda.plugins:cordapp:$corda_gradle_plugins_version" + classpath "net.corda.plugins:cordformation:$corda_gradle_plugins_version" + classpath "net.corda.plugins:quasar-utils:$corda_gradle_plugins_version" + classpath "org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_gradle_plugin_version" + } +} + +allprojects { + apply plugin: 'java' + + repositories { + mavenLocal() + jcenter() + mavenCentral() + maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } + maven { url 'https://jitpack.io' } + // Can be removed post-release - used to get nightly snapshot build. + maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-tokens-dev' } + } + + tasks.withType(JavaCompile) { + options.compilerArgs << "-parameters" // Required by Corda's serialisation framework. + } + + jar { + // This makes the JAR's SHA-256 hash repeatable. + preserveFileTimestamps = false + reproducibleFileOrder = true + } +} + + +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + info { + name "CorDapp Template" + vendor "Corda Open Source" + targetPlatformVersion corda_platform_version.toInteger() + minimumPlatformVersion corda_platform_version.toInteger() + } +} + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } +} + +dependencies { + testCompile "junit:junit:$junit_version" + + // Corda dependencies. + cordaCompile "$corda_release_group:corda-core:$corda_release_version" + cordaCompile "$corda_release_group:corda-node-api:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":workflows") + cordapp project(":contracts") + + // For logging + cordaCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" + cordaCompile "org.apache.logging.log4j:log4j-web:${log4j_version}" + cordaCompile "org.slf4j:jul-to-slf4j:$slf4j_version" + + // Token SDK dependencies. + cordapp "$tokens_release_group:tokens-contracts:$tokens_release_version" + cordapp "$tokens_release_group:tokens-workflows:$tokens_release_version" + cordapp "$tokens_release_group:tokens-money:$tokens_release_version" +} + +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + nodeDefaults { + projectCordapp { + deploy = false + } + cordapp("$tokens_release_group:tokens-contracts:$tokens_release_version") + cordapp("$tokens_release_group:tokens-workflows:$tokens_release_version") + cordapp("$tokens_release_group:tokens-money:$tokens_release_version") + cordapp project(':contracts') + cordapp project(':workflows') + } + node { + name "O=Notary,L=London,C=GB" + notary = [validating : false] + p2pPort 10002 + rpcSettings { + address("localhost:10003") + adminAddress("localhost:10043") + } + cordapps = [] + } + node { + name "O=PartyA,L=London,C=GB" + p2pPort 10005 + rpcSettings { + address("localhost:10006") + adminAddress("localhost:10046") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=PartyB,L=New York,C=US" + p2pPort 10008 + rpcSettings { + address("localhost:10009") + adminAddress("localhost:10049") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } +} + +task installQuasar(type: Copy) { + destinationDir rootProject.file("lib") + from(configurations.quasar) { + rename 'quasar-core(.*).jar', 'quasar.jar' + } +} \ No newline at end of file diff --git a/non-fungible-token-dvp/clients/build.gradle b/non-fungible-token-dvp/clients/build.gradle new file mode 100644 index 000000000..783276424 --- /dev/null +++ b/non-fungible-token-dvp/clients/build.gradle @@ -0,0 +1,47 @@ +apply plugin: 'org.springframework.boot' + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } +} + +dependencies { + // Corda dependencies. + compile "$corda_release_group:corda-rpc:$corda_release_version" + + // CorDapp dependencies. + compile project(":contracts") + compile project(":workflows") + + compile("org.springframework.boot:spring-boot-starter-websocket:$spring_boot_version") { + exclude group: "org.springframework.boot", module: "spring-boot-starter-logging" + } + + compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" + compile "org.apache.logging.log4j:log4j-web:${log4j_version}" + compile "org.slf4j:jul-to-slf4j:$slf4j_version" + + // Token SDK dependencies. + compile "com.r3.tokens-sdk:contract:1.0-SNAPSHOT" + compile "com.r3.tokens-sdk:workflow:1.0-SNAPSHOT" + compile "com.r3.tokens-sdk:money:1.0-SNAPSHOT" +} + +springBoot { + mainClassName = "com.template.webserver.Server" +} + +task runTemplateClient(type: JavaExec, dependsOn: assemble) { + classpath = sourceSets.main.runtimeClasspath + main = 'com.template.Client' + args 'localhost:10006', 'user1', 'test' +} + +task runTemplateServer(type: JavaExec, dependsOn: assemble) { + classpath = sourceSets.main.runtimeClasspath + main = 'com.template.webserver.Starter' + args '--server.port=10050', '--config.rpc.host=localhost', '--config.rpc.port=10006', '--config.rpc.username=user1', '--config.rpc.password=test' +} diff --git a/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/Client.java b/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/Client.java new file mode 100644 index 000000000..19c9189ef --- /dev/null +++ b/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/Client.java @@ -0,0 +1,36 @@ +package corda.tokenSDK.samples.contracts; + +import net.corda.client.rpc.CordaRPCClient; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.node.NodeInfo; +import net.corda.core.utilities.NetworkHostAndPort; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import static net.corda.core.utilities.NetworkHostAndPort.parse; + +/** + * Connects to a Corda node via RPC and performs RPC operations on the node. + * + * The RPC connection is configured using command line arguments. + */ +public class Client { + private static final Logger logger = LoggerFactory.getLogger(Client.class); + + public static void main(String[] args) { + // Create an RPC connection to the node. + if (args.length != 3) throw new IllegalArgumentException("Usage: Client "); + final NetworkHostAndPort nodeAddress = parse(args[0]); + final String rpcUsername = args[1]; + final String rpcPassword = args[2]; + final CordaRPCClient client = new CordaRPCClient(nodeAddress); + final CordaRPCOps proxy = client.start(rpcUsername, rpcPassword).getProxy(); + + // Interact with the node. + // For example, here we print the nodes on the network. + final List nodes = proxy.networkMapSnapshot(); + logger.info("{}", nodes); + } +} \ No newline at end of file diff --git a/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/Controller.java b/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/Controller.java new file mode 100644 index 000000000..2b63d5604 --- /dev/null +++ b/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/Controller.java @@ -0,0 +1,27 @@ +package corda.tokenSDK.samples.contracts.webserver; + +import net.corda.core.messaging.CordaRPCOps; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Define your API endpoints here. + */ +@RestController +@RequestMapping("/") // The paths for HTTP requests are relative to this base path. +public class Controller { + private final CordaRPCOps proxy; + private final static Logger logger = LoggerFactory.getLogger(Controller.class); + + public Controller(NodeRPCConnection rpc) { + this.proxy = rpc.proxy; + } + + @GetMapping(value = "/templateendpoint", produces = "text/plain") + private String templateendpoint() { + return "Define an endpoint here."; + } +} \ No newline at end of file diff --git a/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/NodeRPCConnection.java b/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/NodeRPCConnection.java new file mode 100644 index 000000000..59cb56513 --- /dev/null +++ b/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/NodeRPCConnection.java @@ -0,0 +1,48 @@ +package corda.tokenSDK.samples.contracts.webserver; + +import net.corda.client.rpc.CordaRPCClient; +import net.corda.client.rpc.CordaRPCConnection; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.utilities.NetworkHostAndPort; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +/** + * Wraps an RPC connection to a Corda node. + * + * The RPC connection is configured using command line arguments. + */ +@Component +public class NodeRPCConnection implements AutoCloseable { + // The host of the node we are connecting to. + @Value("${config.rpc.host}") + private String host; + // The RPC port of the node we are connecting to. + @Value("${config.rpc.username}") + private String username; + // The username for logging into the RPC client. + @Value("${config.rpc.password}") + private String password; + // The password for logging into the RPC client. + @Value("${config.rpc.port}") + private int rpcPort; + + private CordaRPCConnection rpcConnection; + CordaRPCOps proxy; + + @PostConstruct + public void initialiseNodeRPCConnection() { + NetworkHostAndPort rpcAddress = new NetworkHostAndPort(host, rpcPort); + CordaRPCClient rpcClient = new CordaRPCClient(rpcAddress); + rpcConnection = rpcClient.start(username, password); + proxy = rpcConnection.getProxy(); + } + + @PreDestroy + public void close() { + rpcConnection.notifyServerAndClose(); + } +} \ No newline at end of file diff --git a/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/Starter.java b/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/Starter.java new file mode 100644 index 000000000..01c1201c9 --- /dev/null +++ b/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/Starter.java @@ -0,0 +1,23 @@ +package corda.tokenSDK.samples.contracts.webserver; + +import org.springframework.boot.Banner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import static org.springframework.boot.WebApplicationType.SERVLET; + +/** + * Our Spring Boot application. + */ +@SpringBootApplication +public class Starter { + /** + * Starts our Spring Boot application. + */ + public static void main(String[] args) { + SpringApplication app = new SpringApplication(Starter.class); + app.setBannerMode(Banner.Mode.OFF); + app.setWebApplicationType(SERVLET); + app.run(args); + } +} \ No newline at end of file diff --git a/non-fungible-token-dvp/clients/src/main/resources/static/app.js b/non-fungible-token-dvp/clients/src/main/resources/static/app.js new file mode 100644 index 000000000..c58d2de8c --- /dev/null +++ b/non-fungible-token-dvp/clients/src/main/resources/static/app.js @@ -0,0 +1,3 @@ +"use strict"; + +// Define your client-side logic here. \ No newline at end of file diff --git a/non-fungible-token-dvp/clients/src/main/resources/static/index.html b/non-fungible-token-dvp/clients/src/main/resources/static/index.html new file mode 100644 index 000000000..758501dd0 --- /dev/null +++ b/non-fungible-token-dvp/clients/src/main/resources/static/index.html @@ -0,0 +1,10 @@ + + + + + Example front-end. + + +
Define your front-end here.
+ + \ No newline at end of file diff --git a/non-fungible-token-dvp/config/dev/log4j2.xml b/non-fungible-token-dvp/config/dev/log4j2.xml new file mode 100644 index 000000000..34ba4d45a --- /dev/null +++ b/non-fungible-token-dvp/config/dev/log4j2.xml @@ -0,0 +1,59 @@ + + + + + logs + node-${hostName} + ${log-path}/archive + + + + + + + + + %highlight{%level{length=1} %d{HH:mm:ss} %T %c{1}.%M - %msg%n}{INFO=white,WARN=red,FATAL=bright red blink} + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/non-fungible-token-dvp/config/test/log4j2.xml b/non-fungible-token-dvp/config/test/log4j2.xml new file mode 100644 index 000000000..cd9926ca8 --- /dev/null +++ b/non-fungible-token-dvp/config/test/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n + > + + + + + + + + + + + + diff --git a/non-fungible-token-dvp/contracts/build.gradle b/non-fungible-token-dvp/contracts/build.gradle new file mode 100644 index 000000000..cf7c99556 --- /dev/null +++ b/non-fungible-token-dvp/contracts/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' + +cordapp { + targetPlatformVersion corda_platform_version.toInteger() + minimumPlatformVersion corda_platform_version.toInteger() + contract { + name "Template CorDapp" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } + signing { + enabled true + } +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_release_group:corda-core:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // Token SDK dependencies. + cordaCompile "$tokens_release_group:tokens-contracts:$tokens_release_version" + cordaCompile "$tokens_release_group:tokens-money:$tokens_release_version" +} \ No newline at end of file diff --git a/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/contracts/HouseContract.java b/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/contracts/HouseContract.java new file mode 100644 index 000000000..7cfc6b49d --- /dev/null +++ b/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/contracts/HouseContract.java @@ -0,0 +1,27 @@ +package corda.tokenSDK.samples.contracts; + +import com.r3.corda.lib.tokens.contracts.EvolvableTokenContract; +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import org.jetbrains.annotations.NotNull; + + +public class HouseContract extends EvolvableTokenContract implements Contract { + + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + + } + + @Override + public void additionalCreateChecks(@NotNull LedgerTransaction tx) { + + } + + @Override + public void additionalUpdateChecks(@NotNull LedgerTransaction tx) { + + } + +} diff --git a/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/states/HouseState.java b/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/states/HouseState.java new file mode 100644 index 000000000..553dbed95 --- /dev/null +++ b/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/states/HouseState.java @@ -0,0 +1,83 @@ +package corda.tokenSDK.samples.states; + +import com.google.common.collect.ImmutableList; +import com.r3.corda.lib.tokens.contracts.states.EvolvableTokenType; +import com.r3.corda.lib.tokens.contracts.types.TokenPointer; +import com.r3.corda.lib.tokens.money.FiatCurrency; +import corda.tokenSDK.samples.contracts.HouseContract; +import net.corda.core.contracts.Amount; +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.LinearPointer; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.Party; +import org.jetbrains.annotations.NotNull; + +import java.util.Currency; +import java.util.List; + +@BelongsToContract(HouseContract.class) +public class HouseState extends EvolvableTokenType { + + private final UniqueIdentifier linearId; + private final List maintainers; + private final int fractionDigits = 0; + + //Properties of House State + private final Amount valuation; + private final int noOfBedRooms; + private final String constructionArea; + private final String additionInfo; + private final String address; + + public HouseState(UniqueIdentifier linearId, List maintainers, Amount valuation, int noOfBedRooms, String constructionArea, String additionInfo, String address) { + this.linearId = linearId; + this.maintainers = maintainers; + this.valuation = valuation; + this.noOfBedRooms = noOfBedRooms; + this.constructionArea = constructionArea; + this.additionInfo = additionInfo; + this.address = address; + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return linearId; + } + + public int getNoOfBedRooms() { + return noOfBedRooms; + } + + public String getConstructionArea() { + return constructionArea; + } + + public String getAdditionInfo() { + return additionInfo; + } + + public String getAddress() { + return address; + } + + public Amount getValuation() { + return valuation; + } + + @Override + public int getFractionDigits() { + return fractionDigits; + } + + @NotNull + @Override + public List getMaintainers() { + return ImmutableList.copyOf(maintainers); + } + + public TokenPointer toPointer(){ + LinearPointer linearPointer = new LinearPointer<>(linearId, HouseState.class); + return new TokenPointer<>(linearPointer, fractionDigits); + } +} diff --git a/non-fungible-token-dvp/contracts/src/test/java/corda/tokenSDK/samples/contracts/contracts/ContractTests.java b/non-fungible-token-dvp/contracts/src/test/java/corda/tokenSDK/samples/contracts/contracts/ContractTests.java new file mode 100644 index 000000000..cad32d14c --- /dev/null +++ b/non-fungible-token-dvp/contracts/src/test/java/corda/tokenSDK/samples/contracts/contracts/ContractTests.java @@ -0,0 +1,13 @@ +package corda.tokenSDK.samples.contracts.contracts; + +import net.corda.testing.node.MockServices; +import org.junit.Test; + +public class ContractTests { + private final MockServices ledgerServices = new MockServices(); + + @Test + public void dummyTest() { + + } +} \ No newline at end of file diff --git a/non-fungible-token-dvp/gradle.properties b/non-fungible-token-dvp/gradle.properties new file mode 100644 index 000000000..7d7dcd3a7 --- /dev/null +++ b/non-fungible-token-dvp/gradle.properties @@ -0,0 +1,3 @@ +name=Test +group=com.template +version=0.1 \ No newline at end of file diff --git a/non-fungible-token-dvp/gradle/wrapper/gradle-wrapper.jar b/non-fungible-token-dvp/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..99340b4ad18d3c7e764794d300ffd35017036793 GIT binary patch literal 54333 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 D&=%~j literal 0 HcmV?d00001 diff --git a/non-fungible-token-dvp/gradle/wrapper/gradle-wrapper.properties b/non-fungible-token-dvp/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..aef7cb78c --- /dev/null +++ b/non-fungible-token-dvp/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Aug 25 12:50:39 BST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip diff --git a/non-fungible-token-dvp/gradlew b/non-fungible-token-dvp/gradlew new file mode 100755 index 000000000..cccdd3d51 --- /dev/null +++ b/non-fungible-token-dvp/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/non-fungible-token-dvp/gradlew.bat b/non-fungible-token-dvp/gradlew.bat new file mode 100644 index 000000000..f9553162f --- /dev/null +++ b/non-fungible-token-dvp/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/non-fungible-token-dvp/settings.gradle b/non-fungible-token-dvp/settings.gradle new file mode 100644 index 000000000..2514aca21 --- /dev/null +++ b/non-fungible-token-dvp/settings.gradle @@ -0,0 +1,3 @@ +include 'workflows' +include 'contracts' +include 'clients' \ No newline at end of file diff --git a/non-fungible-token-dvp/workflows/build.gradle b/non-fungible-token-dvp/workflows/build.gradle new file mode 100644 index 000000000..0fc074a79 --- /dev/null +++ b/non-fungible-token-dvp/workflows/build.gradle @@ -0,0 +1,61 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version.toInteger() + minimumPlatformVersion corda_platform_version.toInteger() + workflow { + name "Template Flows" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } + test { + resources { + srcDir rootProject.file("config/test") + } + } + integrationTest { + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integrationTest/java') + } + } +} + +configurations { + integrationTestCompile.extendsFrom testCompile + integrationTestRuntime.extendsFrom testRuntime +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_release_group:corda-core:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + + // For testing. + testCompile "junit:junit:$junit_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":contracts") + + // Token SDK dependencies. + cordaCompile "$tokens_release_group:tokens-money:$tokens_release_version" + cordaCompile "$tokens_release_group:tokens-workflows:$tokens_release_version" +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} \ No newline at end of file diff --git a/non-fungible-token-dvp/workflows/src/integrationTest/java/corda/tokenSDK/samples/contracts/DriverBasedTest.java b/non-fungible-token-dvp/workflows/src/integrationTest/java/corda/tokenSDK/samples/contracts/DriverBasedTest.java new file mode 100644 index 000000000..c2df6ffc5 --- /dev/null +++ b/non-fungible-token-dvp/workflows/src/integrationTest/java/corda/tokenSDK/samples/contracts/DriverBasedTest.java @@ -0,0 +1,48 @@ +package corda.tokenSDK.samples.contracts; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.driver.DriverParameters; +import net.corda.testing.driver.NodeHandle; +import net.corda.testing.driver.NodeParameters; +import org.junit.Test; + +import java.util.List; + +import static net.corda.testing.driver.Driver.driver; +import static org.junit.Assert.assertEquals; + +public class DriverBasedTest { + private final TestIdentity bankA = new TestIdentity(new CordaX500Name("BankA", "", "GB")); + private final TestIdentity bankB = new TestIdentity(new CordaX500Name("BankB", "", "US")); + + @Test + public void nodeTest() { + driver(new DriverParameters().withIsDebug(true).withStartNodesInProcess(true), dsl -> { + // Start a pair of nodes and wait for them both to be ready. + List> handleFutures = ImmutableList.of( + dsl.startNode(new NodeParameters().withProvidedName(bankA.getName())), + dsl.startNode(new NodeParameters().withProvidedName(bankB.getName())) + ); + + try { + NodeHandle partyAHandle = handleFutures.get(0).get(); + NodeHandle partyBHandle = handleFutures.get(1).get(); + + // From each node, make an RPC call to retrieve another node's name from the network map, to verify that the + // nodes have started and can communicate. + + // This is a very basic test: in practice tests would be starting flows, and verifying the states in the vault + // and other important metrics to ensure that your CorDapp is working as intended. + assertEquals(partyAHandle.getRpc().wellKnownPartyFromX500Name(bankB.getName()).getName(), bankB.getName()); + assertEquals(partyBHandle.getRpc().wellKnownPartyFromX500Name(bankA.getName()).getName(), bankA.getName()); + } catch (Exception e) { + throw new RuntimeException("Caught exception during test: ", e); + } + + return null; + }); + } +} \ No newline at end of file diff --git a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/FiatCurrencyIssueFlow.java b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/FiatCurrencyIssueFlow.java new file mode 100644 index 000000000..8f124f9a0 --- /dev/null +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/FiatCurrencyIssueFlow.java @@ -0,0 +1,35 @@ +package corda.tokenSDK.samples.contracts.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.tokens.money.FiatCurrency; +import com.r3.corda.lib.tokens.workflows.flows.rpc.IssueTokens; +import net.corda.core.contracts.Amount; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; + +import java.util.Currency; + +@StartableByRPC +public class FiatCurrencyIssueFlow extends FlowLogic { + + private final String currency; + private final Long amount; + private final Party recipient; + + public FiatCurrencyIssueFlow(String currency, Long amount, Party recipient) { + this.currency = currency; + this.amount = amount; + this.recipient = recipient; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + FiatCurrency token = new FiatCurrency(Currency.getInstance(currency)); + return subFlow(new IssueTokens<>(new Amount<>(amount, token), getOurIdentity(), recipient)); + } + +} diff --git a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleInitiatorFlow.java b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleInitiatorFlow.java new file mode 100644 index 000000000..532e9210b --- /dev/null +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleInitiatorFlow.java @@ -0,0 +1,68 @@ +package corda.tokenSDK.samples.contracts.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.google.common.collect.ImmutableList; +import com.r3.corda.lib.tokens.contracts.states.EvolvableTokenType; +import com.r3.corda.lib.tokens.contracts.states.FungibleToken; +import com.r3.corda.lib.tokens.contracts.types.TokenPointer; +import com.r3.corda.lib.tokens.money.FiatCurrency; +import com.r3.corda.lib.tokens.workflows.flows.move.MoveFungibleTokensFlow; +import com.r3.corda.lib.tokens.workflows.flows.move.MoveNonFungibleTokensFlow; +import com.r3.corda.lib.tokens.workflows.flows.move.MoveTokensUtilitiesKt; +import com.r3.corda.lib.tokens.workflows.flows.rpc.IssueTokens; +import com.r3.corda.lib.tokens.workflows.flows.rpc.MoveNonFungibleTokens; +import com.r3.corda.lib.tokens.workflows.internal.flows.distribution.UpdateDistributionListFlow; +import com.r3.corda.lib.tokens.workflows.types.PartyAndToken; +import corda.tokenSDK.samples.states.HouseState; +import net.corda.core.contracts.Amount; +import net.corda.core.contracts.LinearPointer; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.flows.*; +import net.corda.core.identity.Party; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; + +import java.util.List; +import java.util.UUID; + +@InitiatingFlow +@StartableByRPC +public class HouseSaleInitiatorFlow extends FlowLogic { + + private final String houseId; + private final Party buyer; + + public HouseSaleInitiatorFlow(String houseId, Party buyer) { + this.houseId = houseId; + this.buyer = buyer; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + + UUID uuid = UUID.fromString(houseId); + QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria( + null, ImmutableList.of(uuid), null, Vault.StateStatus.UNCONSUMED); + StateAndRef houseStateAndRef = getServiceHub().getVaultService(). + queryBy(HouseState.class, queryCriteria).getStates().get(0); + HouseState houseState = houseStateAndRef.getState().getData(); + + TransactionBuilder txBuilder = new TransactionBuilder(notary); + MoveTokensUtilitiesKt.addMoveNonFungibleTokens(txBuilder, getServiceHub(), houseState.toPointer(), buyer); + + FlowSession buyerSession = initiateFlow(buyer); + buyerSession.send(houseState.getValuation()); + List>> inputs = subFlow(new ReceiveStateAndRefFlow<>(buyerSession)); + List> moneyReceived = buyerSession.receive(List.class).unwrap(value -> value); + MoveTokensUtilitiesKt.addMoveTokens(txBuilder, inputs, moneyReceived); + + SignedTransaction initialSignedTrnx = getServiceHub().signInitialTransaction(txBuilder, getOurIdentity().getOwningKey()); + SignedTransaction signedTransaction = subFlow(new CollectSignaturesFlow(initialSignedTrnx, ImmutableList.of(buyerSession))); + subFlow(new UpdateDistributionListFlow(signedTransaction)); + return subFlow(new FinalityFlow(signedTransaction, ImmutableList.of(buyerSession))); + } +} diff --git a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleResponderFlow.java b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleResponderFlow.java new file mode 100644 index 000000000..3c7319146 --- /dev/null +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleResponderFlow.java @@ -0,0 +1,50 @@ +package corda.tokenSDK.samples.contracts.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.google.common.collect.ImmutableList; +import com.r3.corda.lib.tokens.contracts.states.FungibleToken; +import com.r3.corda.lib.tokens.money.FiatCurrency; +import com.r3.corda.lib.tokens.workflows.internal.selection.TokenSelection; +import com.r3.corda.lib.tokens.workflows.types.PartyAndAmount; +import kotlin.Pair; +import net.corda.core.contracts.Amount; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.flows.*; +import net.corda.core.transactions.SignedTransaction; +import org.jetbrains.annotations.NotNull; + +import java.util.Currency; +import java.util.List; + +@InitiatedBy(HouseSaleInitiatorFlow.class) +public class HouseSaleResponderFlow extends FlowLogic { + + private final FlowSession counterpartySession; + + public HouseSaleResponderFlow(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + Amount price = counterpartySession.receive(Amount.class).unwrap(amount -> amount); + Amount priceToken = new Amount<>(price.getQuantity(), new FiatCurrency(price.getToken())); + TokenSelection tokenSelection = new TokenSelection(getServiceHub(), 8, 100, 2000); + PartyAndAmount partyAndAmount = new PartyAndAmount<>(counterpartySession.getCounterparty(), priceToken); + Pair>>, List>> inputsAndOutputs = + tokenSelection.generateMove(getRunId().getUuid(), ImmutableList.of(partyAndAmount), getOurIdentity(), null); + + subFlow(new SendStateAndRefFlow(counterpartySession, inputsAndOutputs.getFirst())); + counterpartySession.send(inputsAndOutputs.getSecond()); + + subFlow(new SignTransactionFlow(counterpartySession) { + @Override + protected void checkTransaction(@NotNull SignedTransaction stx) throws FlowException { + // Custom Logic to validate transaction. + } + }); + return subFlow(new ReceiveFinalityFlow(counterpartySession)); + } +} diff --git a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseTokenCreateAndIssueFlow.java b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseTokenCreateAndIssueFlow.java new file mode 100644 index 000000000..d3af1533b --- /dev/null +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseTokenCreateAndIssueFlow.java @@ -0,0 +1,59 @@ +package corda.tokenSDK.samples.contracts.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.google.common.collect.ImmutableList; +import com.r3.corda.lib.tokens.contracts.states.FungibleToken; +import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken; +import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType; +import com.r3.corda.lib.tokens.contracts.types.TokenPointer; +import com.r3.corda.lib.tokens.contracts.utilities.TransactionUtilitiesKt; +import com.r3.corda.lib.tokens.money.FiatCurrency; +import com.r3.corda.lib.tokens.workflows.flows.evolvable.CreateEvolvableToken; +import com.r3.corda.lib.tokens.workflows.flows.rpc.IssueTokens; +import corda.tokenSDK.samples.states.HouseState; +import net.corda.core.contracts.Amount; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; + +import java.util.Currency; +import java.util.UUID; + +@StartableByRPC +public class HouseTokenCreateAndIssueFlow extends FlowLogic { + + private final Party owner; + private final Amount valuation; + private final int noOfBedRooms; + private final String constructionArea; + private final String additionInfo; + private final String address; + + public HouseTokenCreateAndIssueFlow(Party owner, Amount valuation, int noOfBedRooms, String constructionArea, String additionInfo, String address) { + this.owner = owner; + this.valuation = valuation; + this.noOfBedRooms = noOfBedRooms; + this.constructionArea = constructionArea; + this.additionInfo = additionInfo; + this.address = address; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + Party issuer = getOurIdentity(); + final HouseState houseState = new HouseState(UniqueIdentifier.Companion.fromString(UUID.randomUUID().toString()), ImmutableList.of(issuer), + valuation, noOfBedRooms, constructionArea, additionInfo, address); + subFlow(new CreateEvolvableToken<>(houseState, notary)); + + IssuedTokenType> issuedHouseToken = new IssuedTokenType<>(issuer, houseState.toPointer()); + NonFungibleToken> houseToken = + new NonFungibleToken<>(issuedHouseToken, owner, UniqueIdentifier.Companion.fromString(UUID.randomUUID().toString()), TransactionUtilitiesKt.getAttachmentIdForGenericParam(houseState.toPointer())); + return subFlow(new IssueTokens<>(houseToken)); + } +} diff --git a/non-fungible-token-dvp/workflows/src/test/java/corda/tokenSDK/samples/contracts/ContractTests.java b/non-fungible-token-dvp/workflows/src/test/java/corda/tokenSDK/samples/contracts/ContractTests.java new file mode 100644 index 000000000..40ef10047 --- /dev/null +++ b/non-fungible-token-dvp/workflows/src/test/java/corda/tokenSDK/samples/contracts/ContractTests.java @@ -0,0 +1,13 @@ +package corda.tokenSDK.samples.contracts; + +import net.corda.testing.node.MockServices; +import org.junit.Test; + +public class ContractTests { + private final MockServices ledgerServices = new MockServices(); + + @Test + public void dummyTest() { + + } +} \ No newline at end of file diff --git a/non-fungible-token-dvp/workflows/src/test/java/corda/tokenSDK/samples/contracts/FlowTests.java b/non-fungible-token-dvp/workflows/src/test/java/corda/tokenSDK/samples/contracts/FlowTests.java new file mode 100644 index 000000000..7562bb08f --- /dev/null +++ b/non-fungible-token-dvp/workflows/src/test/java/corda/tokenSDK/samples/contracts/FlowTests.java @@ -0,0 +1,29 @@ +package corda.tokenSDK.samples.contracts; + +import com.google.common.collect.ImmutableList; +import net.corda.testing.node.MockNetwork; +import net.corda.testing.node.StartedMockNode; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class FlowTests { + private final MockNetwork network = new MockNetwork(ImmutableList.of("com.template")); + private final StartedMockNode a = network.createNode(); + private final StartedMockNode b = network.createNode(); + + @Before + public void setup() { + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void dummyTest() { + + } +} diff --git a/non-fungible-token-dvp/workflows/src/test/java/corda/tokenSDK/samples/contracts/NodeDriver.java b/non-fungible-token-dvp/workflows/src/test/java/corda/tokenSDK/samples/contracts/NodeDriver.java new file mode 100644 index 000000000..1154f5e0a --- /dev/null +++ b/non-fungible-token-dvp/workflows/src/test/java/corda/tokenSDK/samples/contracts/NodeDriver.java @@ -0,0 +1,39 @@ +package corda.tokenSDK.samples.contracts; + +import com.google.common.collect.ImmutableList; +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.driver.DriverParameters; +import net.corda.testing.driver.NodeParameters; +import net.corda.testing.node.User; +import com.google.common.collect.ImmutableSet; + +import java.util.List; +import static net.corda.testing.driver.Driver.driver; + +/** + * Allows you to run your nodes through an IDE (as opposed to using deployNodes). Do not use in a production + * environment. + */ +public class NodeDriver { + public static void main(String[] args) { + final List rpcUsers = + ImmutableList.of(new User("user1", "test", ImmutableSet.of("ALL"))); + + driver(new DriverParameters().withStartNodesInProcess(true).withWaitForAllNodesToFinish(true), dsl -> { + try { + dsl.startNode(new NodeParameters() + .withProvidedName(new CordaX500Name("PartyA", "London", "GB")) + .withRpcUsers(rpcUsers)).get(); + dsl.startNode(new NodeParameters() + .withProvidedName(new CordaX500Name("PartyB", "New York", "US")) + .withRpcUsers(rpcUsers)).get(); + } catch (Throwable e) { + System.err.println("Encountered exception in node startup: " + e.getMessage()); + e.printStackTrace(); + } + + return null; + } + ); + } +} From 53aaf35f37ddc3e6cffc93623f271f39e33c02b9 Mon Sep 17 00:00:00 2001 From: Ashutosh Meher Date: Tue, 9 Jul 2019 15:29:26 +0530 Subject: [PATCH 02/20] Readme --- non-fungible-token-dvp/README.md | 139 +++++++------------------------ 1 file changed, 30 insertions(+), 109 deletions(-) diff --git a/non-fungible-token-dvp/README.md b/non-fungible-token-dvp/README.md index 12b3231c0..bd9ecf866 100644 --- a/non-fungible-token-dvp/README.md +++ b/non-fungible-token-dvp/README.md @@ -1,14 +1,11 @@

- Corda + Corda

-# CorDapp Template - Java +# NonFungible House Token DvP Sample CorDapp - Java -Welcome to the Java CorDapp template. The CorDapp template is a stubbed-out CorDapp that you can use to bootstrap -your own CorDapps. +This CorDapp servers a basic example to create, issue and perform a DvP (Delivery vs Payment) of an Evolvable NonFungible token in Corda utilizing the TokenSDK. -**This is the Java version of the CorDapp template. The Kotlin equivalent is -[here](https://github.com/corda/cordapp-template-kotlin/).** # Pre-Requisites @@ -16,24 +13,6 @@ See https://docs.corda.net/getting-set-up.html. # Usage -## Running tests inside IntelliJ - -We recommend editing your IntelliJ preferences so that you use the Gradle runner - this means that the quasar utils -plugin will make sure that some flags (like ``-javaagent`` - see below) are -set for you. - -To switch to using the Gradle runner: - -* Navigate to ``Build, Execution, Deployment -> Build Tools -> Gradle -> Runner`` (or search for `runner`) - * Windows: this is in "Settings" - * MacOS: this is in "Preferences" -* Set "Delegate IDE build/run actions to gradle" to true -* Set "Run test using:" to "Gradle Test Runner" - -If you would prefer to use the built in IntelliJ JUnit test runner, you can run ``gradlew installQuasar`` which will -copy your quasar JAR file to the lib directory. You will then need to specify ``-javaagent:lib/quasar.jar`` -and set the run directory to the project root directory for each test. - ## Running the nodes See https://docs.corda.net/tutorial-cordapp.html#running-the-example-cordapp. @@ -49,94 +28,36 @@ When started via the command line, each node will display an interactive shell: Tue Nov 06 11:58:13 GMT 2018>>> -You can use this shell to interact with your node. For example, enter `run networkMapSnapshot` to see a list of -the other nodes on the network: - - Tue Nov 06 11:58:13 GMT 2018>>> run networkMapSnapshot - [ - { - "addresses" : [ "localhost:10002" ], - "legalIdentitiesAndCerts" : [ "O=Notary, L=London, C=GB" ], - "platformVersion" : 3, - "serial" : 1541505484825 - }, - { - "addresses" : [ "localhost:10005" ], - "legalIdentitiesAndCerts" : [ "O=PartyA, L=London, C=GB" ], - "platformVersion" : 3, - "serial" : 1541505382560 - }, - { - "addresses" : [ "localhost:10008" ], - "legalIdentitiesAndCerts" : [ "O=PartyB, L=New York, C=US" ], - "platformVersion" : 3, - "serial" : 1541505384742 - } - ] - - Tue Nov 06 12:30:11 GMT 2018>>> - -You can find out more about the node shell [here](https://docs.corda.net/shell.html). - -### Client - -`clients/src/main/java/com/template/Client.java` defines a simple command-line client that connects to a node via RPC -and prints a list of the other nodes on the network. - -#### Running the client - -##### Via the command line - -Run the `runTemplateClient` Gradle task. By default, it connects to the node with RPC address `localhost:10006` with -the username `user1` and the password `test`. - -##### Via IntelliJ - -Run the `Run Template Client` run configuration. By default, it connects to the node with RPC address `localhost:10006` -with the username `user1` and the password `test`. - -### Webserver - -`clients/src/main/java/com/template/webserver/` defines a simple Spring webserver that connects to a node via RPC and -allows you to interact with the node over HTTP. - -The API endpoints are defined here: - - clients/src/main/java/com/template/webserver/Controller.java - -And a static webpage is defined here: +You can use this shell to interact with your node. - clients/src/main/resources/static/ +First go to the shell of PartyA and issue some USD to Party C. We will need the fiat currency to exchange it for the house token. -#### Running the webserver + start FiatCurrencyIssueFlow currency: USD, amount: 100000000, recipient: PartyC -##### Via the command line +We can now go to the shell of PartyC and check the amount of USD issued. Since fiat currency is a fungible token we can query the vault for FungibleToken states. -Run the `runTemplateServer` Gradle task. By default, it connects to the node with RPC address `localhost:10006` with -the username `user1` and the password `test`, and serves the webserver on port `localhost:10050`. - -##### Via IntelliJ - -Run the `Run Template Server` run configuration. By default, it connects to the node with RPC address `localhost:10006` -with the username `user1` and the password `test`, and serves the webserver on port `localhost:10050`. - -#### Interacting with the webserver - -The static webpage is served on: - - http://localhost:10050 - -While the sole template endpoint is served on: - - http://localhost:10050/templateendpoint + run vaultQuery contractStateType: com.r3.corda.lib.tokens.contracts.states.FungibleToken -# Extending the template - -You should extend this template as follows: - -* Add your own state and contract definitions under `contracts/src/main/java/` -* Add your own flow definitions under `workflows/src/main/java/` -* Extend or replace the client and webserver under `clients/src/main/java/` +Once we have the USD issued to PartyC, we can Create and Issue the HouseToken to PartyB. Goto PartyA's shell to create and issue the house token. + + start HouseTokenCreateAndIssueFlow owner: PartyB, valuation: 100000 USD, noOfBedRooms: 2, constructionArea: 1000sqft, additionInfo: NA, address: Mumbai + +We can now check the issued house token in PartyB's vault. Since we issued it as a non-fungible token we can query the vault for non-fungible tokens. + + run vaultQuery contractStateType: com.r3.corda.lib.tokens.contracts.states.NonFungibleToken + +Note that HouseState token is an evolvable token which is a linear state, thus we can check PartyB's vault to view the evolvable token -For a guided example of how to extend this template, see the Hello, World! tutorial -[here](https://docs.corda.net/hello-world-introduction.html). + run vaultQuery contractStateType: corda.tokenSDK.samples.states.HouseState + +Note the linearId of the HouseState token from the previous query, we will need it to perform our DvP opearation. Goto PartyB's shell to initiate the token sale. + + start HouseSaleInitiatorFlow houseId: cad35ab4-bcdb-4efd-8c63-d08fbac236fb, buyer: PartyC + +We could now verify that the non-fungible token has been transferred to PartyC and some 100,000 USD from PartyC's vault has been transferred to PartyB. Run the below commands in PartyB and PartyC's shell to verify the same + + // Run on PartyB's shell + run vaultQuery contractStateType: com.r3.corda.lib.tokens.contracts.states.FungibleToken + // Run on PartyC's shell + run vaultQuery contractStateType: com.r3.corda.lib.tokens.contracts.states.NonFungibleToken + \ No newline at end of file From 7ef9695425ccdbef94c721cef4cc62de50f9e58a Mon Sep 17 00:00:00 2001 From: Ashutosh Meher Date: Wed, 10 Jul 2019 10:37:50 +0530 Subject: [PATCH 03/20] partyc added in build.gradle --- non-fungible-token-dvp/build.gradle | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/non-fungible-token-dvp/build.gradle b/non-fungible-token-dvp/build.gradle index 5a7f0ea70..8afc41e5f 100644 --- a/non-fungible-token-dvp/build.gradle +++ b/non-fungible-token-dvp/build.gradle @@ -137,6 +137,15 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { } rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] } + node { + name "O=PartyC,L=Mumbai,C=IN" + p2pPort 10012 + rpcSettings { + address("localhost:10013") + adminAddress("localhost:10052") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } } task installQuasar(type: Copy) { From 6592089d12e9d13b181bdb97051a740dcd788751 Mon Sep 17 00:00:00 2001 From: Ashutosh Meher Date: Wed, 10 Jul 2019 10:41:42 +0530 Subject: [PATCH 04/20] Contract Comments --- .../java/corda/tokenSDK/samples/contracts/HouseContract.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/contracts/HouseContract.java b/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/contracts/HouseContract.java index 7cfc6b49d..262882a39 100644 --- a/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/contracts/HouseContract.java +++ b/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/contracts/HouseContract.java @@ -16,12 +16,12 @@ public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentExceptio @Override public void additionalCreateChecks(@NotNull LedgerTransaction tx) { - + // Write contract validation logic to be performed while creation of token } @Override public void additionalUpdateChecks(@NotNull LedgerTransaction tx) { - + // Write contract validation logic to be performed while updation of token } } From 5620737a3fd83661f5110648eb459df4ceb2b348 Mon Sep 17 00:00:00 2001 From: Ashutosh Meher Date: Wed, 10 Jul 2019 10:54:47 +0530 Subject: [PATCH 05/20] code cleanup --- .../src/main/java/corda/tokenSDK/samples/states/HouseState.java | 1 - 1 file changed, 1 deletion(-) diff --git a/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/states/HouseState.java b/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/states/HouseState.java index 553dbed95..03c48507d 100644 --- a/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/states/HouseState.java +++ b/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/states/HouseState.java @@ -3,7 +3,6 @@ import com.google.common.collect.ImmutableList; import com.r3.corda.lib.tokens.contracts.states.EvolvableTokenType; import com.r3.corda.lib.tokens.contracts.types.TokenPointer; -import com.r3.corda.lib.tokens.money.FiatCurrency; import corda.tokenSDK.samples.contracts.HouseContract; import net.corda.core.contracts.Amount; import net.corda.core.contracts.BelongsToContract; From 61c492c5459e9e707350a78078c48533c6165db1 Mon Sep 17 00:00:00 2001 From: Ashutosh Meher Date: Wed, 10 Jul 2019 12:04:11 +0530 Subject: [PATCH 06/20] Update House Valuation Flow --- non-fungible-token-dvp/README.md | 5 ++- .../samples/contracts/HouseContract.java | 8 +++- .../tokenSDK/samples/states/HouseState.java | 8 +++- .../flows/UpdateHouseValuationFlow.java | 43 +++++++++++++++++++ 4 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/UpdateHouseValuationFlow.java diff --git a/non-fungible-token-dvp/README.md b/non-fungible-token-dvp/README.md index bd9ecf866..a367f5e5c 100644 --- a/non-fungible-token-dvp/README.md +++ b/non-fungible-token-dvp/README.md @@ -60,4 +60,7 @@ We could now verify that the non-fungible token has been transferred to PartyC a run vaultQuery contractStateType: com.r3.corda.lib.tokens.contracts.states.FungibleToken // Run on PartyC's shell run vaultQuery contractStateType: com.r3.corda.lib.tokens.contracts.states.NonFungibleToken - \ No newline at end of file + +Since our house is an evolvable token, we should be able to update the properties of our house. To update the valuation of the house token go to PartyA's shell and start the UpdateHouseValuationFlow + + start UpdateHouseValuationFlow houseId: cad35ab4-bcdb-4efd-8c63-d08fbac236fb, newValuation: 100000 USD \ No newline at end of file diff --git a/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/contracts/HouseContract.java b/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/contracts/HouseContract.java index 262882a39..feaa105b8 100644 --- a/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/contracts/HouseContract.java +++ b/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/contracts/HouseContract.java @@ -1,6 +1,7 @@ package corda.tokenSDK.samples.contracts; import com.r3.corda.lib.tokens.contracts.EvolvableTokenContract; +import corda.tokenSDK.samples.states.HouseState; import net.corda.core.contracts.CommandData; import net.corda.core.contracts.Contract; import net.corda.core.transactions.LedgerTransaction; @@ -11,12 +12,17 @@ public class HouseContract extends EvolvableTokenContract implements Contract { @Override public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { - + HouseState outputState = (HouseState) tx.getOutput(0); + if(!(tx.getCommand(0).getSigners().contains(outputState.getIssuer().getOwningKey()))) + throw new IllegalArgumentException("Issuer Signature Required"); } @Override public void additionalCreateChecks(@NotNull LedgerTransaction tx) { // Write contract validation logic to be performed while creation of token + HouseState outputState = (HouseState) tx.getOutput(0); + if(outputState.getValuation().getQuantity() < 1) + throw new IllegalArgumentException("Valuation must be greater than zero"); } @Override diff --git a/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/states/HouseState.java b/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/states/HouseState.java index 03c48507d..8043d0ef3 100644 --- a/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/states/HouseState.java +++ b/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/states/HouseState.java @@ -19,6 +19,7 @@ public class HouseState extends EvolvableTokenType { private final UniqueIdentifier linearId; private final List maintainers; + private final Party issuer; private final int fractionDigits = 0; //Properties of House State @@ -36,6 +37,7 @@ public HouseState(UniqueIdentifier linearId, List maintainers, Amount getValuation() { return valuation; } + public Party getIssuer() { + return issuer; + } + @Override public int getFractionDigits() { return fractionDigits; @@ -79,4 +85,4 @@ public TokenPointer toPointer(){ LinearPointer linearPointer = new LinearPointer<>(linearId, HouseState.class); return new TokenPointer<>(linearPointer, fractionDigits); } -} +} \ No newline at end of file diff --git a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/UpdateHouseValuationFlow.java b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/UpdateHouseValuationFlow.java new file mode 100644 index 000000000..012668729 --- /dev/null +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/UpdateHouseValuationFlow.java @@ -0,0 +1,43 @@ +package corda.tokenSDK.samples.contracts.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.google.common.collect.ImmutableList; +import com.r3.corda.lib.tokens.workflows.flows.evolvable.UpdateEvolvableToken; +import corda.tokenSDK.samples.states.HouseState; +import net.corda.core.contracts.Amount; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; + +import java.util.Currency; +import java.util.UUID; + +@StartableByRPC +public class UpdateHouseValuationFlow extends FlowLogic { + + private final String houseId; + private final Amount newValuation; + + public UpdateHouseValuationFlow(String houseId, Amount newValuation) { + this.houseId = houseId; + this.newValuation = newValuation; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + UUID uuid = UUID.fromString(houseId); + QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria( + null, ImmutableList.of(uuid), null, Vault.StateStatus.UNCONSUMED); + StateAndRef input = getServiceHub().getVaultService(). + queryBy(HouseState.class, queryCriteria).getStates().get(0); + HouseState houseState = input.getState().getData(); + HouseState outputState = new HouseState(houseState.getLinearId(), houseState.getMaintainers(), newValuation, + houseState.getNoOfBedRooms(), houseState.getConstructionArea(), houseState.getAdditionInfo(), houseState.getAddress()); + return subFlow(new UpdateEvolvableToken(input, outputState)); + } +} From 71b1d566b7cbfe91ecea2fd0fe6674aa94fa2a9c Mon Sep 17 00:00:00 2001 From: Ashutosh Meher Date: Wed, 10 Jul 2019 12:14:04 +0530 Subject: [PATCH 07/20] corda image update --- non-fungible-token-dvp/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/non-fungible-token-dvp/README.md b/non-fungible-token-dvp/README.md index a367f5e5c..21ffa5c1e 100644 --- a/non-fungible-token-dvp/README.md +++ b/non-fungible-token-dvp/README.md @@ -1,5 +1,5 @@

- Corda + Corda

# NonFungible House Token DvP Sample CorDapp - Java From c8b42152f9851a2d7b32ef6338e60aad669b964e Mon Sep 17 00:00:00 2001 From: Ashutosh Meher Date: Wed, 10 Jul 2019 12:28:57 +0530 Subject: [PATCH 08/20] readme update --- non-fungible-token-dvp/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/non-fungible-token-dvp/README.md b/non-fungible-token-dvp/README.md index 21ffa5c1e..22ced15d1 100644 --- a/non-fungible-token-dvp/README.md +++ b/non-fungible-token-dvp/README.md @@ -40,7 +40,7 @@ We can now go to the shell of PartyC and check the amount of USD issued. Since f Once we have the USD issued to PartyC, we can Create and Issue the HouseToken to PartyB. Goto PartyA's shell to create and issue the house token. - start HouseTokenCreateAndIssueFlow owner: PartyB, valuation: 100000 USD, noOfBedRooms: 2, constructionArea: 1000sqft, additionInfo: NA, address: Mumbai + start HouseTokenCreateAndIssueFlow owner: PartyB, valuation: 10000 USD, noOfBedRooms: 2, constructionArea: 1000sqft, additionInfo: NA, address: Mumbai We can now check the issued house token in PartyB's vault. Since we issued it as a non-fungible token we can query the vault for non-fungible tokens. From 9d2e923b06f2dd3f7626b90f0e6e74f1ed56f315 Mon Sep 17 00:00:00 2001 From: Ashutosh Meher Date: Wed, 10 Jul 2019 12:31:07 +0530 Subject: [PATCH 09/20] readme update --- non-fungible-token-dvp/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/non-fungible-token-dvp/README.md b/non-fungible-token-dvp/README.md index 22ced15d1..a65a56efd 100644 --- a/non-fungible-token-dvp/README.md +++ b/non-fungible-token-dvp/README.md @@ -26,7 +26,7 @@ When started via the command line, each node will display an interactive shell: Welcome to the Corda interactive shell. Useful commands include 'help' to see what is available, and 'bye' to shut down the node. - Tue Nov 06 11:58:13 GMT 2018>>> + Tue July 09 11:58:13 GMT 2019>>> You can use this shell to interact with your node. From 081ac833a7aad7e7674c950531dc65d53101b921 Mon Sep 17 00:00:00 2001 From: Ashutosh Meher Date: Wed, 10 Jul 2019 12:40:19 +0530 Subject: [PATCH 10/20] readme update --- non-fungible-token-dvp/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/non-fungible-token-dvp/README.md b/non-fungible-token-dvp/README.md index a65a56efd..6a9ed4a1c 100644 --- a/non-fungible-token-dvp/README.md +++ b/non-fungible-token-dvp/README.md @@ -11,6 +11,8 @@ This CorDapp servers a basic example to create, issue and perform a DvP (Deliver See https://docs.corda.net/getting-set-up.html. +For a brief introduction to Token SDK in Corda, see + # Usage ## Running the nodes From 936ae8d1b35026a6347d561a0dc7f349d8101adb Mon Sep 17 00:00:00 2001 From: ashutoshmeher-r3 Date: Mon, 15 Jul 2019 16:55:40 +0530 Subject: [PATCH 11/20] code comments --- .../samples/contracts/HouseContract.java | 6 ++-- .../tokenSDK/samples/states/HouseState.java | 3 +- .../flows/FiatCurrencyIssueFlow.java | 6 ++++ .../flows/HouseSaleInitiatorFlow.java | 31 +++++++++++++------ .../flows/HouseSaleResponderFlow.java | 17 ++++++++++ .../flows/HouseTokenCreateAndIssueFlow.java | 22 +++++++++++-- .../flows/UpdateHouseValuationFlow.java | 4 +++ 7 files changed, 75 insertions(+), 14 deletions(-) diff --git a/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/contracts/HouseContract.java b/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/contracts/HouseContract.java index feaa105b8..23f32b2e9 100644 --- a/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/contracts/HouseContract.java +++ b/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/contracts/HouseContract.java @@ -2,12 +2,14 @@ import com.r3.corda.lib.tokens.contracts.EvolvableTokenContract; import corda.tokenSDK.samples.states.HouseState; -import net.corda.core.contracts.CommandData; import net.corda.core.contracts.Contract; import net.corda.core.transactions.LedgerTransaction; import org.jetbrains.annotations.NotNull; - +/* +* HouseContract governs the evolution of HouseState token. Evolvable tokens must extend the EvolvableTokenContract abstract class, it defines the +* additionalCreateChecks and additionalCreateChecks method to add custom logic to validate while creation adn updation of evolvable tokens respectively. +* */ public class HouseContract extends EvolvableTokenContract implements Contract { @Override diff --git a/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/states/HouseState.java b/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/states/HouseState.java index 8043d0ef3..51ac8e551 100644 --- a/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/states/HouseState.java +++ b/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/states/HouseState.java @@ -22,7 +22,7 @@ public class HouseState extends EvolvableTokenType { private final Party issuer; private final int fractionDigits = 0; - //Properties of House State + //Properties of House State. Some of these values may evolve over time. private final Amount valuation; private final int noOfBedRooms; private final String constructionArea; @@ -81,6 +81,7 @@ public List getMaintainers() { return ImmutableList.copyOf(maintainers); } + /* This method returns a TokenPointer by using the linear Id of the evolvable state */ public TokenPointer toPointer(){ LinearPointer linearPointer = new LinearPointer<>(linearId, HouseState.class); return new TokenPointer<>(linearPointer, fractionDigits); diff --git a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/FiatCurrencyIssueFlow.java b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/FiatCurrencyIssueFlow.java index 8f124f9a0..66aea21c4 100644 --- a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/FiatCurrencyIssueFlow.java +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/FiatCurrencyIssueFlow.java @@ -12,6 +12,10 @@ import java.util.Currency; +/** + * Flow class to issue fiat currency. FiatCurrency is defined in the TokenSDK and is issued as a Fungible Token. This constructor takes the currecy code + * for the currency to be issued, the amount of the currency to be issued and the recipient as input parameters. + */ @StartableByRPC public class FiatCurrencyIssueFlow extends FlowLogic { @@ -28,7 +32,9 @@ public FiatCurrencyIssueFlow(String currency, Long amount, Party recipient) { @Override @Suspendable public SignedTransaction call() throws FlowException { + /* Create an instance of the fiat currency token */ FiatCurrency token = new FiatCurrency(Currency.getInstance(currency)); + /* Issue the required amount of the token to the recipient */ return subFlow(new IssueTokens<>(new Amount<>(amount, token), getOurIdentity(), recipient)); } diff --git a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleInitiatorFlow.java b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleInitiatorFlow.java index 532e9210b..8b2c28132 100644 --- a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleInitiatorFlow.java +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleInitiatorFlow.java @@ -2,20 +2,11 @@ import co.paralleluniverse.fibers.Suspendable; import com.google.common.collect.ImmutableList; -import com.r3.corda.lib.tokens.contracts.states.EvolvableTokenType; import com.r3.corda.lib.tokens.contracts.states.FungibleToken; -import com.r3.corda.lib.tokens.contracts.types.TokenPointer; import com.r3.corda.lib.tokens.money.FiatCurrency; -import com.r3.corda.lib.tokens.workflows.flows.move.MoveFungibleTokensFlow; -import com.r3.corda.lib.tokens.workflows.flows.move.MoveNonFungibleTokensFlow; import com.r3.corda.lib.tokens.workflows.flows.move.MoveTokensUtilitiesKt; -import com.r3.corda.lib.tokens.workflows.flows.rpc.IssueTokens; -import com.r3.corda.lib.tokens.workflows.flows.rpc.MoveNonFungibleTokens; import com.r3.corda.lib.tokens.workflows.internal.flows.distribution.UpdateDistributionListFlow; -import com.r3.corda.lib.tokens.workflows.types.PartyAndToken; import corda.tokenSDK.samples.states.HouseState; -import net.corda.core.contracts.Amount; -import net.corda.core.contracts.LinearPointer; import net.corda.core.contracts.StateAndRef; import net.corda.core.flows.*; import net.corda.core.identity.Party; @@ -27,6 +18,10 @@ import java.util.List; import java.util.UUID; +/** + * Initiator Flow class to propose the sale of the house. The house token would be exchanged with an equivalent amount of fiat currency as mentioned in the + * valuation of the house. The flow taken the linearId of the house token and the buyer party as the input parameters. + * */ @InitiatingFlow @StartableByRPC public class HouseSaleInitiatorFlow extends FlowLogic { @@ -42,27 +37,45 @@ public HouseSaleInitiatorFlow(String houseId, Party buyer) { @Override @Suspendable public SignedTransaction call() throws FlowException { + /* Choose the notary for the transaction */ Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); UUID uuid = UUID.fromString(houseId); + + /* Fetch the house state from the vault using the vault query */ QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria( null, ImmutableList.of(uuid), null, Vault.StateStatus.UNCONSUMED); StateAndRef houseStateAndRef = getServiceHub().getVaultService(). queryBy(HouseState.class, queryCriteria).getStates().get(0); HouseState houseState = houseStateAndRef.getState().getData(); + /* Build the transaction builder */ TransactionBuilder txBuilder = new TransactionBuilder(notary); + + /* Create a move token proposal for the house token using the helper function provided by Token SDK. This would create the movement proposal and would + * be committed in the ledgers of parties once the transaction in finalized. + **/ MoveTokensUtilitiesKt.addMoveNonFungibleTokens(txBuilder, getServiceHub(), houseState.toPointer(), buyer); + /* Initiate a flow session with the buyer to send the house valuation and transfer of the fiat currency */ FlowSession buyerSession = initiateFlow(buyer); + // Send the house valuation to the buyer. buyerSession.send(houseState.getValuation()); + // Recieve inputStatesAndRef for the fiat currency exchange from the buyer, these would be inputs to the fiat currency exchange transaction. List>> inputs = subFlow(new ReceiveStateAndRefFlow<>(buyerSession)); + // Recieve output for the fiat currency from the buyer, this would contain the transfered amount from buyer to yourself List> moneyReceived = buyerSession.receive(List.class).unwrap(value -> value); + + /* Create a fiat currency proposal for the house token using the helper function provided by Token SDK. */ MoveTokensUtilitiesKt.addMoveTokens(txBuilder, inputs, moneyReceived); + /* Sign the transaction with your private */ SignedTransaction initialSignedTrnx = getServiceHub().signInitialTransaction(txBuilder, getOurIdentity().getOwningKey()); + /* Call the CollectSignaturesFlow to recieve signature of the buyer */ SignedTransaction signedTransaction = subFlow(new CollectSignaturesFlow(initialSignedTrnx, ImmutableList.of(buyerSession))); + /* Distribution list is a list of identities that should receive updates. For this mechanism to behave correctly we call the UpdateDistributionListFlow flow */ subFlow(new UpdateDistributionListFlow(signedTransaction)); + /* Call finality flow to notarise the transaction */ return subFlow(new FinalityFlow(signedTransaction, ImmutableList.of(buyerSession))); } } diff --git a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleResponderFlow.java b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleResponderFlow.java index 3c7319146..c79ecbf58 100644 --- a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleResponderFlow.java +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleResponderFlow.java @@ -16,6 +16,10 @@ import java.util.Currency; import java.util.List; +/* +* Responder Flow for the sale of house in exchage of fiat-currency. This flow receives the valuation of the house from the seller and transfer the equivalent +* amount of fiat currency to the seller. +* */ @InitiatedBy(HouseSaleInitiatorFlow.class) public class HouseSaleResponderFlow extends FlowLogic { @@ -29,14 +33,27 @@ public HouseSaleResponderFlow(FlowSession counterpartySession) { @Suspendable public SignedTransaction call() throws FlowException { + /* Recieve the valuation of the house */ Amount price = counterpartySession.receive(Amount.class).unwrap(amount -> amount); + /* Create instance of the fiat currecy token */ Amount priceToken = new Amount<>(price.getQuantity(), new FiatCurrency(price.getToken())); + + /* Create an instance of the TokenSelection object, it is used to select the token from the vault and generate the proposal for the movement of the token + * The constructor takes the service hub to perform vault query, the max-number of retries, the retry sleep interval, and the retry sleep cap interval. This + * is a temporary solution till in-memory token selection in implemented. + * */ TokenSelection tokenSelection = new TokenSelection(getServiceHub(), 8, 100, 2000); + + /* + * Generate the move proposal, it returns the input-output pair for the fiat currency transfer, which we need to send to the Initiator. + * */ PartyAndAmount partyAndAmount = new PartyAndAmount<>(counterpartySession.getCounterparty(), priceToken); Pair>>, List>> inputsAndOutputs = tokenSelection.generateMove(getRunId().getUuid(), ImmutableList.of(partyAndAmount), getOurIdentity(), null); + /* Call SendStateAndRefFlow to send the inputs to the Initiator*/ subFlow(new SendStateAndRefFlow(counterpartySession, inputsAndOutputs.getFirst())); + /* Send the output generated from the fiat currency move proposal to the initiator */ counterpartySession.send(inputsAndOutputs.getSecond()); subFlow(new SignTransactionFlow(counterpartySession) { diff --git a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseTokenCreateAndIssueFlow.java b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseTokenCreateAndIssueFlow.java index d3af1533b..670fecec6 100644 --- a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseTokenCreateAndIssueFlow.java +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseTokenCreateAndIssueFlow.java @@ -2,12 +2,10 @@ import co.paralleluniverse.fibers.Suspendable; import com.google.common.collect.ImmutableList; -import com.r3.corda.lib.tokens.contracts.states.FungibleToken; import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken; import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType; import com.r3.corda.lib.tokens.contracts.types.TokenPointer; import com.r3.corda.lib.tokens.contracts.utilities.TransactionUtilitiesKt; -import com.r3.corda.lib.tokens.money.FiatCurrency; import com.r3.corda.lib.tokens.workflows.flows.evolvable.CreateEvolvableToken; import com.r3.corda.lib.tokens.workflows.flows.rpc.IssueTokens; import corda.tokenSDK.samples.states.HouseState; @@ -22,6 +20,11 @@ import java.util.Currency; import java.util.UUID; +/** + * Flow to create and issue house token. TokenSDK provides some in-build flows which could be called to Create and Issue tokens. + * This flow should be called by the issuer of the token. The constructor take the owner and other properties of the house as the as input parameters, + * it first create the house token onto the issuer's ledger and then issues it to the owner. +**/ @StartableByRPC public class HouseTokenCreateAndIssueFlow extends FlowLogic { @@ -45,15 +48,30 @@ public HouseTokenCreateAndIssueFlow(Party owner, Amount valuation, int @Suspendable public SignedTransaction call() throws FlowException { + /* Choose the notary for the transaction */ Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + /* Get a reference of own identity */ Party issuer = getOurIdentity(); + + /* Construct the output state */ final HouseState houseState = new HouseState(UniqueIdentifier.Companion.fromString(UUID.randomUUID().toString()), ImmutableList.of(issuer), valuation, noOfBedRooms, constructionArea, additionInfo, address); + /* Create the house token. TokenSDK provides the CreateEvolvableToken flow which could be called to create an evolvable token in the ledger.*/ subFlow(new CreateEvolvableToken<>(houseState, notary)); + /* + * Create an instance of IssuedTokenType, it is used by our Non-Fungible token which would be issued to the owner. Note that the IssuedTokenType takes + * a TokenPointer as an input, since EvolvableTokenType is not TokenType, but is a LinearState. This is done to separate the state info from the token + * so that the state can evolve independently. + * IssuedTokenType is a wrapper around the TokenType and the issuer. + * */ IssuedTokenType> issuedHouseToken = new IssuedTokenType<>(issuer, houseState.toPointer()); + + /* Create an instance of the non-fungible house token with the owner as the token holder. The last paramter is a hash of the jar containing the TokenType, use the helper function to fetch it. */ NonFungibleToken> houseToken = new NonFungibleToken<>(issuedHouseToken, owner, UniqueIdentifier.Companion.fromString(UUID.randomUUID().toString()), TransactionUtilitiesKt.getAttachmentIdForGenericParam(houseState.toPointer())); + + /* Issue the house token by calling the IssueToken flow provided with the TokenSDK */ return subFlow(new IssueTokens<>(houseToken)); } } diff --git a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/UpdateHouseValuationFlow.java b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/UpdateHouseValuationFlow.java index 012668729..b7a50b96a 100644 --- a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/UpdateHouseValuationFlow.java +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/UpdateHouseValuationFlow.java @@ -16,6 +16,10 @@ import java.util.Currency; import java.util.UUID; +/** + * Flow class to update the evolvable Token. TokenSDK provides the UpdateEvolvableToken flow which could be called with the input and outputs. It takes care of the + * building the transaction and performing the updates as well as notifying the maintainers. + */ @StartableByRPC public class UpdateHouseValuationFlow extends FlowLogic { From b40cdf9751b6dcabf3dd9c910f4816128228bf5e Mon Sep 17 00:00:00 2001 From: ashutoshmeher-r3 Date: Mon, 22 Jul 2019 15:24:12 +0530 Subject: [PATCH 12/20] blog link added --- non-fungible-token-dvp/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/non-fungible-token-dvp/README.md b/non-fungible-token-dvp/README.md index 6a9ed4a1c..57e75a5d0 100644 --- a/non-fungible-token-dvp/README.md +++ b/non-fungible-token-dvp/README.md @@ -11,7 +11,7 @@ This CorDapp servers a basic example to create, issue and perform a DvP (Deliver See https://docs.corda.net/getting-set-up.html. -For a brief introduction to Token SDK in Corda, see +For a brief introduction to Token SDK in Corda, see https://medium.com/corda/introduction-to-token-sdk-in-corda-9b4dbcf71025 # Usage From a45debb34e3889819e09f683cfe1a9b3c4882e31 Mon Sep 17 00:00:00 2001 From: snehadamle Date: Sat, 27 Jul 2019 13:25:43 +0530 Subject: [PATCH 13/20] Fungible/NonFungible basic Tokens Flows --- real-estate-token/.gitignore | 78 +++++++ real-estate-token/LICENCE | 13 ++ real-estate-token/README.md | 74 +++++++ real-estate-token/TRADEMARK | 4 + real-estate-token/build.gradle | 147 +++++++++++++ real-estate-token/clients/build.gradle | 47 ++++ .../src/main/java/com/template/Client.java | 36 ++++ .../com/template/webserver/Controller.java | 27 +++ .../template/webserver/NodeRPCConnection.java | 48 +++++ .../java/com/template/webserver/Starter.java | 23 ++ .../clients/src/main/resources/static/app.js | 3 + .../src/main/resources/static/index.html | 10 + real-estate-token/config/dev/log4j2.xml | 59 ++++++ real-estate-token/config/test/log4j2.xml | 20 ++ real-estate-token/contracts/build.gradle | 26 +++ .../RealEstateEvolvableTokenTypeContract.java | 21 ++ .../states/RealEstateEvolvableTokenType.java | 74 +++++++ .../com/template/contracts/ContractTests.java | 13 ++ real-estate-token/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54333 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + real-estate-token/gradlew | 172 +++++++++++++++ real-estate-token/gradlew.bat | 84 ++++++++ real-estate-token/settings.gradle | 3 + real-estate-token/workflows/build.gradle | 61 ++++++ .../java/com/template/DriverBasedTest.java | 48 +++++ .../RealEstateEvolvableFungibleTokenFlow.java | 200 ++++++++++++++++++ ...alEstateEvolvableNonFungibleTokenFlow.java | 183 ++++++++++++++++ .../test/java/com/template/ContractTests.java | 13 ++ .../src/test/java/com/template/FlowTests.java | 29 +++ .../test/java/com/template/NodeDriver.java | 39 ++++ 31 files changed, 1564 insertions(+) create mode 100644 real-estate-token/.gitignore create mode 100644 real-estate-token/LICENCE create mode 100644 real-estate-token/README.md create mode 100644 real-estate-token/TRADEMARK create mode 100644 real-estate-token/build.gradle create mode 100644 real-estate-token/clients/build.gradle create mode 100644 real-estate-token/clients/src/main/java/com/template/Client.java create mode 100644 real-estate-token/clients/src/main/java/com/template/webserver/Controller.java create mode 100644 real-estate-token/clients/src/main/java/com/template/webserver/NodeRPCConnection.java create mode 100644 real-estate-token/clients/src/main/java/com/template/webserver/Starter.java create mode 100644 real-estate-token/clients/src/main/resources/static/app.js create mode 100644 real-estate-token/clients/src/main/resources/static/index.html create mode 100644 real-estate-token/config/dev/log4j2.xml create mode 100644 real-estate-token/config/test/log4j2.xml create mode 100644 real-estate-token/contracts/build.gradle create mode 100644 real-estate-token/contracts/src/main/java/com/template/RealEstateEvolvableTokenTypeContract.java create mode 100644 real-estate-token/contracts/src/main/java/com/template/states/RealEstateEvolvableTokenType.java create mode 100644 real-estate-token/contracts/src/test/java/com/template/contracts/ContractTests.java create mode 100644 real-estate-token/gradle.properties create mode 100644 real-estate-token/gradle/wrapper/gradle-wrapper.jar create mode 100644 real-estate-token/gradle/wrapper/gradle-wrapper.properties create mode 100755 real-estate-token/gradlew create mode 100644 real-estate-token/gradlew.bat create mode 100644 real-estate-token/settings.gradle create mode 100644 real-estate-token/workflows/build.gradle create mode 100644 real-estate-token/workflows/src/integrationTest/java/com/template/DriverBasedTest.java create mode 100644 real-estate-token/workflows/src/main/java/com/template/flows/RealEstateEvolvableFungibleTokenFlow.java create mode 100644 real-estate-token/workflows/src/main/java/com/template/flows/RealEstateEvolvableNonFungibleTokenFlow.java create mode 100644 real-estate-token/workflows/src/test/java/com/template/ContractTests.java create mode 100644 real-estate-token/workflows/src/test/java/com/template/FlowTests.java create mode 100644 real-estate-token/workflows/src/test/java/com/template/NodeDriver.java diff --git a/real-estate-token/.gitignore b/real-estate-token/.gitignore new file mode 100644 index 000000000..4202532ed --- /dev/null +++ b/real-estate-token/.gitignore @@ -0,0 +1,78 @@ +# Eclipse, ctags, Mac metadata, log files +.classpath +.project +.settings +tags +.DS_Store +*.log +*.log.gz +*.orig + +.gradle + +# General build files +**/build/* +!docs/build/* + +lib/dokka.jar + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio + +*.iml + +## Directory-based project format: +#.idea + +# if you remove the above rule, at least ignore the following: + +# Specific files to avoid churn +.idea/*.xml +.idea/copyright +.idea/jsLibraryMappings.xml + +# User-specific stuff: +.idea/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/dataSources.ids +.idea/dataSources.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ +/workflows/out/ +/contracts/out/ +clients/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties + +# docs related +docs/virtualenv/ + +# if you use the installQuasar task +lib \ No newline at end of file diff --git a/real-estate-token/LICENCE b/real-estate-token/LICENCE new file mode 100644 index 000000000..3ff572d11 --- /dev/null +++ b/real-estate-token/LICENCE @@ -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. \ No newline at end of file diff --git a/real-estate-token/README.md b/real-estate-token/README.md new file mode 100644 index 000000000..bb6cd8cb0 --- /dev/null +++ b/real-estate-token/README.md @@ -0,0 +1,74 @@ +

+ Corda +

+ +# Fungible and NonFungible RealEstate Token Sample CorDapp - Java + +This cordapp servers as a basic example to create, issue, move, redeem fungible and non fungible tokens in Corda utilizing the TokenSDK. + +# Pre-Requisites + +See https://docs.corda.net/getting-set-up.html. + +For a brief introduction to Token SDK in Corda, see https://medium.com/corda/introduction-to-token-sdk-in-corda-9b4dbcf71025 + +# Usage + +## Running the nodes + +See https://docs.corda.net/tutorial-cordapp.html#running-the-example-cordapp. + +## Interacting with the nodes + +### Shell + +When started via the command line, each node will display an interactive shell: + + Welcome to the Corda interactive shell. + Useful commands include 'help' to see what is available, and 'bye' to shut down the node. + + Tue July 09 11:58:13 GMT 2019>>> + +You can use this shell to interact with your node. + +### Fungible Tokens + +Create house on the ledger using PartA's terminal + + start CreateEvolvableFungibleTokenFlow valuation : 100 + +This will create a linear state of type RealEstate in A's vault + +Get the uuid of the house type from PartyA's terminal by hitting below command. + + run vaultQuery contractStateType : com.template.states.RealEstateEvolvableTokenType + +PartyA will now issue some tokens to PartB. Fire below command via PartyA's terminal using uuid collected from previous step. + + start IssueEvolvableFungibleTokenFlow tokenId : 680922c8-a0c8-4440-9d72-7dfefb380661 , quantity : 10 , holder : PartyB + +Since PartyB now has 10 tokens, Move tokens to PartyC from PartyB s terminal + + start MoveEvolvableFungibleTokenFlow tokenId : 680922c8-a0c8-4440-9d72-7dfefb380661 , holder : PartyC , quantity : 5 + +Redeem tokens via PartyC's terminal specifying the issuer + + start RedeemHouseFungibleTokenFlow tokenId : 680922c8-a0c8-4440-9d72-7dfefb380661 , issuer : PartyA , quantity : 5 + +### Non Fungible Tokens + +Create house on the ledger on PartyA's terminal + + start CreateEvolvableTokenFlow valuation : 100 + +Issue tokens off the created house from PartyA s terminal to PartyB + + start IssueEvolvableFungibleTokenFlow tokenId : 79332247-61d2-4d00-bb1f-d62416cf4920 , quantity : 10 , holder : PartyB + +Move tokens to PartyC from PartyB s terminal + + start MoveEvolvableFungibleTokenFlow tokenId : 79332247-61d2-4d00-bb1f-d62416cf4920 , holder : Party C , quantity : 5 + +Redeem tokens via PartyC's terminal + + start RedeemHouseFungibleTokenFlow tokenId : 79332247-61d2-4d00-bb1f-d62416cf4920, issuer : PartyA , quantity : 5 diff --git a/real-estate-token/TRADEMARK b/real-estate-token/TRADEMARK new file mode 100644 index 000000000..d2e056b5f --- /dev/null +++ b/real-estate-token/TRADEMARK @@ -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 at +https://www.r3.com/trademark-policy/. \ No newline at end of file diff --git a/real-estate-token/build.gradle b/real-estate-token/build.gradle new file mode 100644 index 000000000..4f9211958 --- /dev/null +++ b/real-estate-token/build.gradle @@ -0,0 +1,147 @@ +buildscript { + ext { + corda_release_group = 'net.corda' + corda_release_version = '4.1' + tokens_release_group = 'com.r3.corda.lib.tokens' + tokens_release_version = '1.1-SNAPSHOT' + corda_gradle_plugins_version = '4.0.42' + junit_version = '4.12' + quasar_version = '0.7.10' + spring_boot_version = '2.0.2.RELEASE' + spring_boot_gradle_plugin_version = '2.0.2.RELEASE' + slf4j_version = '1.7.25' + log4j_version = '2.11.2' + corda_platform_version = '4' + } + + repositories { + mavenLocal() + mavenCentral() + jcenter() + maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } + } + + dependencies { + classpath "net.corda.plugins:cordapp:$corda_gradle_plugins_version" + classpath "net.corda.plugins:cordformation:$corda_gradle_plugins_version" + classpath "net.corda.plugins:quasar-utils:$corda_gradle_plugins_version" + classpath "org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_gradle_plugin_version" + } +} + +allprojects { + apply plugin: 'java' + + repositories { + mavenLocal() + jcenter() + mavenCentral() + maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } + maven { url 'https://jitpack.io' } + // Can be removed post-release - used to get nightly snapshot build. + maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-tokens-dev' } + } + + tasks.withType(JavaCompile) { + options.compilerArgs << "-parameters" // Required by Corda's serialisation framework. + } + + jar { + // This makes the JAR's SHA-256 hash repeatable. + preserveFileTimestamps = false + reproducibleFileOrder = true + } +} + + +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + info { + name "CorDapp Template" + vendor "Corda Open Source" + targetPlatformVersion corda_platform_version.toInteger() + minimumPlatformVersion corda_platform_version.toInteger() + } +} + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } +} + +dependencies { + testCompile "junit:junit:$junit_version" + + // Corda dependencies. + cordaCompile "$corda_release_group:corda-core:$corda_release_version" + cordaCompile "$corda_release_group:corda-node-api:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":workflows") + cordapp project(":contracts") + + // For logging + cordaCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" + cordaCompile "org.apache.logging.log4j:log4j-web:${log4j_version}" + cordaCompile "org.slf4j:jul-to-slf4j:$slf4j_version" + + // Token SDK dependencies. + cordapp "$tokens_release_group:tokens-contracts:$tokens_release_version" + cordapp "$tokens_release_group:tokens-workflows:$tokens_release_version" + cordapp "$tokens_release_group:tokens-money:$tokens_release_version" +} + +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + nodeDefaults { + projectCordapp { + deploy = false + } + cordapp("$tokens_release_group:tokens-contracts:$tokens_release_version") + cordapp("$tokens_release_group:tokens-workflows:$tokens_release_version") + cordapp("$tokens_release_group:tokens-money:$tokens_release_version") + cordapp project(':contracts') + cordapp project(':workflows') + } + node { + name "O=Notary,L=London,C=GB" + notary = [validating : false] + p2pPort 10002 + rpcSettings { + address("localhost:10003") + adminAddress("localhost:10043") + } + cordapps = [] + } + node { + name "O=PartyA,L=London,C=GB" + p2pPort 10005 + rpcSettings { + address("localhost:10006") + adminAddress("localhost:10046") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=PartyB,L=New York,C=US" + p2pPort 10008 + rpcSettings { + address("localhost:10009") + adminAddress("localhost:10049") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } +} + +task installQuasar(type: Copy) { + destinationDir rootProject.file("lib") + from(configurations.quasar) { + rename 'quasar-core(.*).jar', 'quasar.jar' + } +} \ No newline at end of file diff --git a/real-estate-token/clients/build.gradle b/real-estate-token/clients/build.gradle new file mode 100644 index 000000000..783276424 --- /dev/null +++ b/real-estate-token/clients/build.gradle @@ -0,0 +1,47 @@ +apply plugin: 'org.springframework.boot' + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } +} + +dependencies { + // Corda dependencies. + compile "$corda_release_group:corda-rpc:$corda_release_version" + + // CorDapp dependencies. + compile project(":contracts") + compile project(":workflows") + + compile("org.springframework.boot:spring-boot-starter-websocket:$spring_boot_version") { + exclude group: "org.springframework.boot", module: "spring-boot-starter-logging" + } + + compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" + compile "org.apache.logging.log4j:log4j-web:${log4j_version}" + compile "org.slf4j:jul-to-slf4j:$slf4j_version" + + // Token SDK dependencies. + compile "com.r3.tokens-sdk:contract:1.0-SNAPSHOT" + compile "com.r3.tokens-sdk:workflow:1.0-SNAPSHOT" + compile "com.r3.tokens-sdk:money:1.0-SNAPSHOT" +} + +springBoot { + mainClassName = "com.template.webserver.Server" +} + +task runTemplateClient(type: JavaExec, dependsOn: assemble) { + classpath = sourceSets.main.runtimeClasspath + main = 'com.template.Client' + args 'localhost:10006', 'user1', 'test' +} + +task runTemplateServer(type: JavaExec, dependsOn: assemble) { + classpath = sourceSets.main.runtimeClasspath + main = 'com.template.webserver.Starter' + args '--server.port=10050', '--config.rpc.host=localhost', '--config.rpc.port=10006', '--config.rpc.username=user1', '--config.rpc.password=test' +} diff --git a/real-estate-token/clients/src/main/java/com/template/Client.java b/real-estate-token/clients/src/main/java/com/template/Client.java new file mode 100644 index 000000000..a69e5cd94 --- /dev/null +++ b/real-estate-token/clients/src/main/java/com/template/Client.java @@ -0,0 +1,36 @@ +package com.template; + +import net.corda.client.rpc.CordaRPCClient; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.node.NodeInfo; +import net.corda.core.utilities.NetworkHostAndPort; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import static net.corda.core.utilities.NetworkHostAndPort.parse; + +/** + * Connects to a Corda node via RPC and performs RPC operations on the node. + * + * The RPC connection is configured using command line arguments. + */ +public class Client { + private static final Logger logger = LoggerFactory.getLogger(Client.class); + + public static void main(String[] args) { + // Create an RPC connection to the node. + if (args.length != 3) throw new IllegalArgumentException("Usage: Client "); + final NetworkHostAndPort nodeAddress = parse(args[0]); + final String rpcUsername = args[1]; + final String rpcPassword = args[2]; + final CordaRPCClient client = new CordaRPCClient(nodeAddress); + final CordaRPCOps proxy = client.start(rpcUsername, rpcPassword).getProxy(); + + // Interact with the node. + // For example, here we print the nodes on the network. + final List nodes = proxy.networkMapSnapshot(); + logger.info("{}", nodes); + } +} \ No newline at end of file diff --git a/real-estate-token/clients/src/main/java/com/template/webserver/Controller.java b/real-estate-token/clients/src/main/java/com/template/webserver/Controller.java new file mode 100644 index 000000000..12ef4d9c3 --- /dev/null +++ b/real-estate-token/clients/src/main/java/com/template/webserver/Controller.java @@ -0,0 +1,27 @@ +package com.template.webserver; + +import net.corda.core.messaging.CordaRPCOps; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Define your API endpoints here. + */ +@RestController +@RequestMapping("/") // The paths for HTTP requests are relative to this base path. +public class Controller { + private final CordaRPCOps proxy; + private final static Logger logger = LoggerFactory.getLogger(Controller.class); + + public Controller(NodeRPCConnection rpc) { + this.proxy = rpc.proxy; + } + + @GetMapping(value = "/templateendpoint", produces = "text/plain") + private String templateendpoint() { + return "Define an endpoint here."; + } +} \ No newline at end of file diff --git a/real-estate-token/clients/src/main/java/com/template/webserver/NodeRPCConnection.java b/real-estate-token/clients/src/main/java/com/template/webserver/NodeRPCConnection.java new file mode 100644 index 000000000..81644fd99 --- /dev/null +++ b/real-estate-token/clients/src/main/java/com/template/webserver/NodeRPCConnection.java @@ -0,0 +1,48 @@ +package com.template.webserver; + +import net.corda.client.rpc.CordaRPCClient; +import net.corda.client.rpc.CordaRPCConnection; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.utilities.NetworkHostAndPort; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +/** + * Wraps an RPC connection to a Corda node. + * + * The RPC connection is configured using command line arguments. + */ +@Component +public class NodeRPCConnection implements AutoCloseable { + // The host of the node we are connecting to. + @Value("${config.rpc.host}") + private String host; + // The RPC port of the node we are connecting to. + @Value("${config.rpc.username}") + private String username; + // The username for logging into the RPC client. + @Value("${config.rpc.password}") + private String password; + // The password for logging into the RPC client. + @Value("${config.rpc.port}") + private int rpcPort; + + private CordaRPCConnection rpcConnection; + CordaRPCOps proxy; + + @PostConstruct + public void initialiseNodeRPCConnection() { + NetworkHostAndPort rpcAddress = new NetworkHostAndPort(host, rpcPort); + CordaRPCClient rpcClient = new CordaRPCClient(rpcAddress); + rpcConnection = rpcClient.start(username, password); + proxy = rpcConnection.getProxy(); + } + + @PreDestroy + public void close() { + rpcConnection.notifyServerAndClose(); + } +} \ No newline at end of file diff --git a/real-estate-token/clients/src/main/java/com/template/webserver/Starter.java b/real-estate-token/clients/src/main/java/com/template/webserver/Starter.java new file mode 100644 index 000000000..145ca4ffc --- /dev/null +++ b/real-estate-token/clients/src/main/java/com/template/webserver/Starter.java @@ -0,0 +1,23 @@ +package com.template.webserver; + +import org.springframework.boot.Banner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import static org.springframework.boot.WebApplicationType.SERVLET; + +/** + * Our Spring Boot application. + */ +@SpringBootApplication +public class Starter { + /** + * Starts our Spring Boot application. + */ + public static void main(String[] args) { + SpringApplication app = new SpringApplication(Starter.class); + app.setBannerMode(Banner.Mode.OFF); + app.setWebApplicationType(SERVLET); + app.run(args); + } +} \ No newline at end of file diff --git a/real-estate-token/clients/src/main/resources/static/app.js b/real-estate-token/clients/src/main/resources/static/app.js new file mode 100644 index 000000000..c58d2de8c --- /dev/null +++ b/real-estate-token/clients/src/main/resources/static/app.js @@ -0,0 +1,3 @@ +"use strict"; + +// Define your client-side logic here. \ No newline at end of file diff --git a/real-estate-token/clients/src/main/resources/static/index.html b/real-estate-token/clients/src/main/resources/static/index.html new file mode 100644 index 000000000..758501dd0 --- /dev/null +++ b/real-estate-token/clients/src/main/resources/static/index.html @@ -0,0 +1,10 @@ + + + + + Example front-end. + + +
Define your front-end here.
+ + \ No newline at end of file diff --git a/real-estate-token/config/dev/log4j2.xml b/real-estate-token/config/dev/log4j2.xml new file mode 100644 index 000000000..34ba4d45a --- /dev/null +++ b/real-estate-token/config/dev/log4j2.xml @@ -0,0 +1,59 @@ + + + + + logs + node-${hostName} + ${log-path}/archive + + + + + + + + + %highlight{%level{length=1} %d{HH:mm:ss} %T %c{1}.%M - %msg%n}{INFO=white,WARN=red,FATAL=bright red blink} + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/real-estate-token/config/test/log4j2.xml b/real-estate-token/config/test/log4j2.xml new file mode 100644 index 000000000..cd9926ca8 --- /dev/null +++ b/real-estate-token/config/test/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n + > + + + + + + + + + + + + diff --git a/real-estate-token/contracts/build.gradle b/real-estate-token/contracts/build.gradle new file mode 100644 index 000000000..65f853395 --- /dev/null +++ b/real-estate-token/contracts/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' + +cordapp { + targetPlatformVersion corda_platform_version.toInteger() + minimumPlatformVersion corda_platform_version.toInteger() + contract { + name "Template CorDapp" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } + signing { + enabled true + } +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_release_group:corda-core:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // Token SDK dependencies. + cordaCompile "$tokens_release_group:tokens-contracts:$tokens_release_version" +} \ No newline at end of file diff --git a/real-estate-token/contracts/src/main/java/com/template/RealEstateEvolvableTokenTypeContract.java b/real-estate-token/contracts/src/main/java/com/template/RealEstateEvolvableTokenTypeContract.java new file mode 100644 index 000000000..756d978bd --- /dev/null +++ b/real-estate-token/contracts/src/main/java/com/template/RealEstateEvolvableTokenTypeContract.java @@ -0,0 +1,21 @@ +package com.template; + +import com.r3.corda.lib.tokens.contracts.EvolvableTokenContract; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; + +/** + * This doesn't do anything over and above the [EvolvableTokenContract]. + */ +public class RealEstateEvolvableTokenTypeContract extends EvolvableTokenContract implements Contract { + + @Override + public void additionalCreateChecks(LedgerTransaction tx) { + // add additional create checks here + } + + @Override + public void additionalUpdateChecks(LedgerTransaction tx) { + // add additional update checks here + } +} diff --git a/real-estate-token/contracts/src/main/java/com/template/states/RealEstateEvolvableTokenType.java b/real-estate-token/contracts/src/main/java/com/template/states/RealEstateEvolvableTokenType.java new file mode 100644 index 000000000..f9dd1adc0 --- /dev/null +++ b/real-estate-token/contracts/src/main/java/com/template/states/RealEstateEvolvableTokenType.java @@ -0,0 +1,74 @@ +package com.template.states; + +import com.google.common.collect.ImmutableList; +import com.r3.corda.lib.tokens.contracts.states.EvolvableTokenType; +import com.template.RealEstateEvolvableTokenTypeContract; +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.Party; +import org.jetbrains.annotations.NotNull; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Objects; + +@BelongsToContract(RealEstateEvolvableTokenTypeContract.class) +public class RealEstateEvolvableTokenType extends EvolvableTokenType { + + private final BigDecimal valuation; + private final Party maintainer; + private final UniqueIdentifier uniqueIdentifier; + private final int fractionDigits; + + public RealEstateEvolvableTokenType(BigDecimal valuation, Party maintainer, + UniqueIdentifier uniqueIdentifier, int fractionDigits) { + this.valuation = valuation; + this.maintainer = maintainer; + this.uniqueIdentifier = uniqueIdentifier; + this.fractionDigits = fractionDigits; + } + + public BigDecimal getValuation() { + return valuation; + } + + public Party getMaintainer() { + return maintainer; + } + + @Override + public List getMaintainers() { + return ImmutableList.of(maintainer); + } + + @Override + public int getFractionDigits() { + return this.fractionDigits; + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return this.uniqueIdentifier; + } + + public UniqueIdentifier getUniqueIdentifier() { + return uniqueIdentifier; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RealEstateEvolvableTokenType that = (RealEstateEvolvableTokenType) o; + return getFractionDigits() == that.getFractionDigits() && + getValuation().equals(that.getValuation()) && + getMaintainer().equals(that.getMaintainer()) && + uniqueIdentifier.equals(that.uniqueIdentifier); + } + + @Override + public int hashCode() { + return Objects.hash(getValuation(), getMaintainer(), uniqueIdentifier, getFractionDigits()); + } +} diff --git a/real-estate-token/contracts/src/test/java/com/template/contracts/ContractTests.java b/real-estate-token/contracts/src/test/java/com/template/contracts/ContractTests.java new file mode 100644 index 000000000..79116bca5 --- /dev/null +++ b/real-estate-token/contracts/src/test/java/com/template/contracts/ContractTests.java @@ -0,0 +1,13 @@ +package com.template.contracts; + +import net.corda.testing.node.MockServices; +import org.junit.Test; + +public class ContractTests { + private final MockServices ledgerServices = new MockServices(); + + @Test + public void dummyTest() { + + } +} \ No newline at end of file diff --git a/real-estate-token/gradle.properties b/real-estate-token/gradle.properties new file mode 100644 index 000000000..7d7dcd3a7 --- /dev/null +++ b/real-estate-token/gradle.properties @@ -0,0 +1,3 @@ +name=Test +group=com.template +version=0.1 \ No newline at end of file diff --git a/real-estate-token/gradle/wrapper/gradle-wrapper.jar b/real-estate-token/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..99340b4ad18d3c7e764794d300ffd35017036793 GIT binary patch literal 54333 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
1O*56QX{W<#tjF%_n&nv+Pk zZ;REvXc$mX2Yp&rS*OGEy`4(QYz z8@UxwAW!cl!PrxqFDH-IagN5-yQgn{y5}zXX>o8`Hqt~->F8$-TXbEENtnax0kSEb zhjqrS5n3!79}VdffBsDEm1=bG4&47qUYGTgZMt*3MQP`Z4Oj#5Fm+f*Nk7@A zE3#-zq-G_Qe1fZa{Y`HWS)lWKP}{D~#&2ftnGv^NHauAd8AiSF2DSN?%yKV$;`+X{55t7bDuS88dl(~`6S$1&kc`8JpKzDfr7q=m&xCxDr*k91 zI+%V}BC*N9jf<%wBi|rYFgBi@gGvb)9C{0q$%6sduJ7bta9$l7ZS5O$<;>D$7%9xb z0bw{EYJ};s>Cwh>GBsF#kp4B>J-*O(tNNSeuKyL%|D5g0{IiekA6c%%UyyWw86NOw z^7IHDr5o1p(e$>v8Wv1M`N6P_l~b1Q%v~@Wis)xJDqkuN^jP}G>%IRb5eVjq--$0Y zbYKx31&0!J@+sx9)^rN>stHz@(Kx%Fm1<}+8Hx6$sY*(wrWKOFgkJzlW5si*NXv)L z)VbJzc6LfBfJ(Jt{H6#Qz=1n(W5(1WyZV$8-A*3!ReYotF6^uf)e(xRKj9$fak=tf zfBiEE>|Z_2t;T*?{EL=yBK`NCI=Z==*xUcJUoThd-49(0Cs65$b;9yHvaYk9^_=7i zJpx>zGE6WmjDuLZFrFy5nMV_!JtQUD`2~G?)gzz{Xj`pWYTKh8?NymXf$nf#yY?3S z=X>9q)=R}TS9A8JeH!$U&r=T1wtxDw|2E$?kI(ydeJ`Xj-WL#F@1`A|C}mfS6`NIl zEJ2AhIs}p}5?s-inle#b1asleoWfc5Xo0lRA|tUsv0uRywUk^Co+-FV+CNbpm79#x zDw#X{p46uZji_HrV%Mz6zX^?Kl#2IO#$OhoMCB|plrOW_{GyMVpKhG|A_}TRanr&t zNH&Ny{3RzN`qc!m;@~gerGquFg6|sjyaxmf&DfZL8 z52lqjboSoWR7N>+|HS2*rQ(ELF=se%4_{zovPg{*hNk~*zfliyN`%^l=|tNXFpiU-v5qAFZo5F89(4q zNZRaVCFP&oJnCEu(}=sCj+v_nTwU^NU8g-l*mrr2&1uosyVCFbHk!>RTpu5x-feYvNb8Ou)^H9+I zZGlm5c-fslzWyBhr(^DCsnMt>&eb3y0iR1>+_Q0doQVIA-Yl`x7fM2P?RDAWPmBr1 zjJ&vLZ;{czHI$}Hw4d&%m$^U$mWMyq_QGudP9Skp`i&TZ(EKecBh3&n1%77v*39P< z?IT6y{ITR7Vei%oH?VFW>!mc#XtxQV<^F-&H|GM;%v;r7WuiFB(OtFM8Mhskza6}2 zC}6)<#-u1sOS$woy|8KLZ7_((8yGKS{Zbq!v^z7o(|#YSK^w^!+0gG*`9k?sv|sNn zH@d!e_uW2lQJc3pCf^SgwRAvptq zvCrH?JT@pDdBVtJ1&bMQzHRPosBN&mmT9xBoF4V}i^yQ;HGnkCZ~oF(*Us&%Wqj~) z9>V$6XSCKe!i~b^Dx0>;bC@e0`->gT?BL!q)4-eV-T5l*-7edK-POYi&nmXJSY`(2 znVxgtVc=cm8-zdJKu9TQ*$+}>T0=09iMf4LV5>8i#*aR24t#E(4 z;!e$_UBy+8H|q)ABIN|C%jV{IBv%i$Li5e@&$d>vrPlMdD{d(cpvPm8pN`aiCxByO zeOhyil6mIX1OKv{SLfkUQKtW)C=Aaq3-qJ0*brvem6_A4<@bKH9aqD zKy!q->YR=^TjdJ9X{NeA=Ix}0b-i*k7?N|$0%6P_!#LlYZj^Us_>Ld9(nXj(Ui@Jh z-dapXQhVMJG2L+zN(!Evk2^sh?EuA<}BN zSiQLBwkt{0n*@MYTAy6jMe^40YzQECAHMP<%MI7<=0;(~3EVv}hH)ieN3?-=go~)U zTv3Rt!KFma6k6@g2N~A;#A)1K(N+(5f7~9HVRG4Ig^0P z-fkrN^Aq_i%%qhNYLU`VmepGuVYi@a2gT!C_~Jdb2Gd@HFHAgSP392mn-2&Qi(*Pz z_APV%-V2oud&m!SL?5+%bzOl-sTWNXRRZ!Xu>&oU%Tek~LX(3w=M4Ec))89Y>+(x8 z4nx582=*>Dg^KJ**HdEQs3-u^D6xSnt+MF)4)r1Kcfn-WE;q_aAJXWLSMV*O?(89` z4A`3>jo}&gm-m0ex^m$GOVf--;TZO2ekEW-l8X!d5 zYsgT?2tC=Uag&G}Nr+8Yi1*07l43R}rgP;`&ozlObuWuN+AzE-N;T9`*=d0u%WJx| zz@;TCyMW8;fO@A>zj`N{>mW-?Qp9kqqmc55Hf82WS1AHExlmz6&;a-c8A6`lu#ad$5L-1jmP!13i+t}i}R?ur1VeS-tBqcI325yl(ekNI?zHUsV%bL;Tz|Sc< zCXA#-(u8@@Y@ii6W@hgg>daC_8Dj3u+#lZgbaWe#2zKc2(q%@-IK+%1R5MmlB}*pJ z=x;Z4da5mq5In{*;Bm3nt+h0|7~ID_w&E=_W3U?(UC&n+`*t^X4g)8>k73JPJR-g4 zm-e451+qdIwOAmEBBvDxO|#}w8hESu7z9u`s{H7Cy4D-2vU5ag{x)d~+**sqGfzEb zt!U>HVli%ZZfd}xm^r8v@-N%E)4Nido%!Jr8PU&PI2MC9RjHkA;;_g^W~UT*A}l5U)Ri-L&j;!FV#m!*{1PgX9y*ZQ<6O+Cg&Rb9P) z23P&X)}1T%%2L-=pKb=P88!!FSKmn&rx4(wNpPe$<@gF<2u3T`OQXQ}0^JDde^N(3 znnLw<9FwLkHJsZ&XZ6io88RZ{U)@cZi6kMn>{v*a^c!cr>^*D!N%~#e$rD#bPt!AE z0gjCycYl)x&mBPkpaQ@Fb*PL(4ECJ{YD55mg5E}L9%cN@9u&q|oRU=<)3mc0t2QgG zx4$e0S^3_Y99l^dK>(OB^G-FsW(8^~m+QV{tB#qQbrT;b3n9E{n;6*fnp~N2d1_Lk zz4d<3Piqw&R&fVF)uk&}3neKdgY>yxHkw`?T~^JtecK;D)lKWLjKz936;D-07ktLV z$8EXr`H%5>HYK|p#B&mj7sJri7rJ*7YO9ReYJ8xR9EvZG;&ENz4>GLpbSJ0V9n0y> zYTzUn9+<~kN=&#OK-GcTEGIrUdrD{B@POgcOkrTPgI=OhMF&H(6F7U4&+HDk18ghn1N3(TZA}CK!Q(4 zjc{x0Rytw}`N{rL0JlSBnZ-E{T%<5%5s0H!Ya}(Po$~%I@5r*hl3vhSU*b-OE>E8A zp_7TlQUo|qniy2gs32FH4*ceGi(!8qVw(2NT50PD=LLl%B|FNBQg zPsa>YA(R7X8JSXK>I)52yXo*xFr-nRht4NfyjN zLHXu}RZ8+~4I6~hT&-p_U8tsZ%LmisxF;Jpw)?j#Wi$ADqh(K+PL05b=g9u2>JNvl ziI&d{(Gnl3e$v8W!EA6^p~-C(8>Y?TyKi50{nEK^tW4(tHV?dFZVdJjDq z`j&Tg?M@}{4(V!nvRYaJ#8wU(T@E&weeEJLe6&Q0EJu+RcTO-#e9zNE-SE^1n@&r; z%951{hJq?_7KX0*MP;?$*280aFUq=$CHN^MZG2|8Pwoy{``obRExP5xcx(lCWFaF` z8iK##X+GM=l_K_$T!esUyDd6)I=+8zlB7bN5TqjAC({a1IQ_W6`IJwaLDKlJHrtZ;J8QgtqGdJTuz@A@H-+k41lrlyK6ON`Z@}VgatND(Vn1rKGcW1% zg){R?@u)-O2>KE9*E~vdB}$~Q40-il#ijZeN?+kkyv7$yU*(rWL2U%2`%_ z+ZT)>bb;*bl6L?u{R*J_+p6Z@Y)lytJ&O%&r$G2DcWf+L!Ax}j_oJRoN z9P*KcBc^B2QIl24Z$)jMYhU_;@)_3FJSa4%)fKpPwO=nTU7d&nGNEzp`_5ci)ZF`O zjI7SafVa57ThEie*?nU}-<<4(F~U>0Q|R~zW7qYnc}eS(v$zvriqMI(32sD*#7%n2 zIKo&-V&+%}#bg_r9e_y=Rt{I-7W1b|>B5!kp0Iah?x8zRaf#!_$Kb)N&bo=`N)E~5Q!1bEGDK4Y@}4W ze@sOkurijoCLq%_XSALsFRlh8xn*}vz7Vqj5qfKoFwg(9ES9y~d|@8{7k1R^Epn|l zf5?gpH;CV#kO}Z{Wg`3TStlSns^5N)J@!@@f&z^@!ppt@El3X+*uqANH&eWxmS+6ufb%R4Ue(;LoGc70o#Rv-* z$M026Y!^5W`fmB#A_@#=b`r^{OA$-16ZErURnqZNW&=enuz zaWVF|u&w@b}i782gv(o!`hT7!}@N15$tapk=Q3pjEo&a3xNJ zZ`IE&&ymgJmm$-5*U?{>!`RKq+cWxwjO<&e#tL0rmxpBO`^eY96a?vWGfS)KP7DM) zJ4*N*f?zx?EFM>!%&EAhiH@==*L|-^W&cq{{0b^H&B53flS6kZ*$IPsIFIvy(21ii z+7jv*91x(bC)qYnFl!I_(4BlWP}9Ml+#A^j5&&2IsqMqHw9|cdHW+&XXL7I>ViQi& z?U_*4mWDJNW4X=xrzG;hy@9fAHorODQWN0zZga#MRQTtrIH(T#_zkx3pe8^{sOqGA zm1EXb@f^g4Bg#pqff*Z--;anPLtJVwcwlFb4rzgg!)LcUFzdW8{BCf32;$TIEUkgm z;2wB^c-LF(6q*`HOUOB4n-%&58nG#hW=&Vnh``vLa5U?grG(Y5%y;5$`-6wnoe`%c znD@l=j6AvXM70xqmJOsO!)ySN+M7jl8;TOb-!8KxVyV+aV=2X-QC^YonjY? zLveR^hvF2s;_mM5_TAZ;-JSXG?3Ya-A%Q?HzjNPn-se2BKke0+cv0|5OCxPD?#;jl zLQZO2dm`iwqb!@N*BJZIUkRC5=wK2`%$|S8><4YG2U`t&h1KIi>6g+4{nZTr3Duh& zF|~w+OwoohSkEPcE~ju7hd-TD@>U04`fW|J>bX5k@< zcfH$5NnoS=R1a8RQ{IhOIb8)#?g?H%QLcY)RwK@L&DJ8r_?>Y5jhPdPp`GM~wbA4S z!6l~hxjlYy83+ZL_b{tv$MH$GOlL?OeX-~8A=>a^=KO0{m;j{THP8aV4`7!G} zAt6R2D0q)6g9LFo@Q-Mz4~H|h1ctouU3VyS%1ZL0D&{ggr7P!=W}FS5U12ZSE^-Gv z=h!v)e~n-2B*`wmJiS)$0zCnHhokLUQ28QhM0b^LgpOxI_IQ7QMz z0%N_?nHlxCmMwDh9B8saymRU0-78n}#^1V^_h~|MLcbRg9IQI0tzYZ>eNumHBG^^l zW(&96?jEx;=oIc`i|eVT%j`Se+FLW;Gh+3(M|mEgE)}_gf2AJMOAM_K za^lspe9WCr|8{*5J_GdEjP1dy1Y1gPP~?>}N;@`L{v&zp@U7DxDz+`Q66%VN;k%Df z-D}-3?{{3}X=718#Df~)@fOlPACyB4@~#>Q*-NqCyx}xU$UMmX8?ig@PMR+$#wI9^ zykI0<$XoA7wP>5};F)~kgV@KEn(x-O0!*`XTdiT{(-q&1QP$|aqxHj5Y%HnY!#BRC z5D%^q&no=!820j+4SX2FoeGc@aN`w7+vvO0ivc*ytRgkK?6J~yI@u4T^!R)C#`L>h zn-o5(=%{!J`3Q(P6XoiDf^G)N9$|9v#()S+O{?bG;VF{i$-2=NdR4GzZ@Be+k+2_| z^(6{P44T;Am*-X8Hmba@%2W%K+e&1G*2iqX0yO~NOF`U|AH64BI{&HJI~Uq7+~jT#rk* z6kirdC_%0-7En#VU5`PuXh8b>kEH?N;HL0vDzBEA@NcS`-1V176Me2Ju25Mmj#tA% zmW_n?srwXet6qRO#cHrdapHJ`UYho@bo==GYnG7$!N^&V`7`6h|2g19;y=10dgd!4}wpVWy zclXD85<2maZCRO|u~`8)%il zopbvo;N#;3aSKJ^r{tD7Sc)TE;A$zAQZT)!UidfRwIoLpq4+9Ie#@5rw^5zzLppfUj%YZ8dCB`zO_DDR?cxc$E4N$jPwD_KFh zH^<|%1DH-_^WX`v{$5i4utgG;#ls~iju+MzW{dV|W!e_PLspV32$vA%#oB@0hs|Q` zAN+iCc(Kqe0Bf0~`(a%jQOET3$S~Zvaa_z!GaK1gzOi z4deF)7sq7zW&oW1!jvj3#%J zuSZdWDIOM@I-we{`s9-9!DIeQ1lh=2!mC9aWZBIU+H*3V3>_&m2s73^p)(vbg}5^^ zp*)zKYgaoDe=SLC66?4|P@vHjI0y*Wzb{EeBUeiy7Z)=-V_P$43RwpeBU@3RQpVZJ z*v-|-;U9FzBxM~Ld>|!v(tLg1wZf^Xc}1tuE8OOEWk*Lw2n~X?F%*l~mqs>$RK9%~ z@uytmr*yC4Rh~c(u^KNMV5StscPY)~mCdB5{I94@>dGjbDhfH0oqXzBSf32KO?&`*bqx1}g6EE%JHzC8ykEj5v=}Q^H0MzH} z4MP&22~q1oFJjg5o7j-fle=rF=@Hx8xUGfyW(shmbGCwr@xq`O^7RmygFmI-*9m!g1jL>VYDYFPPv^h0u;0+y!T$AdGJdk6I|8o6@&0DP@qeg+%1`L$;5#){jc?qYz9FZKhD1Uf9X zV*!U(bu|1${nS?@IQ*g5VlRymQ^Rk|IP$wLtiJjKc9;g?JCQ6DSIQU$X!}fKSC}k! zYVloI1B+%K;uxDzJ31`B_?xJQOcK}YG+orx2I|*-m;#E9w(X78*wk`EO%qKKHp%pm z+6(mvKKZ5JVzz0x+`Rm4oxQEytc1BI8cF`)hQq z{@!`Hmd&O87t3HNXPMRPblsl+;?B8cujAuwUAnE=DQ$pxlH&MQpXR+ zZCk$@Oh|@_M%Pl3_Tq1YQH-MEx9q2pgzja(nQa4#Mu~MHXVe-h4LP3=&N_p+bWHWD zmImnB8-#ukyv&F&bTsn%dd@l$bQ;R0L-spi-aSjl)alpBd3^i1`ug?0-)6O1)nc{D z2@=kPw{GZUZlPTX>FEboHayh=C#THU=rV@IT7uYvxc$0m_@anT`(Y|NKLf<*VOYch zPt|^CC#3;8JSyOxd-19fP{Le68fm4!*O|=m2IIlb3!SCWjyvEvK!1R!(;q8#Nc}{M zJ7W2iRw;h%g4m66oYGpb_4cjCvF@49)dC zLl7hK64iv+;Dqpkb97MdOC(LyPEC-dMDI0Ln);vFrK}pw>Ncyh=LU~_RoVk|^$|JS zT4wZOwdz*bZg%nC4;4|VeVbPvh#o+Tm~v5HjWsvUuY!miYbH0rlY@~OMr`;a8O;#^ zmY#ZO%n^DE*`^=HK}AW-S)v2p8(-GRQh)0xN#;ZwnlIYpoOYlhcTbFs^pEr&So3bo z%@20sok}-Xo|x}**CdedjE>Ub`0IG^(9j+$+FOyf23us{*~5#BePkKM-mpNy6ZdP5 zsdsc&wcOOU^xF88DLKA;-r;srn|40hW@%0m5s= z@`e0d9E3EbAO;DiX!w}?0c1tiD_6aEnpimXGp;A3Oh#1Flf?37mo=pGyW$v1XDeSs$zhC=#HLg_Z>90X)qZQyP6QIr zCKsmlKuI#aAAHv%%Lj}%OIYlcDD-pM5LjJiQAao>Q+hnl3o5zgemcz2fqukTYFu0#nN%mssKTF1yNw&e(hBP~$k|2& zwoY7uYBk2}`efHkTH78{Tc4EK=;Bnf*QS`kH6-cnk?Dxrb6I^nO; z&%cOmO})oA6M)JkKA=-3^S|$vDqcV_u^mua40PIbwK8*&G_nWUbpDrFrjCk^8pa2w z6c;JMXeYa*PGM7sjxv)ZW#bnN3QRCcm?HfX(oCu_fXmK^6sW`fi?#uQ#YwOO(j?d9 zTnh7Wl;5L7*`_?%-omJ9{lkilpU-(ychjQ$$IEN10BA?JgWu6WVGu3x3G;Ekn=D=G zbuW3e!*i~ox;&Zkue>cgNEf&RKg+&CY$a|+$vT<=Ibv0MDbGsOG0zfII?be=80^TBCZ3o#ipx4;+Je)dcou(`~yH#zTs@1$D`@!iJEW1ebp+9U5=~68d7%S-Q zD#w^y#59FAnt8b7MPblzvy~==!nfM#ryYP1lcSa7@7)OaEVEiATV%4#b(7=)rbgb> z)g^@etj8|vvmUsT4d}}Z>#n2R+M1hlyGTlIgJOX(3Gd`F_BJt;KLwKbN44zbM7&nO zqQ!nQHYCfc6ZNMsa^D}LJ+j~%9ntsPfXJl(&XsLuki7rp>cTT@;a28-wd^Dn-HuR+p%1i+5Cz1s1}I z4BFQyIR{?s{!XbcHnBaXoU9&Iku#5;1K7jsrZ(G+U98KWm^+Nla2}oVRqI^p9XP8E zP+dsHE7w7)P??rV?vg_`M<5VDYGFR+RV)o6F^M09=jI0KVG{46W9ICzVdm@xMlf9! z1=XnfMms=2DQqZ%F(jpE3NjQ$+u38((5=34o+^5Qnx2liz-4l~Cp{#IdhXEDkq3{s z${I_6%|9CFj;c~{=ms)1uQpui4}D4X{025JNCstNs%Wewzp~JLmf`zxfA*1jF}$0y z;hDbIuW24vBUg949BFoyFqB)h@+Fs|Nmg~!2&N^CcvMBe+f4flx`n{CS##ES`8LHf zV4f_csY2n{To_Dq6y}xZr`{CCq6ZWihn6$t-Veri%VIzr@K z5T>|@G%b2p)_g#qjg!w2)*SEGD1bYew+oU5HXRS1iRBq`s@e`QxbE+PWU=zb?JCfB zHNDqH;62FakKB~=hkNgFPtW3*uZJ63{qchv#~T6r`A8z}2ecEnzzYk&iG79~nON-G zzLuvG(!^d}62i-f0kPKUp+$Hj1b2)QJ~e|8T0;ctQX@Y}0W$7A^oP+iWX+aF)Pa<) zaU;Jg8s;9O(H)9M+KPxP`m%(#T}MR96)Ww$V=$e>F=bO@jMLqYg>J76eJJs!%ObN3 zvl6|0uSe2KlP{=2$%DAQ&#l3~-UY@H^~K|XK*clgKJdS&GyfrY6LGLNx3X|^Hu~?Y zVDwKr1#}^lq0iOBg<$@m@A>&;=cJjrTWqk%YAk99n3BFC)sD&J`!Q>J9M>vVWGvUA z#68GnnVmYhm8-wMvNf>X=DeTlzP?UP8GsyI(S*sQju>)A5=0?fI0+0*@P2)4^Xi!p z#*4J#HU0BoCvbJ9{;=TkSbmJxBSO=k~w8VtRu0V>TgyFH1 zEf^e-x1oC5lWCx7>mG|UV{o=>i58o`F~_!A7fcBN64EE)G~FJ{vK^LU?wft)>N_6I zE6_L2FVeE?W*eThDA0QY&)Meisuwv|vfDQ6qNcLFd~Ll3<@Yr74hz;Vys0cm*L+vu z78pA^6stxYre2*@>b&d18j_2Yrf0vD>@rrDomHfVm47~zi%a}Uun=8FEo-sZCimY| zj!`9+7m%2|o210!xxai@4Z8UTtYw!w9uV~|wMW3VHB`ey~68T2CzL^=HW2gbnD zc^(st0Y(>vFZUg|VP?)5bCx^J)Nt&nK+m1>8H7`3B9{gCfH{RBtBOnK(k-r@hXwpTvFK!<>K;E6!08_mD( z-T$aeWWcwc9n_Ms z@%d4SS%JC5ZYAtpc&xY5NZP63J=AD8-3rT>CwKYE{~70><@Kvl-8k>=_V96|BkeI` zcVb+p^Ckz|0IHVo6KT5+g$Q|fg7xG|6h>hbW+xK?R^fV5N7&N_#{qu(oHcv!n<@Ji zE^ed~V~EG7W`qkO#WN$(#zeo*)0U5yC$Y;W@gTG(|I|0)z4@>{&*Veo+pBoy?A>Ru zhMdEBiT)>?DdNLzbR*w{Lxt=W@o>#mwrcfypYv&VGO1$ztz?gd`biCby^@3Kq~C=*FiUjeh;op!;EcONfBq&8+Csg zCkWK3mlh;*;+0&6R8=fimQA|}J26!1`$1*rxwHcmr%o5Z(Kr`1_le6HhA1RmE0=#l z@L8~F_g-@u%AH~yJ^XU9GV7g#D=#Blh)k43r-Z^~af2N**0F{3nm8fAJ=uq+!J}kT zX-kKba7Uy8T#@bPmz8OOT1BOD^Te^u*^d5%GU8MCQBlTLO}yvo(4|;^b`DNTub5lH zK4ycXCH75CgPs)iHQ-G7qvsXSLyDHXX*suKxz>2R#+kRDeXLCQz>n03_ctj$*2GC z^l}R2a*nF4A^K}Lwv5j4yGCgT`F8e#Eox42sa^#wSua(l!ecUao&KrR*V``u#5Z{J zw**`{e!qmECA&mtEmRtN`Dr$O7LI6K)Y&S6G6Py13j?5&NO!LT=39(LH#FtujIG-1 zcwKJ^#`wMYc;|WrQRTI7d#Vd(7+7JP{CwOWxEECcQn z5D{>3RSMs`D0?{x;%W2qF($0WDE=~=<55JyO?(>d&2iggLy{I`Rh6R42F_ZaA zEE$e@UY1NXRp3)plM@rz)`pqv)-gL@pXaEz&qlBC)Jc{t8N3Usl4taW%`gf6MvxJ{ z+17-lkTHn+A&;tLNs{VnuDyHF6|xEQLN=`99_%6j@x{P+qS|w1vIT)K*9JiwYM=?t z^6@r;ShSk;hV|feEwbXu2*sSrqPC=}C4gq+76Ddgha6}wvF?w&wv%SSbmyZvQk_rf zYybEjRq8~1dUP7V@;+)M2v5`#EZP}S%419Z5*MZ~=9&_wFXGw~))jWtPvp%v%}3-- zPPUe7MYv7GV1BZ~&P2NIcSJn`n%}OZK$mL;Tgbc7#F4f-EM9A{8sVacn6Yp;-;p%X~8BN}Hn(QvPqA1EmO|3Nr339Ay z9O@{Dl|S3lNv>-k0x`{u$0Q?=mKKcq;^f;5lpSUowY=FKL6W>qkaDoBJ7AO42^w`| z{%Krh>{BL1n&Rr$?6LD^jpGIL8#*SJO+PxD9ya(MHaamdO|f1JhcXz_-n?s{M+Pq#*QEDH*NwwOS=E4q3l%-Oe( z^8Id$t@dHkkM&FK_0Q}F<@p35CHM`w7K?qfqC(~jny+CDF(gWH=$JVvSjpv|py^~h zH%-j9%zjg4cC1S{agLEIN-W0U6|538v(1`*FfH^=hnC@BnmeU*O zlo#MVm6g6M#D-7Yd}|S4H^F!2#C*m@Bh+8$(lyIXym#9aR>B(=e6dX{`;BEJep;w& zys+xM=R;5;KeYN~t0>)5K)7z)h?|mx{Gwi=4Hhdf=BKbs2!#t7BpF3UNds$|ouVR` zJqXwJG5oO8R_;&#ZBJo1#iaQc#j8TKrFk3xEe{bnCSf6YNaL`$^b4t0c4Rx|4-KrU z6A1H>US0)NMGR<48nwQM_rT;Hx49OGgM>v?*~zNT?*PxB8#np#BOL|J9<3X{Wa$^B zhyjzm-d#;Ott9~UkIzKvnDc6ezkS3obnry8k;PPnJmrQjRN{AHGzleJqpu%)T^&5W?^u z$sUft{STw#e*qbEG^10}a*VXia!k^)BU1BnlOuayzD&@KjLS7a{skui{?`A>Gyy8@ z{v*@m?|*{H#MbJ61dzlH-zD<`uN^AD{pepj+y3_BLdJiK9x-thaj>%kqA9Xg_GZBA zhdU5o`A=LWN%bH8^6-&#&Ea-cn}3k^3tU(E(}X2qrY(ibQD=S+nc*(l8KxkJ6a@5b z<}$y9v#L(Rq}Zgj-1)ddeNxOk-urtnXLnWzgRB^-tZ9W5qE~Hf2~)^_ig+ zXZ|R(EAf}A-P#CioOWT^QXv1D7%rpRU5&UwHecR&^5^;A~ocjJUW86E=+@Z_W`0SNH z(!eGn=J7$L`h}or?#@Q*g^of{_-GTARAwTNL+TfvmN=(+E6A&mJcfs5G_)ZeT_e>H zsHRxN*}9BmI(@-pl~Q*8tGGJ(uV?u9SnTj@3~6%9P-Ul(r45+t%UL)&Qz0+d%2|K% zQdOKo`7g8%SsS%IzZ4Wg@>M*)hb2}_#=~-PQXk?B38W7S6hBe#*@W;8GzfYFvg=X? z)D2tyx)+xN$mYEqW@4R%v+%)?+cMM+miBj=s)1IaB%N)nxF zCXrGr)8!%C@A+ZF;xbL(^Wz*DZ+ceMR~;9J)WXMl#VB^qM~42=o)%7hP7B#^g|{wEW@P zTQ65+>5wKa$6+Veq#kXpHZ8MuR-Q+Uro!LTvf}FtUw*h1B}ZlFmpO9;NA$3&7M7e$ zH!GJ_*hHt7X(z+eF41N1+hwz^8+{RhRI&|*Cg%*U^A_C|8zPxsQ9HmsWN(jL$384| zclN6Gh%D}42Cfn=9IBL5GhoDci%$+3>#LXS-2yz%eIJfFz2$2kU!nG_?Xi&N^0T76 zwGfIvPwMqMlX$T8zXJJk_E_^icr{9u-SYZ-&88hcqyTlZ?m#13T-=6K)a&Rdk9h3s zswirbXzq`~@A=_q0r?S2Ka~c%VB`itvm;7}ONCR6a>YH~nxifSdvQad|Fp>DCCg5?OJyeZcw6Jn?2PVJjbG4 zU2ve97E%k_7K;9`@CWNNPNYp}+Dn1>QeE2+%1KfLLe8_B zayRa^^&__&ppvJ1&NMbGVdT#D5J1-X)JN|ps0;Rq-s`zI`+}teyHfTQRr3k*jGNFm zHQ%kr48V-I(NYbDBr8!|@)qZbQ`uhQr=!Yfh8$O$_e zpJbQdjfW;{HNm9NGb{jH&T2vt^Cwgr*VdjZur8gxLKFfg=5_)YNdG?1%;7qdsMoq% zw&L)cFfgQ`pTaaGyiQl*r_%!6gE{fg-H|VFMHq?E%x1>%^;e|(q;gO0u*)WW1LT^d zqLJa#O!7y4ntdGkkWsT?hG3U%*ZZs2`^PRf;9pO@>YGKc^&A!hU6Oye1W$rF+3%2l z4QLsh*`t%9&JL!mAGVW*uX}=*<$~e+R?w_Gb}N(7l{}AO|Lb+M`(L4qBXYzW2_Shx z2G}$IO~3r7BT2~F!p#mC#saheHFNxLs4gjfLK>U}BUIsqt)Qt{+m7y}@1ju+dNC;| zJczlqw&>LruQ)zUl$GtortM4X6{s%_iK+S#2$kVBhfvFbch49gFNSi(uqrwj`z|h0 z-a2w;5bAAExK9deOc@UTa41|u>W{kbC3Em$9WX&_tyW6XDR@+gJ_MGyjS1 zG0{~;_hl>*eaI?@lzN(Im@fz5|{;c_;`xn!#7FjN|=e3NudH?jhMFyUt-()mPotZ zjZ?kMwJ!wgL#W_o3|0e&$`d;l5`dRh))vebgfEqsI>JG{HV=y|t!dh*N`?dFMBw>= zmhEFy9LmI=QMjg{eB8~#clc9@GlPW#wrHiT;gqnDv#UC5FbqR%RRh!bhqC2Sj}P@7 zlvB4gZ)KKp;y%x|0&CL?Hf7tiH>~bvwPwiJ`D8{-$VC8r--P>4FxGPvUBj|!hM=XP zTRMXRIYK9_YX>Oof=nj=O?pIqQrKR|so2r!A;0MAKr;Bxyq@7GlL(96T$V zD%%{UU5=>2U)Gu+ zO8UcJfgMq!lbdET~4$zykuh4*#NIZdLO zy*j!m)PlDr$tC$BEN z+ud1fTYWy(?N4u|45uJ2UlXq?Q!DQIg&Q<76K|!KSSiV3-)(B!3FQ<7J6A(}Vb8U3 z8#1+!wCYNFtA)%Z^pJ^6>+yx<2Gq0VI-w+5DR~scsZJAETKe8>*tEaMt zWP}y`9E&$7|C|vqd`Z!I)QS(&6l$6_=Lihr-Y)-Q)nQ}Sf>YMc%&1H(sCv9CAjK*CUy>P*Bl zZI!M%e5E$${LPgyb2yDeVMLF;z83f~$uLy6@=Bd@l7p>%dQ2QfSE6T7zD0-(r$rjGx|R57~O^j!+hu1f}+K8}jzPF;0*M zTQb|PNkKK&(Lgcd(fX9MD&cink&X7G`)}rM@zv$juWstRydKwYR>?F^)so>S7x2Tp zi%A=gtW-f2s$6b-RqMucj?>R`9{NcMmgPK*z^-8OijCGF) zRhlSh=2%n|whGaoDJCK8ODw)9Z+L`X5rb7jP$2QU@aPT@5vQ(mo{&D2!%0XRAs50L zP!>gm^*1_l0#t)c8rI&ZLT);^o{vJGTmv&j0JhNdusaV^Lbklpq-V)|Fm zNp!q+%AV3FmA8yKQ%%~U%n@-x*UO^~?C?`tVwwiy=fbfLHfcDHLwW@p2nowuh*A-G zK1OhlfxF6oQ{%_fo5+pUqC2hCx%UoR- zM3ZNSTwX#jUD{qm*rOQKi~Cj??D9a%qj@(eWnOcNOoMw3%zuNoGNMGw$T5sGai{jS zG$$h0f^SLEf`Ugklj6}z(ARfka^VrJZZbvuwOxR*Pn$^<80T58OSZ_3o`1ILk1W;q z=pUZwZPAFbF zE&u`-OuiZ-kstkfC=}UgdD^b5*j~x0psM?;za1#M{j|c&c8#8kvkWT(c8S zntQ}LTjO5`Mm(i)ot*YupHR^P#~5y$Q+!eCp`CKMMLj@v;7F@ELsPHqcOCwS?qc4^ z;i>jL7f-H7PmalDiZOll!P^@w>9u@j*LCFaUx1v}4W)pDAs(#qeF;g^AK*-HqOJ`# z$Q8BHFVUvYu}&arDSWM6%!aQ^c*u_A)^^}oc>Bxn!)jm_TMTTkN5Id&>~a3m1{95) zfhMy5Y2*_cCG@x8J+V*ACT)24{q}GhktszC2(e!D-|nRhR$x;1j*PtD!_ZW8+a39D z1&GZzPTxJjhEc*`L_|Z5Z0(!3?5a|0hvinK^)75CsI3TB-N-K!fV@;;T#eI{p7r@e zQuC0ax}?%dddx`S`JF?rBviq05ABADB-a>1(&KnxDZL-NMceu0g`bjAC*0t^7Dteq zU{StMI6gTq(Qki8 zAe-Rmh+lqO=lqD>5y3e;!=lG(4~=^|9M4SS-b_o&O5+3KB=1lpfo8B(aT!)WWGsW} z<86n-jMmUu$aeS_qohz+7n?7)Zqqjy=MVhaqz|U`l|cB zN z)Z2=gU2g_s=*lXDup*Iphx7xD^}ux2BeG)L6~c|Sh(s)-&NBrE8V8KF(j!Q2i?noo zNp%^hHNKiYDq`LUt*svweFaPwOAmXhVwTJGl?yE%wh3g}%-@Y}h50*tJYj!vg|s+z zQ9QB!Mkzle2u@^kAMfBPxQ>8TUq-mOdzucE@rU@sxKl>VmWs)8IK-RHa@T4WY_v8X zkNr|?e|AEhDEMggMWA|{$HO+>dhtFeRAno;gx1(?AyI{GPA*C3LLI=Q1&DWPQsZOc z{#xvgslrDzYZZ~#@Ccjq%Lj9w9&9N5M0VtGUYV91tKpUdXnQ|q7*00TJCgN3X@i$7Vgr^X$Yoi)hF_hsVnjBT%jHhc5`Ea zKTaS{9Kz|v%&QW&8=!@1_8uUc))dlY0cVQK^&WfD(9oulmKM0Kb@alv%kI=?x~_Zj za4pj^_Kp`wPGFFP5-=Xyu#Slz^^sa|(K#6Bsmq(G_|Hb1Dxi!+fsr^QT;?uD->fIa zP?{R>8d#v5LXR-aa+CCR*D2bh?vgcxiIpJIk3_)=wiK!$5(*zt{a`*MWei8@3FQRNZEt^7X3Y2w>RCuP0%&BvEvM@5G~?_8JmqI&g#R$9#ut zE&+#`K2vr9A1@E!f~YchsS!YVuKA+d_YIWR6IZATN&XssS**q^EHyWE{AAV$YN#(8E zhySYfW82}^Xatd;MH+7mUkMql585x6^PAb52I98#<){`pNB$f}zz7d;00N8GqNIQF z&)3ceu92$yqT1QEecGo63}V_;KdHza)F32(o5vz%!jl?sgJP~E+D+XZ8OX&O>bQwq~Bnt%S@fn`8Qld!VpE=o-B1kz|yAF8V7=gLE=>Tckw)6B!ZRZXiIeg@JM1 zP``?Q>6TCbV6u9CpIlKquq)MS_<=Z)PfQsZW7)#=nEV{o3N9Kqi%@Vnno08PXL)YU z#=GR7(ZeI7j6X|Q&ZhYG?c}j4f+SM(1&8ba;Ulo<85t>G+dAdAlq<22yha>2tx1R| z)J$aDSSv^|iHmOu8GsL5*fgW567n8t5PEh!ogQ77#2Ls0j0C0~=Wu-_;6XY66p`pJ zIlqUnDwMt)uiAvAWs`7Qe>ENFk>hLnmS|7#{hWBjkvzeud_Zbjy4Fq^uirCndfsp! zA`2sTmb;9ZSy11?iZ>-uKoC5|lKxM)Z~8|dlzHy^UqU)niaC!@fSt$}7&F5A|GnJ) zqZ0|6nL9WG6G#4SE3#8T*TC?%uXl86u#`khYPKvCB4G*v`68tVKi7ix!!D7t)b6X1 zoW@T*i-s~_yl}_|{I^*o?ZM_vo>``M;&;@n>|gjI%z=n8Z12tnlYST6Q+^L?4UaXz zri3Vn2P75!o?)?OW$n}486JtNr6-S+0o5JEj8XV)8fv1}!=FUD`(>z!RQIrzbeD?% zCrgxDOh*(7Cl?$+3og|8<(;@|j1ifG_Ppx6a*K_2x$>-vjd^D^-m2I7ANvY*kp|}w z{RPDJu5m}d75opXJ{AX$EoZh2gqifVDwa$XK(u*hrIyE>b2NQkhnA8}MtM=B*BA|K zJHocwd7hQkqHBc_cE*0P0@Qx7k>!H6oEOr&ARf5cJn2T9JR)U5`k4`T&p6ndhyoq z443$_k-<575l1Eg_2XPB(}aqVvhN z2*kU(W2c)qeIWNMjD^a)z3$AlsIRzP*f_Es5Rco)2xdhr#|njdw{^s*E1|j(v+OA3 z9XGz%{Y8JAjxpsE-$r)eAv~CGUKxcc>&UsOI>oylJLDeOxuz#j=IIBukkD;$VclW5 z-9tAbE0EsBQZzHV0JO5`b}Z?6@yMXIqyPv)zsx-;V=QG93axgxR zF9kqKcWJ;02N2Zf;`{jA))wn(`X~u83DpQW0In)Ltd=Yk();KD(^Wz^KvT8r@7s}9 zrVf*h7TSc%c5wR~M}=}90$HehqUk&vmR9c~p6<-n{mx&d>Ui%VqXvQt@};McO{8k(te9=Qn!rj7$Wpt9g$ z%(%`eWeAlfBGAz{*PmVEn@i+E2>6j0EN2WkSpD7|ctiF>&bkVvF9;d%3!)4ICkPX@ zzoN2>^JXQ)wC>U<^UAoVCvr2Sk~u-jM!#7)j7-NBu~h|~u~jEHh=Np-A~(^o_i=fN z9#Jg&Hn|6i+Poqvud3`*m#`Ig2-h-6Hq;E)IxsKA#xijr0)4!Pa1pY--1 zu8ER8hFIGVa1fIT36SWN`HPl|f5I1*1X%InCk+RxIt)?74qMk*H*30)f{nk(Y;(FF zd4c^R{XlvU_Q784%jUpyN-MYB#?snT_y3C&&Ow*EdlL!-q!txO@&CURsEj=@-Tm*A z-QU}uimQ>U+23cpbk z#gnotL3Cd(zJ({wLyGAN*~vgI2DcJ?0s)pMiPFbO*%`{fM&G-!jK_;#tFP-mn|>pg zpT4(*-^HV#6d}!^5Ldxq$iI+ceMkPHLbOLZDe@H=4|z51lN1(Kz?0xFUpR-{2FObxxQ1d`e_>*z=l;uI#J3Ml@e!E!Jm4$MYRTwJk4Zl z*tr&b^A3LdFLfc>#B!GRRpJV<`9muldh4aB$=1oyDF&2^N7^W@2W0ux2u|2yL*ZJY z1n6~6K@`e{K(teONo1xPK1&v*bnN_;%iq08L5kuzao~J4iNA>7%I3$5`$WB!gLnzn zBIY-%gq?b+)rxgD;pyG`jkXM}to14_Gem5Gwo_1-ng!Phdn_)4v<$y=rQv8_``i*} z^LcYng9=pgMn;ol$$Euldr(4aU|T58*d0TW^`k|-8Y)rX$pc*< ziPj7%;=r;yLZsFLxu9wesIYD?+tHYL2}%RfI#?betR&_@Gi2e}RpnO)D_gB=R4Q4f zZs{s6+>t~QI$@KPXdRW+NfhYNQ@=4Xt=i73+6J>#4A=fra|zY}IM<-B%(0R$vyxq! zg@<&4p*+2Q#$~s);b&n?;#+Rz#fMQ5CEwIWh1on4r3T@_X_myZ7o0HQl{|MKWb+`U zN13nDm~hx{E$e`}3<@tBr;j3aS$NvyM4i?ui*uZqvF9p@qbHAQ0x(%urb!k3jB=FI z8r@0Cy|)cc>=vmk1@`pk^CFxp$S~Mc=?d}Pht-LcMIwl#&3p;hV@+_ojaJ<`q_aDt z-_JI3qrkanYTLz-%lJAUVj49|4eEqDi>FHl{$FKZ0i4CMbsHcd4smyPcXxMpcXt=! z?(UutcNd7e6L)umctUs|_uTvEoE+}CysnyJs7kNZU(cTD?%loD{-Uza+YKd1tXAirrHuwjs>2DP*+*Vm7gK%`2kFNqo_Hz~TMknF)xV_w#HwEjo$*7+I20av^*q5r zJgONK?;$c}i2r1k73vph!cnzhN0-SSn{m=AM+hB*>$&3BD)|C=C58!%ROZ?0^^QXg zQ=Dv&v2pJcCQSVWy0pHJ=cu63A!sZ+CKfriumon`voO%8m?NLd*M4g9RaD+IcX%c#MJ>8~;IaeDBt2629L^1RE{ zOAn$lD^Qh9>pYrPSy$7XKMmF7eP5!haUg+pjwznuAxrjJ*)ZxeNd`P7V_sA0XyU{+ znh$mri<~BVc+J+2r=1c~>;jtb3(d^U(%>ro9yQm;HEvThlY!dIW@lIX~h(*_@jZU0%x#S!QSN>LVlR;ad@B7=w4S zrvr2HhMZ1;vA>@)Y73RJ@Fm&3L!#y*I*4U?yK$JhD5q`O3D#3AD!aI)qPh3AAWp1L zQz5e}#nBZ6Z_BEZ$H3wWLZFXR%IEx8Yk4pJ+xN%-P?^1K84*E-4{E?Rl%7csw?ATE zElXfHPa*eujTD_poomy*ap^|BeW4)FD!y^^(ProZ?vKc!4pNhI)BwpbDZ(Nx((Nlem_lG@zZ-Oc2v zEhN9)W0yqyO%)v!uZ>~qW(UdUQ`>O^noJ1Yq0mvyIR{jvlE^aTqu4o%+9pf&-q2|i z8`)B|E~v-CNM0q9p%OxhGc11ArS~|EoiL>=j6=pEs1+wn4H9>u)(Z}Fa*hqn?VSY$ zm*+IQ?e!!plY5CrYPncAXTxqPGjeAXEpBq82XB8g8O9Q|uVV@hDy>~`YSlG=M4sam zwIXkp3?xZ%OjxTRSxC>b2y00cB@kgY zzRR@PGzt{R>v%*$qlKBk{?%g$2rQhh{wOjrm}Xs_VObrBAcmAVwChz~*>HnIP@ zu<^TxDNX@ce+j_)e{0_OZ?kvSwKM$Nys=zy!D?CthLhQLtH)N9#8PEQZYrMWG?wXO zJQYPj?ngO=XkWa9#JAZ_PBa3BJNTpyzNMkFOg5^$12lfsR6M_A+7 z?N6||KY4;y1-F6|&=&IwOL0k>UfN4`GkisZpivkxk)bWp)8c;(Nrt2tj}q(HII}60 zXTeIu-*6lXNWkIJ%@pRiM8>t*MUS55O=Av4`y*v%2kC9LyG;fk?&J|bm%kAeYrA`6 z<7)t!U(Es2$Ax&P<2rRld5&GiG(3F6#<3GJZ6ai$`99NRnhT!9sb>_UOx(+fbB`bQ#>{xpncN3p4wUq59Dk!sT z0-yk0{r`I9`*KN88Z>;t7n~3{wHkRG}a8KpRPeF?OH9L3{y(JD|j9<*lSg$ zWhSYsWrwjlhL@5gUEX;&*YtCyop9C1*&BA-4gt60I=VRv(U2))h!pKi$rC^XT$}h4 zxd*n<;pQ4l_lHXAk74 z>_*3*(Z7c4_@T#Y-3+@WHPt}JKDhP{Yei1E5n*4*rd~d-m$l|CDe=LBol?kzDw2(0 zjCRUvBk&j;b%NC+gP6!4FV;$5`oz3D0XAO_uzBX+Su6cv^DkHL35x&ngnOONPRcT& zR1SIY+73a4)A7KBf{mFZQs~g}Z}!urx{A)lHsC)&d4p*`z7~S(HvSPI`w4Aa6FuER zPNHGee%y7;ehlCbzdou2#10}{@bRM2L)`b~y|1O)MG#3=9%#VPm+g!7&7UW(rqCDs zoHd}5P))>dysx^)bi<6Y>FSuoOUC*@)XfvHwChw`ZQNoV&KbtQk62`+(i%A2%cUUp z#C}R^;PlS(be^!t)}$%$tO=^P#xl)nkd9-VphvIQ_DF4o@tRV;R8<4tqPyxwFnKQU zD8=fO4s?fTPK_PnlFS=~cfD8`{|lWh8)fqV4eE)_EA8xBrlxbW5)2gKKqFKq&C+|S z#v?Z!`~iiI(WY8womr1|`MgBKGWzU*C<<{|WXq8DEaL%9(9sF?9ml2fv|OtMZ^AE^ z%>~~{uiYFdnS#}d*(4i{I<|A~^f5Q~fE^)Fp$g9t(CIR1%QR{)cqx~JP0Ct%&-V;0 z3abM47~GSVI;&7oYYbeB7Oo|hwz#4kaxj-hD9PCeB-R!CkyXpQXK;G=Wmeu3gAilktgqAi8s$s zjJRofy&7_CM*^avlU&EeiqYIAJa_$A?uf>0l98WUAd6E; zF$fZxKp^RZ7E>TC^(rdnJM>>if((3zx+1_Cu)@E~D*bwI=#RJJZ-YRBoaRqiCC*ya zS|>@#y3|eQa8e;sD+Fx0HwT8Gj0FsM4{?kx<84YCY8E>@UIr!^>Xw|v_OdjrhhL7LEn zddbeW_DxD4rPpYAM>fse^lU5NykpQ`cjJ0+KxM@?18jwEd7^T;UXG6$FO$HRq<>T; zi1(u_H5@NGaTUPw( z%V-szf+Qofl2C@?gmp#*0Wm_ZsA{ z4cjqk*XV^(cI^lOChzQAt?1)F+$O><#2awY&HZvyG=9? zlk$paMdz>)z#xvBY=EcRH?Prcg$k(T9Di3r^`1Y?sT`5pL?z zU|1su*a?m|n)GWrXm+;)f663p)g8%0dJPgM_|(Y9hRRLYHz=zbBF5HSh^&x1-_mIi zb$3y}B6re4ovV$$>-I3zja;AUBAjtXEGd6zUOhpfcUHiVbVr%weta+trW;RG}6PtBW`7cB^I!S{eIJ>=QAejMEDC?M=AC+*JIZ z6mvqRSO$kfNvBg|n{nOjyAdqq;|OvPR|FXO|HoYS*S`Aisn0K$`z(^5b|K&T8NC7zNA zX$=AlpIC5yh-jRU1Py^3`cOWUs?*?U;%(*U!8B0!i`OAUeYM{47o!cia&>*(V-K`z z?q_Mt*4IakQCY9ztIhObYMCtBRY+8lYjyRssG}$;_AO&00|X%r1GfXeD%1_?AcVF; z8O^LELE$^@4DOZZaiX>%xKp_b9eMFLwc^Pvp0bVZ^8`RGoNS5Qtx6$~RH|M@MGE5J zaqEc8a7e#ZRQAsySaw@^pDINU^HAY{R$&L`PSED5c^m-7hq_%kuAp0mRxK!K40*+G zi-)1?B3a~DcIg(%7<#c~VK~|WYSY{1ci=2u3nG92|SY?q)Essvm zZO0zDvP05cnqc7Lr)<=>&I;4;vf|jDThjh&F?JbQ-xv2ePl*a@oFA1YqQ~PqS}mlT zK``U_APx``tMxX(yQe!aO7Y?uspGXi;yL2E2d#Ty`&Y0ebg|SxrIfaz&OX^2d5O=O zB6ZI@0%pIb-tY{_6_=@#ypM7wO@;TpIx(54lJ>L$@27$1A_&csDS`+hpy2G0*Re%5 zP;wZHGnRP*FBL#PpriOI9ZaGt8&Ta9=^|l%y?PU7 z+;SJ@6ibYHAMhz$JI2>0;nb7`yv=5+3&urSk-|MOZJp6+=SZJxZKRRsg*D4p9qWNr;$sak(oK>P zyE1Q+-a|-{cd})tpC8GA8S0kNiuI(g%qrMJ7%Gl#v@^*O1RUtan23iE2bomax8 z<8d2&DJ-p3iDKpL4Gq%8x`g)n5FT)Cm$&k}`)Zwi*mF3m>YbYH2%Of3H+{@XQ1g$Y zro^lyRaz2nR4QH*(eskNBCD0)T}(GL**9YShPgY3PGb%}B7_#(MX-oj=7VB`c>}lp zFz?@i@xEf75k74#ZKR}Sm z9x#}TfMAkA7_f~TykNY70!0M1fEt}S0{N|dgTPdYc=w2%v3fE1A7Pz`TUv=;Lmj?Znq4srOB!}d(FH7jBGNa$<`(QhB&Mi4i4j^M zD@&+XZVDZ)e}lq3MS~hlFn$CXS6ybZGtV?%e2du&8Ke@pkvGC6T^*T)69Y2M)Gqjz z(=)xAmDj!3cowy5SSlv;2#K^bVXE+yYd0;CN^2XTseVjT{@71v-rX!#UQ>T6Pg)qK%w#%oz*WRr>OQN9Bi0tFh7UbQz|N(xLj; zQ>&)gR!iZuT3h&bL#s6ow0g`apU)4u8LC{JR;_F5I7ub6l1X(7QH9iAO%KnehpJDU zrR07u)@Oy^j39HHBpzuCwIWcs#3|ZQ3EpYEiYL0AI%GL4gH zDQs}R!Gs*D%dlcbAD9aLil3Mo>CDQVN_$T!;QD(+U;V3@YI0fu?5=nBDuK}m&^d~7 zbptnF2*1LL|{+)(4HQtLF5tJI3l-yv5qaWQ&f*OY@bWe}biZ01ogJ zVt(-Dx<)M8mnenP0Wt>iCZsZW64M~PBbSv3&Na5BYGM=DG7#OEt9Qr4>*}~`GrM9~ zH)M(G1WJL7u4#xGNW))0bJS^m+rcxW&}qM25ciqe!=@xv#yuuH>?tf6ed3Y-G(me~6EwONlN{1rURG=E@`8Wq*$xMlu+(1jLaxW)wn=<8@V0EX*UjeYlUziQ{V4D-%U(M#zmLUmS)2Y>-M6z$SJDG075#vLc@1MAuivIm-D;ClnAkB~q$o zSmF+>ic<2m1*uMXG8G~$F>|_vWj>XtJF5ry5GR+|h53=}T7-#Wrz}2i)F^=*z zSa_jGPiQ%0+C>v0^2qW$AOsV?JVb?Ls-3E(axOe2#6%+Q8_2iW6x&i#xJJAS89av& z#zYaBbsOWamWU%<+%?Q_E7-P7%qi7Ph~|>X;No$X92d)5$C^0FyEV&1Bxdh=w>TCT zEQ1}F)awdz$aQBjm7SKB}h3o+MMW_84K z0vLMA@yLPY^CD}^ALQu@jbxb9+Vcl3p&f-s1hm7En2gRU9JIQP@>^$NtMvF8W;K^E z>DjxLEQMJV(&{YvN%UBUDpVv>rbld8T0XHld_I(D5zl^PT+td2MRqPs`gk0?V12}I zTgz!N8S<2nBSKQGwoXcJ9N&JFn~^w`MO=S*V&15yj*ydW!(Z18WzDFMdzGniN#w>C z&+$Y5i(|T^p=Y}p5lg#8zl-~DzgX1*8~wHnpH}}q`KT={zls^hgl>V{9$#lo4O9tH zpExO3^A?o)M!qpfk)(;iU=v3X!Y9N711owONeN#Qvgvq!%`ZvOJodpzam=Y4OZsHs ztT}MAd_t%5*Hv7~`AM~-27#B6j3b0hq{~T*kkdoaM?>rBeo~Io)Uw&fAZx zzF`!&1lpP4e$ayrr_6PhX%dXuP($J8t;vAVz;+zmT2Ke6au@tWHjpCHQ0@6XfMmIr zdFwzXoYOBSSp6Wedu?@Yra+I*eVNz%1k+I?>3+i4J46_yqD&^R+_q7~Qflg>w8rFt z+?UbFcjQ>qsx2ll*>x6rFeH0LYKZbucMyr z#1hRp8?lWwi^q7=xQX|%0HWGrgf6JCZ zl^TO7b3Q+tYp}{8FHCvb`O)U$KIy5Iq#KkM!;MMJTy)hjJprC<*Wf%1+7vnR^vcXk zV2{74D(6N{iseVPu;L;`<+CQ6nF=xf`)xgbs+jMA7|~w*R0+eu=!WK-JEEbAjEI36 zRLUf|(x`R?8iE#eH!}^3y?eHh?y1{qR~4xcM&57D3f$`ZR4CBsx6KMJ%Px9hA)HHE zU>6-DG|UGa?b>m(lp>pWspz-q?ecJ$2NeT7kQ!0#YCWXl)S;RcMbXhe+lpEa?Cn4v z*JNF? zxJ>A5oH7Lymhv4Uh4Bs#KCfi{GxtUoH0wtU7m-uiZE?Fsq8qf^YLA8JhrX(!&c)~l z#XeW@t%8`c9{cQx=t_|<(cNDavEgBeg|^*30Dfv zp0x~IkiS0JVy0a=tLe3spw?1B(^kO=S8(*Q>7v?{4{#=At^ zC{jTwvZdzTuNm!0jQ*rIp_m7%SErhYJAoWj(i6q-yKwI3^h5-y=UnX`e6ankYSKh& zE7YsLgjtU*bf_d;V+EcE+v^U?m|itk9M?qDAMXKqLkHJAuPcEP`iq=N+kI)9_#Cd% zYy+rD5a($-c*(HW&ZGukBSTcNYm237DtWbGk-4P5ld9jWU9Rj{USJPF(%)pC=}GG9 zy*3Tls@DmF6I;~;_fp+8TsDR5uY_ZDA8`qD#L@ks)A@D?y;|ESKB3hy2jpSo$;mMy zf>3U*B$qDf$*Q&GqxQ0K;Fdm^+y)-+G)v|86Rycy#FKs5&e4dQ`u;dpWrf-8#W*Qh z(-hqn@?KBn6CrL9IZr#F3mn_Q zl{rF=5_7Us-zkPU%&(SC2eDTym=c^l$Z%1UK76nBm4sivP|v_de@tgP z*m~5UaZ?qK7Ym1 zckN}-^6+bpqoQ+$ir!=Mn#|LU5l}h>DLtkUMSC47RZDN+wW0@KGQ}3BUX$z)9 zgY=Y)?+f!`vZK(|K&NLq&b=fiTOXsN;;KRSUY&0M&#c-d=2`W^c+6(5jc{v4pWKz3 zQ6!gJ*?MI0p{=IgwPjeFZ)}7wA@ZLqg@2x70{$^Q;JUoq<~Qyk2PD4HOD8dUu`bnt z`tJQ{*9u1EF5v@CaYCCShCR!!k^+_wQ}AbfQ(mvM;z=Z$XE<>3ph zr;FXOJkl@r$fF~9C}&`HOG4R=iW~vyy4&D^I#9Y|-@v{uk(C&L$*YqE$$mcaW!}f^ zW9Z~{6&!^CS?j%Y({`L-4n_stnar-NGJ&CgpQ{60yNGafocO9aX}FU&-3Ep@nr6m!8qLFL}#K=}E@H!FZJ7w@DZX=_@^ zR@kYY=u+ecjnLb_Hng# zZT_I5MY5nl%sj)Z-9U4XCeg;{j$0*tK&}x(ip0e2A zW=7FJ9Nv~)aCrl7(~LusdI_&be3Y@`M4^e+pKvsRaz^;5Et5? zY-YBY6{XLVb>1l>>h8g9h9(h0tpt;%v+R%{@e8ScFF27_ zDw>l*2pX*Zij*^^9+-V*EqcJ_`z`c`?K2I`hatM6-fg)?qCvNqw9sNBYN6FJ(TJa@ z2n{#`7;0)5g~hCBm`M#wQwed^lyT&a+Ofa+y>)u9dl;o(bz>(L9PJl zdf^F;J$*Nl#Ka4VcGJX&A;|7|gW9E_EEUD3g$If;^KTy`lz5MD8YZ%L6iMC3ibw^4 z+sgx%F!DT9w=Gxm1%fW-MHJ%H*WJG()6(D&Ye zTPsohB9tQvdr;g(Q480iVEu%ujJOA`%m(q5a)AZaxEI)3o<~H{TIAUlYvEoRN$0ox z;hkdpbKlYGkQS)(@hCSIn4PVKKxnVjk^13HT|Rwg-ABR459Vp4lUnkk8Dx-ST77Hu zJN7%QZNAtwV7^}wyab>*RPb)r6}zgF5;?`&6;q0HStIiD_PFz}9)*2N>}RE$b_ZBe z6EEZw3o5)KB(J$YZHjQ(jDanpjGTZ4*EAWU9H3Z5OctZe4<)7DM__c?C9t zTUXmHE4Ui7c!tsX1DIkbrGnQ1TWsnwyr6kZ{Bwsbu%l(k6$anO=ApM)5|5ec8B~Uq zFTM<$vlPyJ5Z@wO){E3!-|&O0qvgdkn#H%SyO5p8cV% z!ik-q#6Xw-1k3nfMf5tRqeJ??E9DwPDg(1xYbB(8l*n1 z9gquWw7*HG!E}FHon&*2`C;xG0TM!Bl+IdQ6;|WYhsm=?xk6V=;~)B2m!J0Qb*;#9 zETYO)-t>pL!iL(0+iP^vc2-Ai5yn?%ZN0L)wr~4Vx+9YV_g^Lm{>T*~#c7sAL)Jio376uqEh0&bB~n{a8#M@cb3Ey2&Qdi!pTTwsU2e4t9Dp@!2L?YQWAZ>ka#!hjH^1 z_tQf^_nTXqRq1T_Kqz`2m~fS8G=V($`;KheVtC$nr1Ye@i0^xdb97K*Sr6Vu`kxpr%u43j8Mw|iIOGjv z6o(zgvZ>TxO|93dVZ}_)l_EpWRH&u;$3+M(BBN zJ5yp30B&z9%LpOgl9ORa5g884s@PB$YBYbUR4{p4W+cDU*_Q$2_b$4CGF4oy6I9Gp z*+Lw%F?2?7(>|~Mov~OY*nT^D15;;Y#9^RSl<5A*ENOZbqd2Gbl+gW{NU%gQh@{K- zxlu{j9^(=Cg(RQbP`a|Xv4S|Vxpi&tPCAi4Uz{X3u4kqHHQO16euL>nnw-`1s5U_c0KC-1fUfnkHaCOi0f;dKmrGbsVa>MSz8t!@5|HEkh*YSLw#{$ z!M@D~Rr1?w3vC&=cyUF5c1}cBelcXOUmUgrozg|Lq4Gzt(^98qGx2>hHkU?DN;;4i3XDK2Hr49~0C#PX?IGFDKJxL1s#ur>%-&aInys6U~WR zYaBt?brZ!F+qxTB+S?6~is}fUuz~4$wmym(4kOoArAV7XLSJ zr>Z~LtW*zca8x>l&t*}S5!?Kn4Ly`QZ_YhIxIe!qG&tR5rf*Jd6M zd{pQhKeq=5ZMS7fKPcX&k3f!TopVtSX@C1!evi5)8gXPj-WT!FS9zRB-p^T(D{npN z7BNVu(w7vFO#fV^mN^g>=elrkHgf z&4~nW>*syQ2f;q7fu_QBHYLQipdy4vtkThKCR$?LbVSD(t4!JS?iT0zY6Y~^!qXvf z<}*7LyZe!7bo9CxVu1H%UzgVVOyRuEEW^qgjOur(fky?DLjx7&`iQq=)^n{Q*ko68VL?9Ek4 zb}6RkN5`HkTVypyC>_>83%|9(YY%iMfD&%a&D0VSiPU?rp}66~)2yx-Ky&i9b=n#G zq;%TmoH6E4+qL(K#KVbx8}qSzjkh?Cd(@e3+nqM#fhJ6|sv4bo)2q!NN{dJ|kHa?o z)q==|Za8jW(PWtLIqg$_GT|mmB#d?JExb#-6ING$MkOxqtH@21b&)7o!7lJ2h_%zP zeCbV5v2+ZNSUwM5C4VXJ7W`{shp_IK0jlKZj9O0GJH;JBc&{Zb`)47M)q z9k^evA-&hi_1)>^hJ4x0eG4Lc*0t$(C_*hKtw+RM|A0snezsO-fjzaNZ{FQw0I#O_ zSs2UAGBw0ClG~41?aIYEcwHaKLfH-;Vq^?rdMcd^ekyL#hBA|On4NFQH_2!Y$!~IV zMVT4|iYo|nv9i$xeWW9VR!Fd}YCeL&2*ns)X{<$mrW1dV(|nD%#tS0Y&r1We!q3nK zd}_6BC={=`nquO~KwEWcJp`^Mkxo`L6`MtgckATDJ}qLG58l6rpoS^dvk-P$X8Zs> zel6o|>2!Y;S6+(ULRS^25xhhy+o*9F)C2r|&u!m|ma`l8j$*|%vhnk`^atwO)00gS z&w0{9VE0c-68s&*{E>Nh{Dh$P7cT-m<1*S1+W>o>IDj)E6o22f`P;bu$04{sM)wGL z^?w}+X=tQ8!7I=};3YxMp&SZDZj&hmro}bw$$#r-&CjkHXyKf;LJ8{)F;3KRn;jw+ zE#P?#*hoL7DnJnRNXcM*$T;&@W2OJ}!{f&*9awTw0~r}v30z;k_xChexjTWOX`&SQ z(H}DVC@F|+)xFL>Hj#h2*{;do><9&gfO8+QuvO+QtQo#eUj6D{tn7b8NxokU(XFR7 zya8d<;+5oEJ4nG_=@wZPzC%|)Al3z^rOL~oJ+AgN@ z&uC;tCp=(}67I_G8^2B7puZp3eMmHCUO1sm*S~Y6*gC1Bq$y{~z^SXQKxJxUEsrKc zlT4kSB!DFaBaqJN^W>}sko;xZwGOi4I@JU?SE~q(2&ukJM zq_||o&g`){8WW}U^P#W76pgC<)S_mSk(uy=9=492YzU z*`{&9;13MSjm>Gb7SxU1*DjH&53E1vCPT>t2E4K&nuPn!D}lr$C@cp6N;3ev7#ln_WRT)oPAkCWgaY^tN_ z2k~RDqqIxYoT1%QVk&KL!Y|inkZQ{LgA^oKU? zVh73-Z|uWP!9Mz47i-!kGx(YnE97O}_80SE!DG%OEzb0lVqKITImG_hjJ=Ad*Z?hJ zkKNL>n~28fC89-Ca}k0yRgNS0CcGc9?&wUu8%@ND-ywsI{&jGL!N>#E!I-~Kj)WZj zay{1f&ByBZ29NqleRuC778*KPu@heI{D<)}T6h4?fK(c*iY#*>Lh)0+btm_C#9CAgDLsKg5)Ys2*QEk!&H9 z(9R2NQJfy6v#wn_6+hU$hV|d zEyLk{!g{M&VWm39Jf3mWANEC&EON@* zY*N1u0akV9@`3pJc8(G%SGUF{9@BGtf&LL@S@hTQ7MqE<21mxMD4!2?_-b^dMJ9gJ zG=mATR$mCMqPTjFrqqrZp0{jDgGK8-U=H2Y>HRLkhQ%+#8C0!$jYWaR_>>zBRpwH+ za4apwGCq9lgwro`gdYM#z82md6S5Gg=v7`Aya)I&twz+wrP`tGTS`yds~tFL!1i#>P*k!XVXCmR|MYvuqaRQXL2;yNTL6dQe@Q$#(cRoFPx3hBh#;p;Is8g%(BA7tERjUn}L=0#LT6< zezFR>eA>R->ceayM(xQpn&F z>>sFkG8K)%#mIK`tKS(SlH27Pq3?fmD)x{~v#zp|4!lRO6lr@Z#^po_!0R1meFicZbB}70)K%yGJ z|6jdE00Ok{Pv8LC`&R*og1sn`f(pEpqEbTCFRKl|>hby6G9|$E^T$4ypGu|wS51;v zN>oTtUV%zd=q19>8vh{%oRIrdNeA3p+Zq3j^XH%Zrv^YZ=1=7_;QnWge@nvu6V=Z? zcrHhP+dozOw_N?-n9tnQlFk5)oB{OE&ooxg1q<-*{sMr{+S=i##=MQBuBjCu6!BSE z{$<-td<~590C+Qi3;!7xaHj$Yg!u)u06-zY!dlnhKQeSL0f(&F?B4^ri3iYLhQ9!E z06ZiA1PB;8TwHk_9ZUfFzNY%mf*3CuqAy^_-v8801&ICQ{)Hg}&_Dmc@W+JgCEOP& z>XjS-%n;zB{R`X?@;|}>+G}iQXm2k8NFr@$^&jc}m+)S}G_YELuG|5b%+Evg&t(bl zGX0{p0BK_jM|*%gh>)p;A+NqZfI(8%O85V=slOxuE_Ped0Cc!4pu_)a9HD^M{}%+p zcDjHJj*0*&K@n?9LkVkZb4QzBcg<0mSzIANIGzE}QnKfU1MXmX{UbL-mh`@ zj#cbS04Pg<3jp+|f(`fuzi6`Pf2{7;cp0TAw_gB#%mm2I@K-!=s$b(-**oZ3So}8| z&VX#@AppFo*MN@+&YxNb1cb@>Pk6s>U4bi2ixS`g0RdJIKR?LN#arZ;XcE@O|Mh9V z%ya$>1}FYYu;0ph{?evi=Fom-!I1plEWhrCm$^otF+~)9iTQH!@t4e_FI)UF)88{X ziRv%W|81nd$ouya`emZEXJ}xJe}evN+O?OcFEf@rqrw{g-=O_Z`Ac2`{sj1GWk1W$ z93{`i1_1b{m+EKi&dbU#GnPD8&iZ@h|0Iq0@3X;|@GrAaJj0Wj{H<;Of&ZPA#7m-= zO7hP{;}(A-`cwP=i>~}j_?MFC&+vv;{{;VHviuVEr4slvY_Ih{!T#CL@DlT-tn4%9 zh~qzD{t*T6-@c451zev|o1A_F^}oxxzHI(WQOai`Nw?o6`lEaPEm8TB;H3cIGXbCH zZxH;09NamwGP0v&WfK}UzG44;F_kVxX z{%2pk3~qVGchCF{{C^I0dD(9-V*#GYngQ#P|Mg>jJ;{2x1O1GdRQor~e|5*-_oH97 z^QHIu8PmG;e`EgB81>Sl_>An>@pt6k_b>kK7rgLtJ@bfu{u|HBY3HBJ=zk33FFh~M zG#Fj~H>LY;ewhEX`9EHlKRZo+dR?B2B>?Qd`Cw$kK>+KZp81p@ki D&=%~j literal 0 HcmV?d00001 diff --git a/real-estate-token/gradle/wrapper/gradle-wrapper.properties b/real-estate-token/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..aef7cb78c --- /dev/null +++ b/real-estate-token/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Aug 25 12:50:39 BST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip diff --git a/real-estate-token/gradlew b/real-estate-token/gradlew new file mode 100755 index 000000000..cccdd3d51 --- /dev/null +++ b/real-estate-token/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/real-estate-token/gradlew.bat b/real-estate-token/gradlew.bat new file mode 100644 index 000000000..f9553162f --- /dev/null +++ b/real-estate-token/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/real-estate-token/settings.gradle b/real-estate-token/settings.gradle new file mode 100644 index 000000000..2514aca21 --- /dev/null +++ b/real-estate-token/settings.gradle @@ -0,0 +1,3 @@ +include 'workflows' +include 'contracts' +include 'clients' \ No newline at end of file diff --git a/real-estate-token/workflows/build.gradle b/real-estate-token/workflows/build.gradle new file mode 100644 index 000000000..0fc074a79 --- /dev/null +++ b/real-estate-token/workflows/build.gradle @@ -0,0 +1,61 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version.toInteger() + minimumPlatformVersion corda_platform_version.toInteger() + workflow { + name "Template Flows" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } + test { + resources { + srcDir rootProject.file("config/test") + } + } + integrationTest { + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integrationTest/java') + } + } +} + +configurations { + integrationTestCompile.extendsFrom testCompile + integrationTestRuntime.extendsFrom testRuntime +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_release_group:corda-core:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + + // For testing. + testCompile "junit:junit:$junit_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":contracts") + + // Token SDK dependencies. + cordaCompile "$tokens_release_group:tokens-money:$tokens_release_version" + cordaCompile "$tokens_release_group:tokens-workflows:$tokens_release_version" +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} \ No newline at end of file diff --git a/real-estate-token/workflows/src/integrationTest/java/com/template/DriverBasedTest.java b/real-estate-token/workflows/src/integrationTest/java/com/template/DriverBasedTest.java new file mode 100644 index 000000000..a99b15284 --- /dev/null +++ b/real-estate-token/workflows/src/integrationTest/java/com/template/DriverBasedTest.java @@ -0,0 +1,48 @@ +package com.template; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.driver.DriverParameters; +import net.corda.testing.driver.NodeHandle; +import net.corda.testing.driver.NodeParameters; +import org.junit.Test; + +import java.util.List; + +import static net.corda.testing.driver.Driver.driver; +import static org.junit.Assert.assertEquals; + +public class DriverBasedTest { + private final TestIdentity bankA = new TestIdentity(new CordaX500Name("BankA", "", "GB")); + private final TestIdentity bankB = new TestIdentity(new CordaX500Name("BankB", "", "US")); + + @Test + public void nodeTest() { + driver(new DriverParameters().withIsDebug(true).withStartNodesInProcess(true), dsl -> { + // Start a pair of nodes and wait for them both to be ready. + List> handleFutures = ImmutableList.of( + dsl.startNode(new NodeParameters().withProvidedName(bankA.getName())), + dsl.startNode(new NodeParameters().withProvidedName(bankB.getName())) + ); + + try { + NodeHandle partyAHandle = handleFutures.get(0).get(); + NodeHandle partyBHandle = handleFutures.get(1).get(); + + // From each node, make an RPC call to retrieve another node's name from the network map, to verify that the + // nodes have started and can communicate. + + // This is a very basic test: in practice tests would be starting flows, and verifying the states in the vault + // and other important metrics to ensure that your CorDapp is working as intended. + assertEquals(partyAHandle.getRpc().wellKnownPartyFromX500Name(bankB.getName()).getName(), bankB.getName()); + assertEquals(partyBHandle.getRpc().wellKnownPartyFromX500Name(bankA.getName()).getName(), bankA.getName()); + } catch (Exception e) { + throw new RuntimeException("Caught exception during test: ", e); + } + + return null; + }); + } +} \ No newline at end of file diff --git a/real-estate-token/workflows/src/main/java/com/template/flows/RealEstateEvolvableFungibleTokenFlow.java b/real-estate-token/workflows/src/main/java/com/template/flows/RealEstateEvolvableFungibleTokenFlow.java new file mode 100644 index 000000000..a6c519d6e --- /dev/null +++ b/real-estate-token/workflows/src/main/java/com/template/flows/RealEstateEvolvableFungibleTokenFlow.java @@ -0,0 +1,200 @@ +package com.template.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.google.common.collect.ImmutableList; +import com.r3.corda.lib.tokens.contracts.states.FungibleToken; +import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType; +import com.r3.corda.lib.tokens.contracts.types.TokenPointer; +import com.r3.corda.lib.tokens.contracts.utilities.TransactionUtilitiesKt; +import com.r3.corda.lib.tokens.workflows.flows.rpc.*; +import com.r3.corda.lib.tokens.workflows.types.PartyAndAmount; +import com.template.states.RealEstateEvolvableTokenType; +import net.corda.core.contracts.*; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.identity.Party; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; + +import java.math.BigDecimal; +import java.util.UUID; + +/** + * Create,Issue,Move,Redeem token flows for a house asset on ledger + */ +public class RealEstateEvolvableFungibleTokenFlow { + + private RealEstateEvolvableFungibleTokenFlow() { + //Instantiation not allowed + } + + /** + * Create Fungible Token for a house asset on ledger + */ + @StartableByRPC + public static class CreateEvolvableFungibleTokenFlow extends FlowLogic { + + // valuation property of a house can change hence we are considering house as a evolvable asset + private final BigDecimal valuation; + + public CreateEvolvableFungibleTokenFlow(BigDecimal valuation) { + this.valuation = valuation; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + //grab the notary + Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + + //create token type + RealEstateEvolvableTokenType evolvableTokenType = new RealEstateEvolvableTokenType(valuation, getOurIdentity(), + new UniqueIdentifier(), 0); + + //warp it with transaction state specifying the notary + TransactionState transactionState = new TransactionState(evolvableTokenType, notary); + + //call built in sub flow CreateEvolvableTokens. This can be called via rpc or in unit testing + return (SignedTransaction) subFlow(new CreateEvolvableTokens(transactionState)); + + } + } + + /** + * Issue Fungible Token against an evolvable house asset on ledger + */ + @StartableByRPC + public static class IssueEvolvableFungibleTokenFlow extends FlowLogic{ + private final String tokenId; + private final int quantity; + private final Party holder; + + public IssueEvolvableFungibleTokenFlow(String tokenId, int quantity, Party holder) { + this.tokenId = tokenId; + this.quantity = quantity; + this.holder = holder; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + //get uuid from input tokenId + UUID uuid = UUID.fromString(tokenId); + + //create criteria to get all unconsumed house states on ledger with uuid as input tokenId + QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria(null, ImmutableList.of(uuid), null, + Vault.StateStatus.UNCONSUMED); + StateAndRef stateAndRef = getServiceHub().getVaultService(). + queryBy(RealEstateEvolvableTokenType.class, queryCriteria).getStates().get(0); + + //get the RealEstateEvolvableTokenType object + RealEstateEvolvableTokenType evolvableTokenType = stateAndRef.getState().getData(); + + //get the pointer pointer to the house + TokenPointer tokenPointer = evolvableTokenType.toPointer(evolvableTokenType.getClass()); + + //assign the issuer to the house type who will be issuing the tokens + IssuedTokenType issuedTokenType = new IssuedTokenType(getOurIdentity(), tokenPointer); + + //specify how much amount to issue to holder + Amount amount = new Amount(quantity, issuedTokenType); + + //create fungible amount specifying the new owner + FungibleToken fungibleToken = new FungibleToken(amount, holder, TransactionUtilitiesKt.getAttachmentIdForGenericParam(tokenPointer)); + + //use built in flow for issuing tokens on ledger + subFlow(new IssueTokens(ImmutableList.of(fungibleToken))); + return null; + } + } + + /** + * Move created fungible tokens to other party + */ + @StartableByRPC + public static class MoveEvolvableFungibleTokenFlow extends FlowLogic{ + private final String tokenId; + private final Party holder; + private final int quantity; + + + public MoveEvolvableFungibleTokenFlow(String tokenId, Party holder, int quantity) { + this.tokenId = tokenId; + this.holder = holder; + this.quantity = quantity; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + //get uuid from input tokenId + UUID uuid = UUID.fromString(tokenId); + + //create criteria to get all unconsumed house states on ledger with uuid as input tokenId + QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria(null, ImmutableList.of(uuid), null, + Vault.StateStatus.UNCONSUMED, null); + StateAndRef stateAndRef = getServiceHub().getVaultService(). + queryBy(RealEstateEvolvableTokenType.class, queryCriteria).getStates().get(0); + + //get the RealEstateEvolvableTokenType object + RealEstateEvolvableTokenType evolvableTokenType = stateAndRef.getState().getData(); + + //get the pointer pointer to the house + TokenPointer tokenPointer = evolvableTokenType.toPointer(evolvableTokenType.getClass()); + + //specify how much amount to transfer to which holder + Amount amount = new Amount(quantity, tokenPointer); + PartyAndAmount partyAndAmount = new PartyAndAmount(holder, amount); + + //use built in flow to move fungible tokens to holder + subFlow(new MoveFungibleTokens(partyAndAmount)); + return null; + } + } + + /** + * Holder Redeems fungible token issued by issuer + */ + @StartableByRPC + public static class RedeemHouseFungibleTokenFlow extends FlowLogic { + + private final String tokenId; + private final Party issuer; + private final int quantity; + + public RedeemHouseFungibleTokenFlow(String tokenId, Party issuer, int quantity) { + this.tokenId = tokenId; + this.issuer = issuer; + this.quantity = quantity; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + //get uuid from input tokenId + UUID uuid = UUID.fromString(tokenId); + + //create criteria to get all unconsumed house states on ledger with uuid as input tokenId + QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria(null, ImmutableList.of(uuid), null, + Vault.StateStatus.UNCONSUMED, null); + StateAndRef stateAndRef = getServiceHub().getVaultService(). + queryBy(RealEstateEvolvableTokenType.class, queryCriteria).getStates().get(0); + + //get the RealEstateEvolvableTokenType object + RealEstateEvolvableTokenType evolvableTokenType = stateAndRef.getState().getData(); + + //get the pointer pointer to the house + TokenPointer tokenPointer = evolvableTokenType.toPointer(evolvableTokenType.getClass()); + + //specify how much amount quantity of tokens of type token parameter + Amount amount = new Amount(quantity, tokenPointer); + + //call built in redeem flow to redeem tokens with issuer + subFlow(new RedeemFungibleTokens(amount, issuer)); + return null; + } + } +} + diff --git a/real-estate-token/workflows/src/main/java/com/template/flows/RealEstateEvolvableNonFungibleTokenFlow.java b/real-estate-token/workflows/src/main/java/com/template/flows/RealEstateEvolvableNonFungibleTokenFlow.java new file mode 100644 index 000000000..1aff96438 --- /dev/null +++ b/real-estate-token/workflows/src/main/java/com/template/flows/RealEstateEvolvableNonFungibleTokenFlow.java @@ -0,0 +1,183 @@ +package com.template.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.google.common.collect.ImmutableList; +import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken; +import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType; +import com.r3.corda.lib.tokens.contracts.types.TokenPointer; +import com.r3.corda.lib.tokens.contracts.utilities.TransactionUtilitiesKt; +import com.r3.corda.lib.tokens.workflows.flows.rpc.CreateEvolvableTokens; +import com.r3.corda.lib.tokens.workflows.flows.rpc.IssueTokens; +import com.r3.corda.lib.tokens.workflows.flows.rpc.MoveNonFungibleTokens; +import com.r3.corda.lib.tokens.workflows.flows.rpc.RedeemNonFungibleTokens; +import com.r3.corda.lib.tokens.workflows.types.PartyAndToken; +import com.template.states.RealEstateEvolvableTokenType; +import net.corda.core.contracts.*; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.identity.Party; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; + +import java.math.BigDecimal; +import java.util.UUID; + +public class RealEstateEvolvableNonFungibleTokenFlow { + + private RealEstateEvolvableNonFungibleTokenFlow() { + //Instantiation not allowed + } + + /** + * Create NonFungible Token in ledger + */ + @StartableByRPC + public static class CreateEvolvableTokenFlow extends FlowLogic { + + private final BigDecimal valuation; + + public CreateEvolvableTokenFlow(BigDecimal valuation) { + this.valuation = valuation; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + //grab the notary + Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + + //create token type + RealEstateEvolvableTokenType evolvableTokenType = new RealEstateEvolvableTokenType(valuation, getOurIdentity(), + new UniqueIdentifier(), 0); + + //warp it with transaction state specifying the notary + TransactionState transactionState = new TransactionState(evolvableTokenType, notary); + + //call built in sub flow CreateEvolvableTokens. This can be called via rpc or in unit testing + return (SignedTransaction) subFlow(new CreateEvolvableTokens(transactionState)); + } + } + + /** + * Issue Non Fungible Token + */ + @StartableByRPC + public static class IssueEvolvableTokenFlow extends FlowLogic{ + private final String tokenId; + private final Party holder; + + public IssueEvolvableTokenFlow(String tokenId, Party recipient) { + this.tokenId = tokenId; + this.holder = recipient; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + //using id of my house to grab the house from db. + // you can use any custom criteria depending on your requirements + UUID uuid = UUID.fromString(tokenId); + + //construct the query criteria + QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria(null, ImmutableList.of(uuid), null, + Vault.StateStatus.UNCONSUMED, null); + + // grab the house off the ledger + StateAndRef stateAndRef = getServiceHub().getVaultService(). + queryBy(RealEstateEvolvableTokenType.class, queryCriteria).getStates().get(0); + RealEstateEvolvableTokenType evolvableTokenType = stateAndRef.getState().getData(); + + //get the pointer pointer to the house + TokenPointer tokenPointer = evolvableTokenType.toPointer(evolvableTokenType.getClass()); + + //assign the issuer to the house type who will be issuing the tokens + IssuedTokenType issuedTokenType = new IssuedTokenType(getOurIdentity(), tokenPointer); + + //mention the current holder also + NonFungibleToken nonFungibleToken = new NonFungibleToken(issuedTokenType, holder, new UniqueIdentifier(), TransactionUtilitiesKt.getAttachmentIdForGenericParam(tokenPointer)); + + //call built in flow to issue non fungible tokens + return (SignedTransaction) subFlow(new IssueTokens(ImmutableList.of(nonFungibleToken))); + } + } + + /** + * Move created non fungible token to other party + */ + @StartableByRPC + public static class MoveEvolvableTokenFlow extends FlowLogic{ + private final String tokenId; + private final Party holder; + + + public MoveEvolvableTokenFlow(String tokenId, Party recipient) { + this.tokenId = tokenId; + this.holder = recipient; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + //using id of my house to grab the house from db. + //you can use any custom criteria depending on your requirements + UUID uuid = UUID.fromString(tokenId); + + //construct the query criteria + QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria(null, ImmutableList.of(uuid), null, + Vault.StateStatus.UNCONSUMED, null); + + // grab the house off the ledger + StateAndRef stateAndRef = getServiceHub().getVaultService(). + queryBy(RealEstateEvolvableTokenType.class, queryCriteria).getStates().get(0); + RealEstateEvolvableTokenType evolvableTokenType = stateAndRef.getState().getData(); + + //get the pointer pointer to the house + TokenPointer tokenPointer = evolvableTokenType.toPointer(evolvableTokenType.getClass()); + + //specify the party who will be the new owner of the token + PartyAndToken partyAndToken = new PartyAndToken(holder, tokenPointer); + return (SignedTransaction) subFlow(new MoveNonFungibleTokens(partyAndToken)); + } + } + + /** + * Holder Redeems non fungible token issued by issuer + */ + @StartableByRPC + public static class RedeemHouseToken extends FlowLogic { + + private final String tokenId; + private final Party issuer; + + public RedeemHouseToken(String tokenId, Party issuer) { + this.tokenId = tokenId; + this.issuer = issuer; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + //using id of my house to grab the house from db. + //you can use any custom criteria depending on your requirements + UUID uuid = UUID.fromString(tokenId); + + //construct the query criteria + QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria(null, ImmutableList.of(uuid), null, + Vault.StateStatus.UNCONSUMED, null); + + // grab the house off the ledger + StateAndRef stateAndRef = getServiceHub().getVaultService(). + queryBy(RealEstateEvolvableTokenType.class, queryCriteria).getStates().get(0); + RealEstateEvolvableTokenType evolvableTokenType = stateAndRef.getState().getData(); + + //get the pointer pointer to the house + TokenPointer token = evolvableTokenType.toPointer(evolvableTokenType.getClass()); + + //call built in flow to redeem the tokens + return (SignedTransaction) subFlow(new RedeemNonFungibleTokens(token, issuer)); + } + } +} + diff --git a/real-estate-token/workflows/src/test/java/com/template/ContractTests.java b/real-estate-token/workflows/src/test/java/com/template/ContractTests.java new file mode 100644 index 000000000..ed7c4615f --- /dev/null +++ b/real-estate-token/workflows/src/test/java/com/template/ContractTests.java @@ -0,0 +1,13 @@ +package com.template; + +import net.corda.testing.node.MockServices; +import org.junit.Test; + +public class ContractTests { + private final MockServices ledgerServices = new MockServices(); + + @Test + public void dummyTest() { + + } +} \ No newline at end of file diff --git a/real-estate-token/workflows/src/test/java/com/template/FlowTests.java b/real-estate-token/workflows/src/test/java/com/template/FlowTests.java new file mode 100644 index 000000000..d6f97a9e6 --- /dev/null +++ b/real-estate-token/workflows/src/test/java/com/template/FlowTests.java @@ -0,0 +1,29 @@ +package com.template; + +import com.google.common.collect.ImmutableList; +import net.corda.testing.node.MockNetwork; +import net.corda.testing.node.StartedMockNode; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class FlowTests { + private final MockNetwork network = new MockNetwork(ImmutableList.of("com.template")); + private final StartedMockNode a = network.createNode(); + private final StartedMockNode b = network.createNode(); + + @Before + public void setup() { + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void dummyTest() { + + } +} diff --git a/real-estate-token/workflows/src/test/java/com/template/NodeDriver.java b/real-estate-token/workflows/src/test/java/com/template/NodeDriver.java new file mode 100644 index 000000000..568c31d20 --- /dev/null +++ b/real-estate-token/workflows/src/test/java/com/template/NodeDriver.java @@ -0,0 +1,39 @@ +package com.template; + +import com.google.common.collect.ImmutableList; +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.driver.DriverParameters; +import net.corda.testing.driver.NodeParameters; +import net.corda.testing.node.User; +import com.google.common.collect.ImmutableSet; + +import java.util.List; +import static net.corda.testing.driver.Driver.driver; + +/** + * Allows you to run your nodes through an IDE (as opposed to using deployNodes). Do not use in a production + * environment. + */ +public class NodeDriver { + public static void main(String[] args) { + final List rpcUsers = + ImmutableList.of(new User("user1", "test", ImmutableSet.of("ALL"))); + + driver(new DriverParameters().withStartNodesInProcess(true).withWaitForAllNodesToFinish(true), dsl -> { + try { + dsl.startNode(new NodeParameters() + .withProvidedName(new CordaX500Name("PartyA", "London", "GB")) + .withRpcUsers(rpcUsers)).get(); + dsl.startNode(new NodeParameters() + .withProvidedName(new CordaX500Name("PartyB", "New York", "US")) + .withRpcUsers(rpcUsers)).get(); + } catch (Throwable e) { + System.err.println("Encountered exception in node startup: " + e.getMessage()); + e.printStackTrace(); + } + + return null; + } + ); + } +} From 3e44af9e871a8e38accb91e8362138f853e4d4f7 Mon Sep 17 00:00:00 2001 From: ashutoshmeher-r3 Date: Tue, 27 Aug 2019 13:48:02 +0530 Subject: [PATCH 14/20] updated to token release v1.0 --- non-fungible-token-dvp/build.gradle | 5 +- non-fungible-token-dvp/clients/build.gradle | 47 ------------------ .../tokenSDK/samples/contracts/Client.java | 36 -------------- .../contracts/webserver/Controller.java | 27 ----------- .../webserver/NodeRPCConnection.java | 48 ------------------- .../samples/contracts/webserver/Starter.java | 23 --------- .../clients/src/main/resources/static/app.js | 3 -- .../src/main/resources/static/index.html | 10 ---- non-fungible-token-dvp/settings.gradle | 3 +- .../flows/FiatCurrencyIssueFlow.java | 18 +++++-- .../flows/HouseSaleInitiatorFlow.java | 4 +- .../flows/HouseSaleResponderFlow.java | 11 +++-- .../flows/HouseTokenCreateAndIssueFlow.java | 22 +++++---- .../flows/UpdateHouseValuationFlow.java | 2 +- 14 files changed, 41 insertions(+), 218 deletions(-) delete mode 100644 non-fungible-token-dvp/clients/build.gradle delete mode 100644 non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/Client.java delete mode 100644 non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/Controller.java delete mode 100644 non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/NodeRPCConnection.java delete mode 100644 non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/Starter.java delete mode 100644 non-fungible-token-dvp/clients/src/main/resources/static/app.js delete mode 100644 non-fungible-token-dvp/clients/src/main/resources/static/index.html diff --git a/non-fungible-token-dvp/build.gradle b/non-fungible-token-dvp/build.gradle index 8afc41e5f..f20bf042d 100644 --- a/non-fungible-token-dvp/build.gradle +++ b/non-fungible-token-dvp/build.gradle @@ -3,7 +3,7 @@ buildscript { corda_release_group = 'net.corda' corda_release_version = '4.1' tokens_release_group = 'com.r3.corda.lib.tokens' - tokens_release_version = '1.0-RC03' + tokens_release_version = '1.0' corda_gradle_plugins_version = '4.0.42' junit_version = '4.12' quasar_version = '0.7.10' @@ -39,7 +39,8 @@ allprojects { maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } maven { url 'https://jitpack.io' } // Can be removed post-release - used to get nightly snapshot build. - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-tokens-dev' } + maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-lib' } + maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-lib-dev' } } tasks.withType(JavaCompile) { diff --git a/non-fungible-token-dvp/clients/build.gradle b/non-fungible-token-dvp/clients/build.gradle deleted file mode 100644 index 783276424..000000000 --- a/non-fungible-token-dvp/clients/build.gradle +++ /dev/null @@ -1,47 +0,0 @@ -apply plugin: 'org.springframework.boot' - -sourceSets { - main { - resources { - srcDir rootProject.file("config/dev") - } - } -} - -dependencies { - // Corda dependencies. - compile "$corda_release_group:corda-rpc:$corda_release_version" - - // CorDapp dependencies. - compile project(":contracts") - compile project(":workflows") - - compile("org.springframework.boot:spring-boot-starter-websocket:$spring_boot_version") { - exclude group: "org.springframework.boot", module: "spring-boot-starter-logging" - } - - compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" - compile "org.apache.logging.log4j:log4j-web:${log4j_version}" - compile "org.slf4j:jul-to-slf4j:$slf4j_version" - - // Token SDK dependencies. - compile "com.r3.tokens-sdk:contract:1.0-SNAPSHOT" - compile "com.r3.tokens-sdk:workflow:1.0-SNAPSHOT" - compile "com.r3.tokens-sdk:money:1.0-SNAPSHOT" -} - -springBoot { - mainClassName = "com.template.webserver.Server" -} - -task runTemplateClient(type: JavaExec, dependsOn: assemble) { - classpath = sourceSets.main.runtimeClasspath - main = 'com.template.Client' - args 'localhost:10006', 'user1', 'test' -} - -task runTemplateServer(type: JavaExec, dependsOn: assemble) { - classpath = sourceSets.main.runtimeClasspath - main = 'com.template.webserver.Starter' - args '--server.port=10050', '--config.rpc.host=localhost', '--config.rpc.port=10006', '--config.rpc.username=user1', '--config.rpc.password=test' -} diff --git a/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/Client.java b/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/Client.java deleted file mode 100644 index 19c9189ef..000000000 --- a/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/Client.java +++ /dev/null @@ -1,36 +0,0 @@ -package corda.tokenSDK.samples.contracts; - -import net.corda.client.rpc.CordaRPCClient; -import net.corda.core.messaging.CordaRPCOps; -import net.corda.core.node.NodeInfo; -import net.corda.core.utilities.NetworkHostAndPort; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; - -import static net.corda.core.utilities.NetworkHostAndPort.parse; - -/** - * Connects to a Corda node via RPC and performs RPC operations on the node. - * - * The RPC connection is configured using command line arguments. - */ -public class Client { - private static final Logger logger = LoggerFactory.getLogger(Client.class); - - public static void main(String[] args) { - // Create an RPC connection to the node. - if (args.length != 3) throw new IllegalArgumentException("Usage: Client "); - final NetworkHostAndPort nodeAddress = parse(args[0]); - final String rpcUsername = args[1]; - final String rpcPassword = args[2]; - final CordaRPCClient client = new CordaRPCClient(nodeAddress); - final CordaRPCOps proxy = client.start(rpcUsername, rpcPassword).getProxy(); - - // Interact with the node. - // For example, here we print the nodes on the network. - final List nodes = proxy.networkMapSnapshot(); - logger.info("{}", nodes); - } -} \ No newline at end of file diff --git a/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/Controller.java b/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/Controller.java deleted file mode 100644 index 2b63d5604..000000000 --- a/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/Controller.java +++ /dev/null @@ -1,27 +0,0 @@ -package corda.tokenSDK.samples.contracts.webserver; - -import net.corda.core.messaging.CordaRPCOps; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * Define your API endpoints here. - */ -@RestController -@RequestMapping("/") // The paths for HTTP requests are relative to this base path. -public class Controller { - private final CordaRPCOps proxy; - private final static Logger logger = LoggerFactory.getLogger(Controller.class); - - public Controller(NodeRPCConnection rpc) { - this.proxy = rpc.proxy; - } - - @GetMapping(value = "/templateendpoint", produces = "text/plain") - private String templateendpoint() { - return "Define an endpoint here."; - } -} \ No newline at end of file diff --git a/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/NodeRPCConnection.java b/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/NodeRPCConnection.java deleted file mode 100644 index 59cb56513..000000000 --- a/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/NodeRPCConnection.java +++ /dev/null @@ -1,48 +0,0 @@ -package corda.tokenSDK.samples.contracts.webserver; - -import net.corda.client.rpc.CordaRPCClient; -import net.corda.client.rpc.CordaRPCConnection; -import net.corda.core.messaging.CordaRPCOps; -import net.corda.core.utilities.NetworkHostAndPort; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; - -/** - * Wraps an RPC connection to a Corda node. - * - * The RPC connection is configured using command line arguments. - */ -@Component -public class NodeRPCConnection implements AutoCloseable { - // The host of the node we are connecting to. - @Value("${config.rpc.host}") - private String host; - // The RPC port of the node we are connecting to. - @Value("${config.rpc.username}") - private String username; - // The username for logging into the RPC client. - @Value("${config.rpc.password}") - private String password; - // The password for logging into the RPC client. - @Value("${config.rpc.port}") - private int rpcPort; - - private CordaRPCConnection rpcConnection; - CordaRPCOps proxy; - - @PostConstruct - public void initialiseNodeRPCConnection() { - NetworkHostAndPort rpcAddress = new NetworkHostAndPort(host, rpcPort); - CordaRPCClient rpcClient = new CordaRPCClient(rpcAddress); - rpcConnection = rpcClient.start(username, password); - proxy = rpcConnection.getProxy(); - } - - @PreDestroy - public void close() { - rpcConnection.notifyServerAndClose(); - } -} \ No newline at end of file diff --git a/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/Starter.java b/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/Starter.java deleted file mode 100644 index 01c1201c9..000000000 --- a/non-fungible-token-dvp/clients/src/main/java/corda/tokenSDK/samples/contracts/webserver/Starter.java +++ /dev/null @@ -1,23 +0,0 @@ -package corda.tokenSDK.samples.contracts.webserver; - -import org.springframework.boot.Banner; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -import static org.springframework.boot.WebApplicationType.SERVLET; - -/** - * Our Spring Boot application. - */ -@SpringBootApplication -public class Starter { - /** - * Starts our Spring Boot application. - */ - public static void main(String[] args) { - SpringApplication app = new SpringApplication(Starter.class); - app.setBannerMode(Banner.Mode.OFF); - app.setWebApplicationType(SERVLET); - app.run(args); - } -} \ No newline at end of file diff --git a/non-fungible-token-dvp/clients/src/main/resources/static/app.js b/non-fungible-token-dvp/clients/src/main/resources/static/app.js deleted file mode 100644 index c58d2de8c..000000000 --- a/non-fungible-token-dvp/clients/src/main/resources/static/app.js +++ /dev/null @@ -1,3 +0,0 @@ -"use strict"; - -// Define your client-side logic here. \ No newline at end of file diff --git a/non-fungible-token-dvp/clients/src/main/resources/static/index.html b/non-fungible-token-dvp/clients/src/main/resources/static/index.html deleted file mode 100644 index 758501dd0..000000000 --- a/non-fungible-token-dvp/clients/src/main/resources/static/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Example front-end. - - -
Define your front-end here.
- - \ No newline at end of file diff --git a/non-fungible-token-dvp/settings.gradle b/non-fungible-token-dvp/settings.gradle index 2514aca21..b4446eaf8 100644 --- a/non-fungible-token-dvp/settings.gradle +++ b/non-fungible-token-dvp/settings.gradle @@ -1,3 +1,2 @@ include 'workflows' -include 'contracts' -include 'clients' \ No newline at end of file +include 'contracts' \ No newline at end of file diff --git a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/FiatCurrencyIssueFlow.java b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/FiatCurrencyIssueFlow.java index 66aea21c4..5a402822e 100644 --- a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/FiatCurrencyIssueFlow.java +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/FiatCurrencyIssueFlow.java @@ -1,17 +1,20 @@ package corda.tokenSDK.samples.contracts.flows; import co.paralleluniverse.fibers.Suspendable; +import com.google.common.collect.ImmutableList; +import com.r3.corda.lib.tokens.contracts.states.FungibleToken; +import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType; +import com.r3.corda.lib.tokens.contracts.types.TokenType; import com.r3.corda.lib.tokens.money.FiatCurrency; import com.r3.corda.lib.tokens.workflows.flows.rpc.IssueTokens; import net.corda.core.contracts.Amount; import net.corda.core.flows.FlowException; import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.FlowSession; import net.corda.core.flows.StartableByRPC; import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; -import java.util.Currency; - /** * Flow class to issue fiat currency. FiatCurrency is defined in the TokenSDK and is issued as a Fungible Token. This constructor takes the currecy code * for the currency to be issued, the amount of the currency to be issued and the recipient as input parameters. @@ -33,9 +36,16 @@ public FiatCurrencyIssueFlow(String currency, Long amount, Party recipient) { @Suspendable public SignedTransaction call() throws FlowException { /* Create an instance of the fiat currency token */ - FiatCurrency token = new FiatCurrency(Currency.getInstance(currency)); + TokenType token = FiatCurrency.Companion.getInstance(currency); + + /* Create an instance of IssuedTokenType for the fiat currency */ + IssuedTokenType issuedTokenType = new IssuedTokenType(getOurIdentity(), token); + + /* Create an instance of FungibleToken for the fiat currency to be issued */ + FungibleToken fungibleToken = new FungibleToken(new Amount<>(amount, issuedTokenType), recipient, null); + /* Issue the required amount of the token to the recipient */ - return subFlow(new IssueTokens<>(new Amount<>(amount, token), getOurIdentity(), recipient)); + return subFlow(new IssueTokens(ImmutableList.of(fungibleToken), ImmutableList.of(recipient))); } } diff --git a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleInitiatorFlow.java b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleInitiatorFlow.java index 8b2c28132..7d6839476 100644 --- a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleInitiatorFlow.java +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleInitiatorFlow.java @@ -62,9 +62,9 @@ public SignedTransaction call() throws FlowException { // Send the house valuation to the buyer. buyerSession.send(houseState.getValuation()); // Recieve inputStatesAndRef for the fiat currency exchange from the buyer, these would be inputs to the fiat currency exchange transaction. - List>> inputs = subFlow(new ReceiveStateAndRefFlow<>(buyerSession)); + List> inputs = subFlow(new ReceiveStateAndRefFlow<>(buyerSession)); // Recieve output for the fiat currency from the buyer, this would contain the transfered amount from buyer to yourself - List> moneyReceived = buyerSession.receive(List.class).unwrap(value -> value); + List moneyReceived = buyerSession.receive(List.class).unwrap(value -> value); /* Create a fiat currency proposal for the house token using the helper function provided by Token SDK. */ MoveTokensUtilitiesKt.addMoveTokens(txBuilder, inputs, moneyReceived); diff --git a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleResponderFlow.java b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleResponderFlow.java index c79ecbf58..26d5c4c30 100644 --- a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleResponderFlow.java +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleResponderFlow.java @@ -3,6 +3,8 @@ import co.paralleluniverse.fibers.Suspendable; import com.google.common.collect.ImmutableList; import com.r3.corda.lib.tokens.contracts.states.FungibleToken; +import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType; +import com.r3.corda.lib.tokens.contracts.types.TokenType; import com.r3.corda.lib.tokens.money.FiatCurrency; import com.r3.corda.lib.tokens.workflows.internal.selection.TokenSelection; import com.r3.corda.lib.tokens.workflows.types.PartyAndAmount; @@ -35,8 +37,9 @@ public SignedTransaction call() throws FlowException { /* Recieve the valuation of the house */ Amount price = counterpartySession.receive(Amount.class).unwrap(amount -> amount); - /* Create instance of the fiat currecy token */ - Amount priceToken = new Amount<>(price.getQuantity(), new FiatCurrency(price.getToken())); + + /* Create instance of the fiat currecy token amount */ + Amount priceToken = new Amount<>(price.getQuantity(), FiatCurrency.Companion.getInstance(price.getToken().getCurrencyCode())); /* Create an instance of the TokenSelection object, it is used to select the token from the vault and generate the proposal for the movement of the token * The constructor takes the service hub to perform vault query, the max-number of retries, the retry sleep interval, and the retry sleep cap interval. This @@ -47,8 +50,8 @@ public SignedTransaction call() throws FlowException { /* * Generate the move proposal, it returns the input-output pair for the fiat currency transfer, which we need to send to the Initiator. * */ - PartyAndAmount partyAndAmount = new PartyAndAmount<>(counterpartySession.getCounterparty(), priceToken); - Pair>>, List>> inputsAndOutputs = + PartyAndAmount partyAndAmount = new PartyAndAmount<>(counterpartySession.getCounterparty(), priceToken); + Pair>, List> inputsAndOutputs = tokenSelection.generateMove(getRunId().getUuid(), ImmutableList.of(partyAndAmount), getOurIdentity(), null); /* Call SendStateAndRefFlow to send the inputs to the Initiator*/ diff --git a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseTokenCreateAndIssueFlow.java b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseTokenCreateAndIssueFlow.java index 670fecec6..e492a3401 100644 --- a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseTokenCreateAndIssueFlow.java +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseTokenCreateAndIssueFlow.java @@ -4,12 +4,12 @@ import com.google.common.collect.ImmutableList; import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken; import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType; -import com.r3.corda.lib.tokens.contracts.types.TokenPointer; import com.r3.corda.lib.tokens.contracts.utilities.TransactionUtilitiesKt; -import com.r3.corda.lib.tokens.workflows.flows.evolvable.CreateEvolvableToken; +import com.r3.corda.lib.tokens.workflows.flows.rpc.CreateEvolvableTokens; import com.r3.corda.lib.tokens.workflows.flows.rpc.IssueTokens; import corda.tokenSDK.samples.states.HouseState; import net.corda.core.contracts.Amount; +import net.corda.core.contracts.TransactionState; import net.corda.core.contracts.UniqueIdentifier; import net.corda.core.flows.FlowException; import net.corda.core.flows.FlowLogic; @@ -56,8 +56,12 @@ public SignedTransaction call() throws FlowException { /* Construct the output state */ final HouseState houseState = new HouseState(UniqueIdentifier.Companion.fromString(UUID.randomUUID().toString()), ImmutableList.of(issuer), valuation, noOfBedRooms, constructionArea, additionInfo, address); - /* Create the house token. TokenSDK provides the CreateEvolvableToken flow which could be called to create an evolvable token in the ledger.*/ - subFlow(new CreateEvolvableToken<>(houseState, notary)); + + /* Create an instance of TransactionState using the houseState token and the notary */ + TransactionState transactionState = new TransactionState<>(houseState, notary); + + /* Create the house token. TokenSDK provides the CreateEvolvableTokens flow which could be called to create an evolvable token in the ledger.*/ + subFlow(new CreateEvolvableTokens(transactionState)); /* * Create an instance of IssuedTokenType, it is used by our Non-Fungible token which would be issued to the owner. Note that the IssuedTokenType takes @@ -65,13 +69,13 @@ public SignedTransaction call() throws FlowException { * so that the state can evolve independently. * IssuedTokenType is a wrapper around the TokenType and the issuer. * */ - IssuedTokenType> issuedHouseToken = new IssuedTokenType<>(issuer, houseState.toPointer()); + IssuedTokenType issuedHouseToken = new IssuedTokenType(issuer, houseState.toPointer()); /* Create an instance of the non-fungible house token with the owner as the token holder. The last paramter is a hash of the jar containing the TokenType, use the helper function to fetch it. */ - NonFungibleToken> houseToken = - new NonFungibleToken<>(issuedHouseToken, owner, UniqueIdentifier.Companion.fromString(UUID.randomUUID().toString()), TransactionUtilitiesKt.getAttachmentIdForGenericParam(houseState.toPointer())); + NonFungibleToken houseToken = + new NonFungibleToken(issuedHouseToken, owner, UniqueIdentifier.Companion.fromString(UUID.randomUUID().toString()), TransactionUtilitiesKt.getAttachmentIdForGenericParam(houseState.toPointer())); - /* Issue the house token by calling the IssueToken flow provided with the TokenSDK */ - return subFlow(new IssueTokens<>(houseToken)); + /* Issue the house token by calling the IssueTokens flow provided with the TokenSDK */ + return subFlow(new IssueTokens(ImmutableList.of(houseToken))); } } diff --git a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/UpdateHouseValuationFlow.java b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/UpdateHouseValuationFlow.java index b7a50b96a..942ff0d84 100644 --- a/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/UpdateHouseValuationFlow.java +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/UpdateHouseValuationFlow.java @@ -2,7 +2,7 @@ import co.paralleluniverse.fibers.Suspendable; import com.google.common.collect.ImmutableList; -import com.r3.corda.lib.tokens.workflows.flows.evolvable.UpdateEvolvableToken; +import com.r3.corda.lib.tokens.workflows.flows.rpc.UpdateEvolvableToken; import corda.tokenSDK.samples.states.HouseState; import net.corda.core.contracts.Amount; import net.corda.core.contracts.StateAndRef; From 809ab3191aec0e5bdf6efcecf33a010a3b805fde Mon Sep 17 00:00:00 2001 From: snehadamle Date: Tue, 27 Aug 2019 17:21:45 +0530 Subject: [PATCH 15/20] Basic Java RPC Client --- .../com/example/server/JavaClientRpc.java | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 cordapp-example/clients/src/main/java/com/example/server/JavaClientRpc.java diff --git a/cordapp-example/clients/src/main/java/com/example/server/JavaClientRpc.java b/cordapp-example/clients/src/main/java/com/example/server/JavaClientRpc.java new file mode 100644 index 000000000..dd459b46b --- /dev/null +++ b/cordapp-example/clients/src/main/java/com/example/server/JavaClientRpc.java @@ -0,0 +1,71 @@ +package com.example.server; + +import com.example.state.IOUState; +import net.corda.client.rpc.CordaRPCClient; +import net.corda.client.rpc.CordaRPCConnection; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.messaging.DataFeed; +import net.corda.core.node.NodeInfo; +import net.corda.core.node.services.Vault; +import net.corda.core.utilities.NetworkHostAndPort; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.Observable; + +import java.util.List; + +/** + * Demonstration of using the CordaRPCClient to connect to a Corda Node. + */ +public class JavaClientRpc { + + private static final Logger logger = LoggerFactory.getLogger(JavaClientRpc.class); + + public static void main(String[] args) { + //Get the node address to connect to, rpc username , rpc password via command line + if (args.length != 3) throw new IllegalArgumentException("Usage: Client "); + + NetworkHostAndPort networkHostAndPort = NetworkHostAndPort.parse(args[0]); + String rpcUsername = args[1]; + String rpcPassword = args[2]; + + /*get the client handle which has the start method + Secure SSL connection can be established with the server if specified by the client. + This can be configured by specifying the truststore path containing the RPC SSL certificate in the while creating CordaRPCClient instance.*/ + CordaRPCClient client = new CordaRPCClient(networkHostAndPort); + + //start method establishes conenction with the server, starts off a proxy handler and return a wrapper around proxy. + CordaRPCConnection rpcConnection = client.start(rpcUsername, rpcPassword); + + //proxy is used to convert the client high level calls to artemis specific low level messages + CordaRPCOps proxy = rpcConnection.getProxy(); + + //hit the node to retrieve network map + List nodes = proxy.networkMapSnapshot(); + logger.info("All the nodes available in this network", nodes); + + //hit the node to get snapshot and observable for IOUState + DataFeed, Vault.Update> dataFeed = proxy.vaultTrack(IOUState.class); + + //this gives a snapshot of IOUState as of now. so if there are 11 IOUState as of now, this will return 11 IOUState objects + Vault.Page snapshot = dataFeed.getSnapshot(); + + //this returns an observable on IOUState + Observable> updates = dataFeed.getUpdates(); + + // call a method for each IOUState + snapshot.getStates().forEach(JavaClientRpc::actionToPerform); + + //perform certain action for each update to IOUState + updates.toBlocking().subscribe(update -> update.getProduced().forEach(JavaClientRpc::actionToPerform)); + } + + /** + * Perform certain action because of any update to Observable IOUState + * @param state + */ + private static void actionToPerform(StateAndRef state) { + logger.info("{}", state.getState().getData()); + } +} From aebf9a71782c5a0034b566ff572c85325b3f8803 Mon Sep 17 00:00:00 2001 From: snehadamle Date: Mon, 16 Sep 2019 16:52:29 +0530 Subject: [PATCH 16/20] Updated version 1.1-SNAPSHOT to 1.0 --- real-estate-token/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/real-estate-token/build.gradle b/real-estate-token/build.gradle index 4f9211958..543c73c2c 100644 --- a/real-estate-token/build.gradle +++ b/real-estate-token/build.gradle @@ -3,7 +3,7 @@ buildscript { corda_release_group = 'net.corda' corda_release_version = '4.1' tokens_release_group = 'com.r3.corda.lib.tokens' - tokens_release_version = '1.1-SNAPSHOT' + tokens_release_version = '1.0' corda_gradle_plugins_version = '4.0.42' junit_version = '4.12' quasar_version = '0.7.10' @@ -39,6 +39,7 @@ allprojects { maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } maven { url 'https://jitpack.io' } // Can be removed post-release - used to get nightly snapshot build. + maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-lib' } maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-tokens-dev' } } From f6d3ff471144792e0755ba42fb2394c3efd24b53 Mon Sep 17 00:00:00 2001 From: snehadamle Date: Wed, 25 Sep 2019 15:29:06 +0530 Subject: [PATCH 17/20] Updated readme --- real-estate-token/README.md | 12 ++++++------ .../flows/RealEstateEvolvableFungibleTokenFlow.java | 11 ++++------- .../RealEstateEvolvableNonFungibleTokenFlow.java | 6 +++--- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/real-estate-token/README.md b/real-estate-token/README.md index bb6cd8cb0..45378bce8 100644 --- a/real-estate-token/README.md +++ b/real-estate-token/README.md @@ -45,15 +45,15 @@ Get the uuid of the house type from PartyA's terminal by hitting below command. PartyA will now issue some tokens to PartB. Fire below command via PartyA's terminal using uuid collected from previous step. - start IssueEvolvableFungibleTokenFlow tokenId : 680922c8-a0c8-4440-9d72-7dfefb380661 , quantity : 10 , holder : PartyB + start IssueEvolvableFungibleTokenFlow tokenId : 61ec42bc-4fed-4e6f-aeb7-8e93d1b3e471 , quantity : 10 , holder : PartyB Since PartyB now has 10 tokens, Move tokens to PartyC from PartyB s terminal - start MoveEvolvableFungibleTokenFlow tokenId : 680922c8-a0c8-4440-9d72-7dfefb380661 , holder : PartyC , quantity : 5 + start MoveEvolvableFungibleTokenFlow tokenId : 61ec42bc-4fed-4e6f-aeb7-8e93d1b3e471 , holder : PartyC , quantity : 5 Redeem tokens via PartyC's terminal specifying the issuer - start RedeemHouseFungibleTokenFlow tokenId : 680922c8-a0c8-4440-9d72-7dfefb380661 , issuer : PartyA , quantity : 5 + start RedeemHouseFungibleTokenFlow tokenId : 61ec42bc-4fed-4e6f-aeb7-8e93d1b3e471 , issuer : PartyA , quantity : 5 ### Non Fungible Tokens @@ -63,12 +63,12 @@ Create house on the ledger on PartyA's terminal Issue tokens off the created house from PartyA s terminal to PartyB - start IssueEvolvableFungibleTokenFlow tokenId : 79332247-61d2-4d00-bb1f-d62416cf4920 , quantity : 10 , holder : PartyB + start IssueEvolvableTokenFlow tokenId : 45caf6b2-2342-48bc-9f19-ee6ef0528c1f , recipient : PartyB Move tokens to PartyC from PartyB s terminal - start MoveEvolvableFungibleTokenFlow tokenId : 79332247-61d2-4d00-bb1f-d62416cf4920 , holder : Party C , quantity : 5 + start MoveEvolvableTokenFlow tokenId : 45caf6b2-2342-48bc-9f19-ee6ef0528c1f , recipient : PartyC Redeem tokens via PartyC's terminal - start RedeemHouseFungibleTokenFlow tokenId : 79332247-61d2-4d00-bb1f-d62416cf4920, issuer : PartyA , quantity : 5 + start RedeemHouseToken tokenId : 45caf6b2-2342-48bc-9f19-ee6ef0528c1f, issuer : PartyA diff --git a/real-estate-token/workflows/src/main/java/com/template/flows/RealEstateEvolvableFungibleTokenFlow.java b/real-estate-token/workflows/src/main/java/com/template/flows/RealEstateEvolvableFungibleTokenFlow.java index a6c519d6e..d6bfd3b3e 100644 --- a/real-estate-token/workflows/src/main/java/com/template/flows/RealEstateEvolvableFungibleTokenFlow.java +++ b/real-estate-token/workflows/src/main/java/com/template/flows/RealEstateEvolvableFungibleTokenFlow.java @@ -57,7 +57,7 @@ public SignedTransaction call() throws FlowException { TransactionState transactionState = new TransactionState(evolvableTokenType, notary); //call built in sub flow CreateEvolvableTokens. This can be called via rpc or in unit testing - return (SignedTransaction) subFlow(new CreateEvolvableTokens(transactionState)); + return subFlow(new CreateEvolvableTokens(transactionState)); } } @@ -105,8 +105,7 @@ public SignedTransaction call() throws FlowException { FungibleToken fungibleToken = new FungibleToken(amount, holder, TransactionUtilitiesKt.getAttachmentIdForGenericParam(tokenPointer)); //use built in flow for issuing tokens on ledger - subFlow(new IssueTokens(ImmutableList.of(fungibleToken))); - return null; + return subFlow(new IssueTokens(ImmutableList.of(fungibleToken))); } } @@ -149,8 +148,7 @@ public SignedTransaction call() throws FlowException { PartyAndAmount partyAndAmount = new PartyAndAmount(holder, amount); //use built in flow to move fungible tokens to holder - subFlow(new MoveFungibleTokens(partyAndAmount)); - return null; + return subFlow(new MoveFungibleTokens(partyAndAmount)); } } @@ -192,8 +190,7 @@ public SignedTransaction call() throws FlowException { Amount amount = new Amount(quantity, tokenPointer); //call built in redeem flow to redeem tokens with issuer - subFlow(new RedeemFungibleTokens(amount, issuer)); - return null; + return subFlow(new RedeemFungibleTokens(amount, issuer)); } } } diff --git a/real-estate-token/workflows/src/main/java/com/template/flows/RealEstateEvolvableNonFungibleTokenFlow.java b/real-estate-token/workflows/src/main/java/com/template/flows/RealEstateEvolvableNonFungibleTokenFlow.java index 1aff96438..ae2f5d2ab 100644 --- a/real-estate-token/workflows/src/main/java/com/template/flows/RealEstateEvolvableNonFungibleTokenFlow.java +++ b/real-estate-token/workflows/src/main/java/com/template/flows/RealEstateEvolvableNonFungibleTokenFlow.java @@ -56,7 +56,7 @@ public SignedTransaction call() throws FlowException { TransactionState transactionState = new TransactionState(evolvableTokenType, notary); //call built in sub flow CreateEvolvableTokens. This can be called via rpc or in unit testing - return (SignedTransaction) subFlow(new CreateEvolvableTokens(transactionState)); + return subFlow(new CreateEvolvableTokens(transactionState)); } } @@ -99,7 +99,7 @@ public SignedTransaction call() throws FlowException { NonFungibleToken nonFungibleToken = new NonFungibleToken(issuedTokenType, holder, new UniqueIdentifier(), TransactionUtilitiesKt.getAttachmentIdForGenericParam(tokenPointer)); //call built in flow to issue non fungible tokens - return (SignedTransaction) subFlow(new IssueTokens(ImmutableList.of(nonFungibleToken))); + return subFlow(new IssueTokens(ImmutableList.of(nonFungibleToken))); } } @@ -176,7 +176,7 @@ public SignedTransaction call() throws FlowException { TokenPointer token = evolvableTokenType.toPointer(evolvableTokenType.getClass()); //call built in flow to redeem the tokens - return (SignedTransaction) subFlow(new RedeemNonFungibleTokens(token, issuer)); + return subFlow(new RedeemNonFungibleTokens(token, issuer)); } } } From 2ba38b8768955f923db5014be05f7b1fecbed631 Mon Sep 17 00:00:00 2001 From: snehadamle Date: Thu, 28 Nov 2019 14:20:30 +0530 Subject: [PATCH 18/20] contract-constraint-migration sample commit. --- contract-constraint-migration/LICENCE | 29 +++ contract-constraint-migration/README.md | 219 ++++++++++++++++++ contract-constraint-migration/TRADEMARK | 4 + contract-constraint-migration/build.gradle | 149 ++++++++++++ .../config/dev/log4j2.xml | 59 +++++ .../config/test/log4j2.xml | 20 ++ .../contracts/v1-contracts/build.gradle | 28 +++ .../upgrades/contracts/OldContract.java | 20 ++ .../samples/upgrades/states/OldState.java | 43 ++++ .../upgrades/contracts/ContractTests.java | 13 ++ .../contracts/v2-contracts/build.gradle | 25 ++ .../upgrades/contracts/OldContract.java | 25 ++ .../samples/upgrades/states/OldState.java | 43 ++++ .../upgrades/contracts/ContractTests.java | 13 ++ ...rk-bootstrapper-4.3-20191021.000058-50.jar | 0 .../gradle/wrapper/gradle-wrapper.jar | 0 .../gradle/wrapper/gradle-wrapper.properties | 6 + contract-constraint-migration/gradlew | 0 contract-constraint-migration/gradlew.bat | 84 +++++++ .../include_whitelist.txt | 1 + .../script/upgrade.sh | 32 +++ contract-constraint-migration/settings.gradle | 8 + .../workflows/v1-workflows/build.gradle | 63 +++++ .../samples/upgrades/DriverBasedTest.java | 48 ++++ ...licitMigrateToSignatureConstraintFlow.java | 78 +++++++ ...ateToSignatureConstraintResponderFlow.java | 38 +++ .../upgrades/flows/IssueInitialStateFlow.java | 58 +++++ .../flows/IssueInitialStateFlowResponder.java | 38 +++ .../corda/samples/upgrades/ContractTests.java | 13 ++ .../corda/samples/upgrades/FlowTests.java | 39 ++++ .../corda/samples/upgrades/NodeDriver.java | 39 ++++ 31 files changed, 1235 insertions(+) create mode 100644 contract-constraint-migration/LICENCE create mode 100644 contract-constraint-migration/README.md create mode 100644 contract-constraint-migration/TRADEMARK create mode 100644 contract-constraint-migration/build.gradle create mode 100644 contract-constraint-migration/config/dev/log4j2.xml create mode 100644 contract-constraint-migration/config/test/log4j2.xml create mode 100644 contract-constraint-migration/contracts/v1-contracts/build.gradle create mode 100644 contract-constraint-migration/contracts/v1-contracts/src/main/java/corda/samples/upgrades/contracts/OldContract.java create mode 100644 contract-constraint-migration/contracts/v1-contracts/src/main/java/corda/samples/upgrades/states/OldState.java create mode 100644 contract-constraint-migration/contracts/v1-contracts/src/test/java/corda/samples/upgrades/contracts/ContractTests.java create mode 100644 contract-constraint-migration/contracts/v2-contracts/build.gradle create mode 100644 contract-constraint-migration/contracts/v2-contracts/src/main/java/corda/samples/upgrades/contracts/OldContract.java create mode 100644 contract-constraint-migration/contracts/v2-contracts/src/main/java/corda/samples/upgrades/states/OldState.java create mode 100644 contract-constraint-migration/contracts/v2-contracts/src/test/java/corda/samples/upgrades/contracts/ContractTests.java create mode 100644 contract-constraint-migration/corda-tools-network-bootstrapper-4.3-20191021.000058-50.jar create mode 100644 contract-constraint-migration/gradle/wrapper/gradle-wrapper.jar create mode 100644 contract-constraint-migration/gradle/wrapper/gradle-wrapper.properties create mode 100644 contract-constraint-migration/gradlew create mode 100644 contract-constraint-migration/gradlew.bat create mode 100644 contract-constraint-migration/include_whitelist.txt create mode 100644 contract-constraint-migration/script/upgrade.sh create mode 100644 contract-constraint-migration/settings.gradle create mode 100644 contract-constraint-migration/workflows/v1-workflows/build.gradle create mode 100644 contract-constraint-migration/workflows/v1-workflows/src/integrationTest/java/corda/samples/upgrades/DriverBasedTest.java create mode 100644 contract-constraint-migration/workflows/v1-workflows/src/main/java/corda/samples/upgrades/flows/ImplicitMigrateToSignatureConstraintFlow.java create mode 100644 contract-constraint-migration/workflows/v1-workflows/src/main/java/corda/samples/upgrades/flows/ImplicitMigrateToSignatureConstraintResponderFlow.java create mode 100644 contract-constraint-migration/workflows/v1-workflows/src/main/java/corda/samples/upgrades/flows/IssueInitialStateFlow.java create mode 100644 contract-constraint-migration/workflows/v1-workflows/src/main/java/corda/samples/upgrades/flows/IssueInitialStateFlowResponder.java create mode 100644 contract-constraint-migration/workflows/v1-workflows/src/test/java/corda/samples/upgrades/ContractTests.java create mode 100644 contract-constraint-migration/workflows/v1-workflows/src/test/java/corda/samples/upgrades/FlowTests.java create mode 100644 contract-constraint-migration/workflows/v1-workflows/src/test/java/corda/samples/upgrades/NodeDriver.java diff --git a/contract-constraint-migration/LICENCE b/contract-constraint-migration/LICENCE new file mode 100644 index 000000000..d2ac29f41 --- /dev/null +++ b/contract-constraint-migration/LICENCE @@ -0,0 +1,29 @@ +@PostMapping(value = [ "create-invoice" ], produces = [ TEXT_PLAIN_VALUE ], headers = [ "Content-Type=application/x-www-form-urlencoded" ]) + fun createInvoice(request: HttpServletRequest): ResponseEntity { + val startDate = "startDate" + val endDate = "endDate" + val payDate = "payDate" + val empName = "empName" + val empID = 23 + val gross = 100.0 + val ot = 5.0 + val pt = 3.0 + val tips = 2.0 + val classcode = 8564 + val hoursWorked = request.getParameter("hoursWorked").toInt() + val date = LocalDate.parse(request.getParameter("date").substringBefore("00").trim(), DateTimeFormatter.ofPattern("E MMMM d yyyy")) + val partyName = request.getParameter("megacorp") ?: return ResponseEntity.badRequest().body("Query parameter 'MegaCorp' must not be null.\n") + if (hoursWorked <= 0 ) { + return ResponseEntity.badRequest().body("Query parameter 'hoursWorked' must be non-negative.\n") + } + val partyX500Name = CordaX500Name.parse(partyName) + val otherParty = proxy.wellKnownPartyFromX500Name(partyX500Name) ?: return ResponseEntity.badRequest().body("Party named $partyName cannot be found.\n") + return try { + val signedTx = proxy.startTrackedFlow(IssueInvoiceFlow::Initiator,startDate,endDate,payDate,empName,empID, gross, ot, pt,tips,classcode,hoursWorked, date, otherParty).returnValue.getOrThrow() + + ResponseEntity.status(HttpStatus.CREATED).body("Transaction id ${signedTx.id} committed to ledger.\n") + } catch (ex: Throwable) { + logger.error(ex.message, ex) + ResponseEntity.badRequest().body(ex.message!!) + } + } \ No newline at end of file diff --git a/contract-constraint-migration/README.md b/contract-constraint-migration/README.md new file mode 100644 index 000000000..456db2560 --- /dev/null +++ b/contract-constraint-migration/README.md @@ -0,0 +1,219 @@ +

+ Corda +

+ +# Contract Constraint Migration + +This sample shows you how to migrate your contract constraints. + +**Migrate from HashConstraint to SignatureConstraint** + +1. Run the deployNodes task. I have uncommented the contract jar from deployNodes task. By default if you have contract jar in deployNodes task in nodeDefaults property, the task adds +the jar's hash to whitelist zone param in network param. So when you issue a state, the state will be issued using whitelist zone constraint. + + ./gradew clean deployNodes + +2. We want to issue using hash constraint. We will explicitly add the v1-contract jar from contracts by running the below script. + + ./upgrade.sh --node=PartyA , --contract=1 + ./upgrade.sh --node=PartyB , --contract=1 + +3. Start the nodes + + cd build/nodes + ./runnodes + +4. Go to PartyA terminal and issue some hash constraint states + + start DefaultHashFlow counterParty : PartyB , amount : 10 + start DefaultHashFlow counterParty : PartyB , amount : 11 + start DefaultHashFlow counterParty : PartyB , amount : 12 + start DefaultHashFlow counterParty : PartyB , amount : 13 + +5. Run the vaultQuery to confirm the states have been created using HashConstraint. + + run vaultQuery contractStateType : corda.samples.upgrades.states.OldState + + - state: + data: ! + issuer: "O=PartyA, L=Delhi, C=IN" + owner: "O=PartyB, L=Delhi, C=IN" + amount: 10 + contract: "corda.samples.upgrades.contracts.OldContract" + notary: "O=Notary, L=Delhi, C=IN" + encumbrance: null + **constraint: !** + attachmentId: "B66F8B2E768D5809F281160437C7C240E92E340E9128441D89E180D1A86F127E" + ref: + txhash: "A90143DB9EA3A7CCF3DA2E55E0E746F30A4406C82777051D8C069348008D459E" + index: 0 + - state: + data: ! + issuer: "O=PartyA, L=Delhi, C=IN" + owner: "O=PartyB, L=Delhi, C=IN" + amount: 11 + contract: "corda.samples.upgrades.contracts.OldContract" + notary: "O=Notary, L=Delhi, C=IN" + encumbrance: null + **constraint: !** + attachmentId: "B66F8B2E768D5809F281160437C7C240E92E340E9128441D89E180D1A86F127E" + ref: + txhash: "9EBED42058CB38D655561884E86EBFDC713D644BD4C103F0C61A85B2918F40E2" + index: 0 + ............... +6. Stop the node +7. Replace the v1 contract with v2 contract. V2 contract is built using Signature Constraints. + + ./upgrade.sh --node=PartyA , --contract=2 + ./upgrade.sh --node=PartyB , --contract=2 + +8. Now disable the HashConstraint check by setting Java system property while starting each node. This disables the platform check and the new output states can be migrated to Signature Constraints. + + cd build/nodes/PartyA + java -jar -Dnet.corda.node.disableHashConstraints="true" corda.jar + + cd build/nodes/PartyB + java -jar -Dnet.corda.node.disableHashConstraints="true" corda.jar + +9. Run the flow which consumes a HashConstraint state and creates a SignatureConstraint state. + + start MigrateToSignatureFromWhitelistFlow counterParty : PartyB , amount : 100 + +10. Run the vaultQuery to confirm the new states are using SignatureConstraints. The state issued with amount 10 is consumed and new state with amount 100 is issued +with SignatureConstraint. + + run vaultQuery contractStateType : corda.samples.upgrades.states.OldState + + - state: + data: ! + issuer: "O=PartyA, L=Delhi, C=IN" + owner: "O=PartyB, L=Delhi, C=IN" + amount: 11 + contract: "corda.samples.upgrades.contracts.OldContract" + notary: "O=Notary, L=Delhi, C=IN" + encumbrance: null + **constraint: !** + attachmentId: "B66F8B2E768D5809F281160437C7C240E92E340E9128441D89E180D1A86F127E" + ref: + txhash: "A90143DB9EA3A7CCF3DA2E55E0E746F30A4406C82777051D8C069348008D459E" + index: 0 + index: 0 + - state: + data: ! + issuer: "O=PartyA, L=Delhi, C=IN" + owner: "O=PartyB, L=Delhi, C=IN" + amount: 100 + contract: "corda.samples.upgrades.contracts.OldContract" + notary: "O=Notary, L=Delhi, C=IN" + encumbrance: null + **constraint: !** + key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6" + ref: + txhash: "0CEBAEA3D5B02F827FAC9445702FE5D34E78F72D6277D80195D70F92DD61A3AF" + index: 0 + + +**Migrate from WhiteListZoneConstraints to SignatureConstraint** + +1. Run the deployNodes task by uncommenting v1-contract in node_defaults property. This will whitelist v1 contract and when we issue a state , it will default to whitelist zone list constraint. + + ./gradlew deployNodes + +2. Check the network param file output when you run above deployNodes command. B66F8B2E768D5809F281160437C7C240E92E340E9128441D89E180D1A86F127E is the hash of v1-contract added to the whitelist param. + + Loading existing network parameters... NetworkParameters { + minimumPlatformVersion=5 + notaries=[NotaryInfo(identity=O=Notary, L=Delhi, C=IN, validating=false)] + maxMessageSize=10485760 + maxTransactionSize=524288000 + whitelistedContractImplementations { + **corda.samples.upgrades.contracts.OldContract=[B66F8B2E768D5809F281160437C7C240E92E340E9128441D89E180D1A86F127E]** + } + eventHorizon=PT720H + packageOwnership { + + } + modifiedTime=2019-11-07T13:44:36.945Z + epoch=1 + } + +3. Start the nodes + + cd build/nodes + ./runnodes + +4. Go to PartyA terminal and issue some states, this will default to WhiteListZoneConstraint. + + start DefaultHashFlow counterParty : PartyB , amount : 10 + start DefaultHashFlow counterParty : PartyB , amount : 11 + start DefaultHashFlow counterParty : PartyB , amount : 12 + start DefaultHashFlow counterParty : PartyB , amount : 13 + +5. Run the vaultQuery to confirm the states have been created using WhiteListZoneConstraint. + + run vaultQuery contractStateType : corda.samples.upgrades.states.OldState + states: + - state: + data: ! + issuer: "O=PartyA, L=Delhi, C=IN" + owner: "O=PartyB, L=Delhi, C=IN" + amount: 10 + contract: "corda.samples.upgrades.contracts.OldContract" + notary: "O=Notary, L=Delhi, C=IN" + encumbrance: null + constraint: ! {} + ref: + txhash: "01C4B2CFB31B6DE14AF20A0BABAC136E8B0E2A0DB99DC82F3AD27B6EC56FCD7F" + index: 0 + - state: + data: ! + issuer: "O=PartyA, L=Delhi, C=IN" + owner: "O=PartyB, L=Delhi, C=IN" + amount: 11 + contract: "corda.samples.upgrades.contracts.OldContract" + notary: "O=Notary, L=Delhi, C=IN" + encumbrance: null + constraint: ! {} + ref: + txhash: "EE676193242B5BA7245CDC6CCD8F84513B49D2821F3ADC651503B85B71DBAF06" + index: 0 + ................ + +6. Stop the node + +7. Replace the v1 contract with v2 contract. V2 contract is built using Signature Constraints. + + ./upgrade.sh --node=PartyA , --contract=2 + ./upgrade.sh --node=PartyB , --contract=2 + +8. Add the new jar to whitelistedContractImplementations to network param file. To do this add the network-bootstrapper, v3-contract.jar to nodes directory. Also create a file name +include_whitelist.txt and add the contract class full name to this file. + + vi include_whitelist.txt + add corda.samples.upgrades.states.OldState to include_whitelist.txt + + cp network-bootstrapper.jar /nodes + cp include_whitelist.txt /nodes + cp v3-contract.jar /nodes + +9. Running below command will whitelist the new contract. + + java -jar network-bootstrapper.jar --dir . + +10. This adds the new hash of new jar to whitelistedContractImplementations to network-param file + + Updated NetworkParameters { + minimumPlatformVersion=5 + notaries=[NotaryInfo(identity=O=Notary, L=Delhi, C=IN, validating=false)] + maxMessageSize=10485760 + maxTransactionSize=524288000 + whitelistedContractImplementations { + **corda.samples.upgrades.contracts.OldContract=[B66F8B2E768D5809F281160437C7C240E92E340E9128441D89E180D1A86F127E, 26973C032E20F57E62CCC7A5F0EA883E21619D433B706A743D5AD08806B7924E]** + } + eventHorizon=PT720H + packageOwnership { + + } + modifiedTime=2019-11-07T15:04:38.840Z + epoch=2 + } diff --git a/contract-constraint-migration/TRADEMARK b/contract-constraint-migration/TRADEMARK new file mode 100644 index 000000000..d2e056b5f --- /dev/null +++ b/contract-constraint-migration/TRADEMARK @@ -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 at +https://www.r3.com/trademark-policy/. \ No newline at end of file diff --git a/contract-constraint-migration/build.gradle b/contract-constraint-migration/build.gradle new file mode 100644 index 000000000..4066b4f39 --- /dev/null +++ b/contract-constraint-migration/build.gradle @@ -0,0 +1,149 @@ +buildscript { + + Properties constants = new Properties() + file("$projectDir/../constants.properties").withInputStream { constants.load(it) } + + ext { + corda_release_group = constants.getProperty("cordaReleaseGroup") + corda_release_version = constants.getProperty("cordaVersion") + corda_gradle_plugins_version = constants.getProperty("gradlePluginsVersion") + kotlin_version = constants.getProperty("kotlinVersion") + junit_version = constants.getProperty("junitVersion") + quasar_version = constants.getProperty("quasarVersion") + log4j_version = constants.getProperty("log4jVersion") + slf4j_version = constants.getProperty("slf4jVersion") + corda_platform_version = constants.getProperty("platformVersion").toInteger() + //springboot + spring_boot_version = '2.0.2.RELEASE' + spring_boot_gradle_plugin_version = '2.0.2.RELEASE' + + v1_contract = ":contracts:v1-contracts" + v2_contract = ":contracts:v2-contracts" + + v1_workflow = ":workflows:v1-workflows" + } + + repositories { + mavenLocal() + mavenCentral() + jcenter() + maven { + url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases' + } + } + + dependencies { + classpath "net.corda.plugins:cordapp:$corda_gradle_plugins_version" + classpath "net.corda.plugins:cordformation:$corda_gradle_plugins_version" + classpath "net.corda.plugins:quasar-utils:$corda_gradle_plugins_version" + classpath "org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_gradle_plugin_version" + } +} + +allprojects { + apply plugin: 'java' + + repositories { + mavenLocal() + jcenter() + mavenCentral() + maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } + maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-dev' } + maven { + url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases' + } + maven { url 'https://jitpack.io' } + } + + tasks.withType(JavaCompile) { + options.compilerArgs << "-parameters" // Required by Corda's serialisation framework. + } + + jar { + // This makes the JAR's SHA-256 hash repeatable. + preserveFileTimestamps = false + reproducibleFileOrder = true + } +} + + +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } +} + +dependencies { + testCompile "junit:junit:$junit_version" + + // Corda dependencies. + cordaCompile "$corda_release_group:corda-core:$corda_release_version" + cordaCompile "$corda_release_group:corda-node-api:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + + // CorDapp dependencies. + cordapp project(v1_contract) + cordapp project(v2_contract) + + cordapp project(v1_workflow) + + cordaCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" + cordaCompile "org.apache.logging.log4j:log4j-web:${log4j_version}" + cordaCompile "org.slf4j:jul-to-slf4j:$slf4j_version" +} + +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + nodeDefaults { + projectCordapp { + deploy = false + } + cordapp project(v1_contract) + cordapp project(v1_workflow) + + } + node { + name "O=Notary,L=Delhi,C=IN" + notary = [validating : false] + p2pPort 10002 + rpcSettings { + address("localhost:10003") + adminAddress("localhost:10043") + } + extraConfig = ['h2Settings.address' : 'localhost:20040'] + cordapps.clear() + } + node { + name "O=PartyA,L=Delhi,C=IN" + p2pPort 10008 + rpcSettings { + address("localhost:10009") + adminAddress("localhost:10049") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + extraConfig = ['h2Settings.address' : 'localhost:20041'] + } + node { + name "O=PartyB,L=Delhi,C=IN" + p2pPort 10011 + rpcSettings { + address("localhost:10012") + adminAddress("localhost:10052") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + extraConfig = ['h2Settings.address' : 'localhost:20043'] + } + +} + +task installQuasar(type: Copy) { + destinationDir rootProject.file("lib") + from(configurations.quasar) { + rename 'quasar-core(.*).jar', 'quasar.jar' + } +} \ No newline at end of file diff --git a/contract-constraint-migration/config/dev/log4j2.xml b/contract-constraint-migration/config/dev/log4j2.xml new file mode 100644 index 000000000..34ba4d45a --- /dev/null +++ b/contract-constraint-migration/config/dev/log4j2.xml @@ -0,0 +1,59 @@ + + + + + logs + node-${hostName} + ${log-path}/archive + + + + + + + + + %highlight{%level{length=1} %d{HH:mm:ss} %T %c{1}.%M - %msg%n}{INFO=white,WARN=red,FATAL=bright red blink} + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contract-constraint-migration/config/test/log4j2.xml b/contract-constraint-migration/config/test/log4j2.xml new file mode 100644 index 000000000..cd9926ca8 --- /dev/null +++ b/contract-constraint-migration/config/test/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n + > + + + + + + + + + + + + diff --git a/contract-constraint-migration/contracts/v1-contracts/build.gradle b/contract-constraint-migration/contracts/v1-contracts/build.gradle new file mode 100644 index 000000000..62202c90f --- /dev/null +++ b/contract-constraint-migration/contracts/v1-contracts/build.gradle @@ -0,0 +1,28 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' + +cordapp { + targetPlatformVersion corda_platform_version.toInteger() + minimumPlatformVersion corda_platform_version.toInteger() + contract { + name "CorDapp Upgrades" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } + signing{ + enabled false + } +} + +jar{ + baseName = "cordapp-upgrades" + archiveName = "v1-contracts.jar" +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_release_group:corda-core:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" +} diff --git a/contract-constraint-migration/contracts/v1-contracts/src/main/java/corda/samples/upgrades/contracts/OldContract.java b/contract-constraint-migration/contracts/v1-contracts/src/main/java/corda/samples/upgrades/contracts/OldContract.java new file mode 100644 index 000000000..af3124d6c --- /dev/null +++ b/contract-constraint-migration/contracts/v1-contracts/src/main/java/corda/samples/upgrades/contracts/OldContract.java @@ -0,0 +1,20 @@ +package corda.samples.upgrades.contracts; + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import org.jetbrains.annotations.NotNull; + +public class OldContract implements Contract { + + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + + } + + public interface Commands extends CommandData { + class Issue implements OldContract.Commands {} + + } + +} diff --git a/contract-constraint-migration/contracts/v1-contracts/src/main/java/corda/samples/upgrades/states/OldState.java b/contract-constraint-migration/contracts/v1-contracts/src/main/java/corda/samples/upgrades/states/OldState.java new file mode 100644 index 000000000..84a1d708c --- /dev/null +++ b/contract-constraint-migration/contracts/v1-contracts/src/main/java/corda/samples/upgrades/states/OldState.java @@ -0,0 +1,43 @@ +package corda.samples.upgrades.states; + +import com.google.common.collect.ImmutableList; +import corda.samples.upgrades.contracts.OldContract; +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.ContractState; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +@BelongsToContract(OldContract.class) + public class OldState implements ContractState { + + private Party issuer; + private Party owner; + private int amount; + + public OldState(Party issuer, Party owner, int amount) { + this.issuer = issuer; + this.owner = owner; + this.amount = amount; + } + + public Party getIssuer() { + return issuer; + } + + public Party getOwner() { + return owner; + } + + public int getAmount() { + return amount; + } + + @NotNull + @Override + public List getParticipants() { + return ImmutableList.of(issuer, owner); + } +} diff --git a/contract-constraint-migration/contracts/v1-contracts/src/test/java/corda/samples/upgrades/contracts/ContractTests.java b/contract-constraint-migration/contracts/v1-contracts/src/test/java/corda/samples/upgrades/contracts/ContractTests.java new file mode 100644 index 000000000..6e78c0dbf --- /dev/null +++ b/contract-constraint-migration/contracts/v1-contracts/src/test/java/corda/samples/upgrades/contracts/ContractTests.java @@ -0,0 +1,13 @@ +package corda.samples.upgrades.contracts; + +import net.corda.testing.node.MockServices; +import org.junit.Test; + +public class ContractTests { + private final MockServices ledgerServices = new MockServices(); + + @Test + public void dummyTest() { + + } +} \ No newline at end of file diff --git a/contract-constraint-migration/contracts/v2-contracts/build.gradle b/contract-constraint-migration/contracts/v2-contracts/build.gradle new file mode 100644 index 000000000..33bbab26a --- /dev/null +++ b/contract-constraint-migration/contracts/v2-contracts/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' + +cordapp { + targetPlatformVersion corda_platform_version.toInteger() + minimumPlatformVersion corda_platform_version.toInteger() + contract { + name "CorDapp Upgrades" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +jar{ + baseName = "cordapp-upgrades" + archiveName = "v2-contracts.jar" +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_release_group:corda-core:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" +} \ No newline at end of file diff --git a/contract-constraint-migration/contracts/v2-contracts/src/main/java/corda/samples/upgrades/contracts/OldContract.java b/contract-constraint-migration/contracts/v2-contracts/src/main/java/corda/samples/upgrades/contracts/OldContract.java new file mode 100644 index 000000000..1e3723a9d --- /dev/null +++ b/contract-constraint-migration/contracts/v2-contracts/src/main/java/corda/samples/upgrades/contracts/OldContract.java @@ -0,0 +1,25 @@ +package corda.samples.upgrades.contracts; + +import corda.samples.upgrades.states.OldState; +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import org.jetbrains.annotations.NotNull; + +public class OldContract implements Contract { + + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + + OldState oldState = (OldState) tx.getOutputStates().get(0); + + if(oldState.getAmount() <20) throw new IllegalArgumentException("Amount shd be > 20"); + + } + + public interface Commands extends CommandData { + class Issue implements OldContract.Commands {} + + } + +} diff --git a/contract-constraint-migration/contracts/v2-contracts/src/main/java/corda/samples/upgrades/states/OldState.java b/contract-constraint-migration/contracts/v2-contracts/src/main/java/corda/samples/upgrades/states/OldState.java new file mode 100644 index 000000000..af0d94702 --- /dev/null +++ b/contract-constraint-migration/contracts/v2-contracts/src/main/java/corda/samples/upgrades/states/OldState.java @@ -0,0 +1,43 @@ + package corda.samples.upgrades.states; + +import com.google.common.collect.ImmutableList; +import corda.samples.upgrades.contracts.OldContract; +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.ContractState; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + + @BelongsToContract(OldContract.class) + public class OldState implements ContractState { + + private Party issuer; + private Party owner; + private int amount; + + public OldState(Party issuer, Party owner, int amount) { + this.issuer = issuer; + this.owner = owner; + this.amount = amount; + } + + public Party getIssuer() { + return issuer; + } + + public Party getOwner() { + return owner; + } + + public int getAmount() { + return amount; + } + + @NotNull + @Override + public List getParticipants() { + return ImmutableList.of(issuer, owner); + } + } diff --git a/contract-constraint-migration/contracts/v2-contracts/src/test/java/corda/samples/upgrades/contracts/ContractTests.java b/contract-constraint-migration/contracts/v2-contracts/src/test/java/corda/samples/upgrades/contracts/ContractTests.java new file mode 100644 index 000000000..6e78c0dbf --- /dev/null +++ b/contract-constraint-migration/contracts/v2-contracts/src/test/java/corda/samples/upgrades/contracts/ContractTests.java @@ -0,0 +1,13 @@ +package corda.samples.upgrades.contracts; + +import net.corda.testing.node.MockServices; +import org.junit.Test; + +public class ContractTests { + private final MockServices ledgerServices = new MockServices(); + + @Test + public void dummyTest() { + + } +} \ No newline at end of file diff --git a/contract-constraint-migration/corda-tools-network-bootstrapper-4.3-20191021.000058-50.jar b/contract-constraint-migration/corda-tools-network-bootstrapper-4.3-20191021.000058-50.jar new file mode 100644 index 000000000..e69de29bb diff --git a/contract-constraint-migration/gradle/wrapper/gradle-wrapper.jar b/contract-constraint-migration/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..e69de29bb diff --git a/contract-constraint-migration/gradle/wrapper/gradle-wrapper.properties b/contract-constraint-migration/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..aef7cb78c --- /dev/null +++ b/contract-constraint-migration/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Aug 25 12:50:39 BST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip diff --git a/contract-constraint-migration/gradlew b/contract-constraint-migration/gradlew new file mode 100644 index 000000000..e69de29bb diff --git a/contract-constraint-migration/gradlew.bat b/contract-constraint-migration/gradlew.bat new file mode 100644 index 000000000..f9553162f --- /dev/null +++ b/contract-constraint-migration/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/contract-constraint-migration/include_whitelist.txt b/contract-constraint-migration/include_whitelist.txt new file mode 100644 index 000000000..5929ab05b --- /dev/null +++ b/contract-constraint-migration/include_whitelist.txt @@ -0,0 +1 @@ +corda.samples.upgrades.contracts.OldContract \ No newline at end of file diff --git a/contract-constraint-migration/script/upgrade.sh b/contract-constraint-migration/script/upgrade.sh new file mode 100644 index 000000000..505ec78bb --- /dev/null +++ b/contract-constraint-migration/script/upgrade.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +for i in "$@" +do + case $i in + -n=*|--node=*) + NODES="${i#*=}" + ;; + -c=*|--contract*) + CONTRACT="${i#*=}" + ;; + -w=*|--workflow*) + FLOW="${i#*=}" + ;; + esac +done + + +NODE=(${NODES//,/ }) +for i in "${!NODE[@]}" +do + if [[ "${CONTRACT}" != "" ]]; then + rm ../build/nodes/${NODE[i]}/cordapps/*-contracts.jar + cp ../contracts/v${CONTRACT}-contracts/build/libs/v${CONTRACT}-contracts.jar ../build/nodes/${NODE[i]}/cordapps/ + fi + + if [[ "${FLOW}" != "" ]]; then + rm ../build/nodes/${NODE[i]}/cordapps/*-workflows.jar + cp ../workflows/v${FLOW}-workflows/build/libs/v${FLOW}-workflows.jar ../build/nodes/${NODE[i]}/cordapps/ + fi +done + diff --git a/contract-constraint-migration/settings.gradle b/contract-constraint-migration/settings.gradle new file mode 100644 index 000000000..cad99c65c --- /dev/null +++ b/contract-constraint-migration/settings.gradle @@ -0,0 +1,8 @@ +rootProject.name = 'contract-constraint-migration' + +include 'contracts:v1-contracts' +include 'contracts:v2-contracts' + +include 'workflows:v1-workflows' +include 'workflows:v2-workflows' + diff --git a/contract-constraint-migration/workflows/v1-workflows/build.gradle b/contract-constraint-migration/workflows/v1-workflows/build.gradle new file mode 100644 index 000000000..43038528a --- /dev/null +++ b/contract-constraint-migration/workflows/v1-workflows/build.gradle @@ -0,0 +1,63 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version.toInteger() + minimumPlatformVersion corda_platform_version.toInteger() + workflow { + name "Contract Upgrade Flows" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +jar{ + baseName = "cordapp-upgrades" + archiveName = "v1-workflows.jar" +} + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } + test { + resources { + srcDir rootProject.file("config/test") + } + } + integrationTest { + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integrationTest/java') + } + } +} + +configurations { + integrationTestCompile.extendsFrom testCompile + integrationTestRuntime.extendsFrom testRuntime +} + +dependencies { + testCompile "junit:junit:$junit_version" + + // Corda dependencies. + cordaCompile "$corda_release_group:corda-core:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // CorDapp dependencies. + cordapp project(v1_contract) + cordapp project(v2_contract) +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} \ No newline at end of file diff --git a/contract-constraint-migration/workflows/v1-workflows/src/integrationTest/java/corda/samples/upgrades/DriverBasedTest.java b/contract-constraint-migration/workflows/v1-workflows/src/integrationTest/java/corda/samples/upgrades/DriverBasedTest.java new file mode 100644 index 000000000..137ee4c51 --- /dev/null +++ b/contract-constraint-migration/workflows/v1-workflows/src/integrationTest/java/corda/samples/upgrades/DriverBasedTest.java @@ -0,0 +1,48 @@ +package corda.samples.upgrades; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.driver.DriverParameters; +import net.corda.testing.driver.NodeHandle; +import net.corda.testing.driver.NodeParameters; +import org.junit.Test; + +import java.util.List; + +import static net.corda.testing.driver.Driver.driver; +import static org.junit.Assert.assertEquals; + +public class DriverBasedTest { + private final TestIdentity bankA = new TestIdentity(new CordaX500Name("BankA", "", "GB")); + private final TestIdentity bankB = new TestIdentity(new CordaX500Name("BankB", "", "US")); + + @Test + public void nodeTest() { + driver(new DriverParameters().withIsDebug(true).withStartNodesInProcess(true), dsl -> { + // Start a pair of nodes and wait for them both to be ready. + List> handleFutures = ImmutableList.of( + dsl.startNode(new NodeParameters().withProvidedName(bankA.getName())), + dsl.startNode(new NodeParameters().withProvidedName(bankB.getName())) + ); + + try { + NodeHandle partyAHandle = handleFutures.get(0).get(); + NodeHandle partyBHandle = handleFutures.get(1).get(); + + // From each node, make an RPC call to retrieve another node's name from the network map, to verify that the + // nodes have started and can communicate. + + // This is a very basic test: in practice tests would be starting flows, and verifying the states in the vault + // and other important metrics to ensure that your CorDapp is working as intended. + assertEquals(partyAHandle.getRpc().wellKnownPartyFromX500Name(bankB.getName()).getName(), bankB.getName()); + assertEquals(partyBHandle.getRpc().wellKnownPartyFromX500Name(bankA.getName()).getName(), bankA.getName()); + } catch (Exception e) { + throw new RuntimeException("Caught exception during test: ", e); + } + + return null; + }); + } +} \ No newline at end of file diff --git a/contract-constraint-migration/workflows/v1-workflows/src/main/java/corda/samples/upgrades/flows/ImplicitMigrateToSignatureConstraintFlow.java b/contract-constraint-migration/workflows/v1-workflows/src/main/java/corda/samples/upgrades/flows/ImplicitMigrateToSignatureConstraintFlow.java new file mode 100644 index 000000000..ca1938395 --- /dev/null +++ b/contract-constraint-migration/workflows/v1-workflows/src/main/java/corda/samples/upgrades/flows/ImplicitMigrateToSignatureConstraintFlow.java @@ -0,0 +1,78 @@ +package corda.samples.upgrades.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.google.common.collect.ImmutableList; +import corda.samples.upgrades.contracts.OldContract; +import corda.samples.upgrades.states.OldState; +import net.corda.core.contracts.SignatureAttachmentConstraint; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.crypto.SecureHash; +import net.corda.core.flows.*; +import net.corda.core.identity.Party; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; + +import java.security.PublicKey; +import java.util.List; + +@InitiatingFlow +@StartableByRPC +public class ImplicitMigrateToSignatureConstraintFlow extends FlowLogic { + + private Party counterParty; + private int amount; + + public ImplicitMigrateToSignatureConstraintFlow(Party counterParty, int amount) { + this.counterParty = counterParty; + this.amount = amount; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + + Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + + StateAndRef input = getServiceHub().getVaultService() + .queryBy(OldState.class, + new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED)).getStates().get(0); + + TransactionBuilder transactionBuilder = new TransactionBuilder(notary); + + OldState output = new OldState(getOurIdentity() , counterParty , amount); + + transactionBuilder.addInputState(input); + + /* you can explicitly specify signature constraint + SecureHash attachment = this.getServiceHub().getCordappProvider().getContractAttachmentID("corda.samples.upgrades.contracts.OldContract"); + + List signers = getServiceHub().getAttachments().openAttachment(attachment).getSignerKeys(); + + // Create the key that will have to pass for all future versions. + PublicKey ownersKey = signers.get(0); + + transactionBuilder.addOutputState(output , "corda.samples.upgrades.contracts.OldContract" , new SignatureAttachmentConstraint(ownersKey)); + + transactionBuilder.addOutputState(output, new SignatureAttachmentConstraint(ownersKey)); + */ + + transactionBuilder.addOutputState(output); + + transactionBuilder.addCommand(new OldContract.Commands.Issue() , + ImmutableList.of(getOurIdentity().getOwningKey() , counterParty.getOwningKey())); + + transactionBuilder.verify(getServiceHub()); + + SignedTransaction partiallySignedTransaction = getServiceHub().signInitialTransaction(transactionBuilder); + + FlowSession flowSession = initiateFlow(counterParty); + + SignedTransaction signedTransaction = subFlow(new CollectSignaturesFlow(partiallySignedTransaction, ImmutableList.of(flowSession))); + + return subFlow(new FinalityFlow(signedTransaction, ImmutableList.of(flowSession))); + + } +} diff --git a/contract-constraint-migration/workflows/v1-workflows/src/main/java/corda/samples/upgrades/flows/ImplicitMigrateToSignatureConstraintResponderFlow.java b/contract-constraint-migration/workflows/v1-workflows/src/main/java/corda/samples/upgrades/flows/ImplicitMigrateToSignatureConstraintResponderFlow.java new file mode 100644 index 000000000..4786b75d5 --- /dev/null +++ b/contract-constraint-migration/workflows/v1-workflows/src/main/java/corda/samples/upgrades/flows/ImplicitMigrateToSignatureConstraintResponderFlow.java @@ -0,0 +1,38 @@ +package corda.samples.upgrades.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.crypto.SecureHash; +import net.corda.core.flows.*; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.utilities.ProgressTracker; + +@InitiatedBy(ImplicitMigrateToSignatureConstraintFlow.class) +public class ImplicitMigrateToSignatureConstraintResponderFlow extends FlowLogic { + + private FlowSession counterPartySession; + + public ImplicitMigrateToSignatureConstraintResponderFlow(FlowSession counterPartySession) { + this.counterPartySession = counterPartySession; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + class SignTxFlow extends SignTransactionFlow { + private SignTxFlow(FlowSession otherPartyFlow, ProgressTracker progressTracker) { + super(otherPartyFlow, progressTracker); + } + + @Override + protected void checkTransaction(SignedTransaction stx) { + + } + } + + final SignTxFlow signTxFlow = new SignTxFlow(counterPartySession, SignTransactionFlow.Companion.tracker()); + final SecureHash txId = subFlow(signTxFlow).getId(); + + return subFlow(new ReceiveFinalityFlow(counterPartySession, txId)); + } +} diff --git a/contract-constraint-migration/workflows/v1-workflows/src/main/java/corda/samples/upgrades/flows/IssueInitialStateFlow.java b/contract-constraint-migration/workflows/v1-workflows/src/main/java/corda/samples/upgrades/flows/IssueInitialStateFlow.java new file mode 100644 index 000000000..4c7013ca7 --- /dev/null +++ b/contract-constraint-migration/workflows/v1-workflows/src/main/java/corda/samples/upgrades/flows/IssueInitialStateFlow.java @@ -0,0 +1,58 @@ +package corda.samples.upgrades.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.google.common.collect.ImmutableList; +import corda.samples.upgrades.contracts.OldContract; +import corda.samples.upgrades.states.OldState; +import net.corda.core.contracts.HashAttachmentConstraint; +import net.corda.core.crypto.SecureHash; +import net.corda.core.flows.*; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; + +@InitiatingFlow +@StartableByRPC +public class IssueInitialStateFlow extends FlowLogic { + + private Party counterParty; + private int amount; + + public IssueInitialStateFlow(Party counterParty, int amount) { + this.counterParty = counterParty; + this.amount = amount; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + + Party issuer = getOurIdentity(); + + OldState outputState = new OldState(issuer, counterParty, amount); + + TransactionBuilder transactionBuilder = new TransactionBuilder(notary); + + /* Explicit way of specifying constraint to override the default behavior. + SecureHash secureHash = SecureHash.parse("56c51e6d4f3553f70e39af00b469e8fdabd7b17876107a4177e8a00b1a33ef86"); + transactionBuilder.addOutputState(outputState, new HashAttachmentConstraint(secureHash)); + */ + + transactionBuilder.addOutputState(outputState); + + transactionBuilder.addCommand(new OldContract.Commands.Issue() , + ImmutableList.of(issuer.getOwningKey() , counterParty.getOwningKey())); + + transactionBuilder.verify(getServiceHub()); + + SignedTransaction partialSignedTransaction = getServiceHub().signInitialTransaction(transactionBuilder); + + FlowSession flowSession = initiateFlow(counterParty); + + SignedTransaction signedTransaction = subFlow(new CollectSignaturesFlow(partialSignedTransaction, ImmutableList.of(flowSession))); + + return subFlow(new FinalityFlow(signedTransaction, ImmutableList.of(flowSession))); + } +} diff --git a/contract-constraint-migration/workflows/v1-workflows/src/main/java/corda/samples/upgrades/flows/IssueInitialStateFlowResponder.java b/contract-constraint-migration/workflows/v1-workflows/src/main/java/corda/samples/upgrades/flows/IssueInitialStateFlowResponder.java new file mode 100644 index 000000000..f85de3259 --- /dev/null +++ b/contract-constraint-migration/workflows/v1-workflows/src/main/java/corda/samples/upgrades/flows/IssueInitialStateFlowResponder.java @@ -0,0 +1,38 @@ +package corda.samples.upgrades.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.crypto.SecureHash; +import net.corda.core.flows.*; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.utilities.ProgressTracker; + +@InitiatedBy(IssueInitialStateFlow.class) +public class IssueInitialStateFlowResponder extends FlowLogic { + + private FlowSession counterPartySession; + + public IssueInitialStateFlowResponder(FlowSession counterPartySession) { + this.counterPartySession = counterPartySession; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + class SignTxFlow extends SignTransactionFlow { + private SignTxFlow(FlowSession otherPartyFlow, ProgressTracker progressTracker) { + super(otherPartyFlow, progressTracker); + } + + @Override + protected void checkTransaction(SignedTransaction stx) { + + } + } + + final SignTxFlow signTxFlow = new SignTxFlow(counterPartySession, SignTransactionFlow.Companion.tracker()); + final SecureHash txId = subFlow(signTxFlow).getId(); + + return subFlow(new ReceiveFinalityFlow(counterPartySession, txId)); + } +} diff --git a/contract-constraint-migration/workflows/v1-workflows/src/test/java/corda/samples/upgrades/ContractTests.java b/contract-constraint-migration/workflows/v1-workflows/src/test/java/corda/samples/upgrades/ContractTests.java new file mode 100644 index 000000000..6b627712c --- /dev/null +++ b/contract-constraint-migration/workflows/v1-workflows/src/test/java/corda/samples/upgrades/ContractTests.java @@ -0,0 +1,13 @@ +package corda.samples.upgrades; + +import net.corda.testing.node.MockServices; +import org.junit.Test; + +public class ContractTests { + private final MockServices ledgerServices = new MockServices(); + + @Test + public void dummyTest() { + + } +} \ No newline at end of file diff --git a/contract-constraint-migration/workflows/v1-workflows/src/test/java/corda/samples/upgrades/FlowTests.java b/contract-constraint-migration/workflows/v1-workflows/src/test/java/corda/samples/upgrades/FlowTests.java new file mode 100644 index 000000000..785608080 --- /dev/null +++ b/contract-constraint-migration/workflows/v1-workflows/src/test/java/corda/samples/upgrades/FlowTests.java @@ -0,0 +1,39 @@ +package corda.samples.upgrades; + +import com.google.common.collect.ImmutableList; +import net.corda.testing.node.MockNetwork; +import net.corda.testing.node.MockNetworkParameters; +import net.corda.testing.node.StartedMockNode; +import net.corda.testing.node.TestCordapp; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class FlowTests { + private final MockNetwork network = new MockNetwork(new MockNetworkParameters(ImmutableList.of( + TestCordapp.findCordapp("com.template.contracts"), + TestCordapp.findCordapp("com.template.flows") + ))); + private final StartedMockNode a = network.createNode(); + private final StartedMockNode b = network.createNode(); + + public FlowTests() { +// a.registerInitiatedFlow(Responder.class); +// b.registerInitiatedFlow(Responder.class); + } + + @Before + public void setup() { + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void dummyTest() { + + } +} diff --git a/contract-constraint-migration/workflows/v1-workflows/src/test/java/corda/samples/upgrades/NodeDriver.java b/contract-constraint-migration/workflows/v1-workflows/src/test/java/corda/samples/upgrades/NodeDriver.java new file mode 100644 index 000000000..f0bf1127d --- /dev/null +++ b/contract-constraint-migration/workflows/v1-workflows/src/test/java/corda/samples/upgrades/NodeDriver.java @@ -0,0 +1,39 @@ +package corda.samples.upgrades; + +import com.google.common.collect.ImmutableList; +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.driver.DriverParameters; +import net.corda.testing.driver.NodeParameters; +import net.corda.testing.node.User; +import com.google.common.collect.ImmutableSet; + +import java.util.List; +import static net.corda.testing.driver.Driver.driver; + +/** + * Allows you to run your nodes through an IDE (as opposed to using deployNodes). Do not use in a production + * environment. + */ +public class NodeDriver { + public static void main(String[] args) { + final List rpcUsers = + ImmutableList.of(new User("user1", "test", ImmutableSet.of("ALL"))); + + driver(new DriverParameters().withStartNodesInProcess(true).withWaitForAllNodesToFinish(true), dsl -> { + try { + dsl.startNode(new NodeParameters() + .withProvidedName(new CordaX500Name("PartyA", "London", "GB")) + .withRpcUsers(rpcUsers)).get(); + dsl.startNode(new NodeParameters() + .withProvidedName(new CordaX500Name("PartyB", "New York", "US")) + .withRpcUsers(rpcUsers)).get(); + } catch (Throwable e) { + System.err.println("Encountered exception in node startup: " + e.getMessage()); + e.printStackTrace(); + } + + return null; + } + ); + } +} From f85f7d3cb59a14fef0284831db17359bdda07af9 Mon Sep 17 00:00:00 2001 From: snehadamle Date: Thu, 28 Nov 2019 14:23:49 +0530 Subject: [PATCH 19/20] removing un-necessary spaces --- .../samples/upgrades/states/OldState.java | 4 +- .../samples/upgrades/states/OldState.java | 65 ++++++++++--------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/contract-constraint-migration/contracts/v1-contracts/src/main/java/corda/samples/upgrades/states/OldState.java b/contract-constraint-migration/contracts/v1-contracts/src/main/java/corda/samples/upgrades/states/OldState.java index 84a1d708c..2d9e45223 100644 --- a/contract-constraint-migration/contracts/v1-contracts/src/main/java/corda/samples/upgrades/states/OldState.java +++ b/contract-constraint-migration/contracts/v1-contracts/src/main/java/corda/samples/upgrades/states/OldState.java @@ -11,13 +11,13 @@ import java.util.List; @BelongsToContract(OldContract.class) - public class OldState implements ContractState { +public class OldState implements ContractState { private Party issuer; private Party owner; private int amount; - public OldState(Party issuer, Party owner, int amount) { + public OldState(Party issuer, Party owner, int amount) { this.issuer = issuer; this.owner = owner; this.amount = amount; diff --git a/contract-constraint-migration/contracts/v2-contracts/src/main/java/corda/samples/upgrades/states/OldState.java b/contract-constraint-migration/contracts/v2-contracts/src/main/java/corda/samples/upgrades/states/OldState.java index af0d94702..49d60d172 100644 --- a/contract-constraint-migration/contracts/v2-contracts/src/main/java/corda/samples/upgrades/states/OldState.java +++ b/contract-constraint-migration/contracts/v2-contracts/src/main/java/corda/samples/upgrades/states/OldState.java @@ -1,4 +1,4 @@ - package corda.samples.upgrades.states; +package corda.samples.upgrades.states; import com.google.common.collect.ImmutableList; import corda.samples.upgrades.contracts.OldContract; @@ -10,34 +10,35 @@ import java.util.List; - @BelongsToContract(OldContract.class) - public class OldState implements ContractState { - - private Party issuer; - private Party owner; - private int amount; - - public OldState(Party issuer, Party owner, int amount) { - this.issuer = issuer; - this.owner = owner; - this.amount = amount; - } - - public Party getIssuer() { - return issuer; - } - - public Party getOwner() { - return owner; - } - - public int getAmount() { - return amount; - } - - @NotNull - @Override - public List getParticipants() { - return ImmutableList.of(issuer, owner); - } - } +@BelongsToContract(OldContract.class) +public class OldState implements ContractState { + + private Party issuer; + private Party owner; + private int amount; + + public OldState(Party issuer, Party owner, int amount) { + this.issuer = issuer; + this.owner = owner; + this.amount = amount; + } + + public Party getIssuer() { + return issuer; + } + + public Party getOwner() { + return owner; + } + + public int getAmount() { + return amount; + } + + @NotNull + @Override + public List getParticipants() { + return ImmutableList.of(issuer, owner); + } + +} From b2a3510db00d9efda5572e07ecc71de3964a89d6 Mon Sep 17 00:00:00 2001 From: snehadamle Date: Thu, 28 Nov 2019 14:27:12 +0530 Subject: [PATCH 20/20] adding comments for contract logic --- .../main/java/corda/samples/upgrades/contracts/OldContract.java | 2 +- .../main/java/corda/samples/upgrades/contracts/OldContract.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contract-constraint-migration/contracts/v1-contracts/src/main/java/corda/samples/upgrades/contracts/OldContract.java b/contract-constraint-migration/contracts/v1-contracts/src/main/java/corda/samples/upgrades/contracts/OldContract.java index af3124d6c..aa6c1028a 100644 --- a/contract-constraint-migration/contracts/v1-contracts/src/main/java/corda/samples/upgrades/contracts/OldContract.java +++ b/contract-constraint-migration/contracts/v1-contracts/src/main/java/corda/samples/upgrades/contracts/OldContract.java @@ -9,7 +9,7 @@ public class OldContract implements Contract { @Override public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { - + //add your business logic here } public interface Commands extends CommandData { diff --git a/contract-constraint-migration/contracts/v2-contracts/src/main/java/corda/samples/upgrades/contracts/OldContract.java b/contract-constraint-migration/contracts/v2-contracts/src/main/java/corda/samples/upgrades/contracts/OldContract.java index 1e3723a9d..e4ef8b7fd 100644 --- a/contract-constraint-migration/contracts/v2-contracts/src/main/java/corda/samples/upgrades/contracts/OldContract.java +++ b/contract-constraint-migration/contracts/v2-contracts/src/main/java/corda/samples/upgrades/contracts/OldContract.java @@ -11,8 +11,8 @@ public class OldContract implements Contract { @Override public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + //adding some logic so that we can upgrade from v1 to v2. Add your new business logic here. OldState oldState = (OldState) tx.getOutputStates().get(0); - if(oldState.getAmount() <20) throw new IllegalArgumentException("Amount shd be > 20"); }