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..aa6c1028a --- /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 { + //add your business logic here + } + + 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..2d9e45223 --- /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..e4ef8b7fd --- /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 { + + //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"); + + } + + 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..49d60d172 --- /dev/null +++ b/contract-constraint-migration/contracts/v2-contracts/src/main/java/corda/samples/upgrades/states/OldState.java @@ -0,0 +1,44 @@ +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; + } + ); + } +} 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()); + } +} 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..57e75a5d0 --- /dev/null +++ b/non-fungible-token-dvp/README.md @@ -0,0 +1,68 @@ +

+ Corda +

+ +# NonFungible House Token DvP Sample CorDapp - Java + +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. + + +# 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. + +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. + + start FiatCurrencyIssueFlow currency: USD, amount: 100000000, recipient: PartyC + +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 vaultQuery contractStateType: com.r3.corda.lib.tokens.contracts.states.FungibleToken + +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: 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. + + 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 + + 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 + +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/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..f20bf042d --- /dev/null +++ b/non-fungible-token-dvp/build.gradle @@ -0,0 +1,157 @@ +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' + 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-lib' } + maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-lib-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"]]] + } + 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) { + 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/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..23f32b2e9 --- /dev/null +++ b/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/contracts/HouseContract.java @@ -0,0 +1,35 @@ +package corda.tokenSDK.samples.contracts; + +import com.r3.corda.lib.tokens.contracts.EvolvableTokenContract; +import corda.tokenSDK.samples.states.HouseState; +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 + 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 + public void additionalUpdateChecks(@NotNull LedgerTransaction tx) { + // Write contract validation logic to be performed while updation of token + } + +} 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..51ac8e551 --- /dev/null +++ b/non-fungible-token-dvp/contracts/src/main/java/corda/tokenSDK/samples/states/HouseState.java @@ -0,0 +1,89 @@ +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 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 Party issuer; + private final int fractionDigits = 0; + + //Properties of House State. Some of these values may evolve over time. + 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; + issuer = maintainers.get(0); + } + + @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; + } + + public Party getIssuer() { + return issuer; + } + + @Override + public int getFractionDigits() { + return fractionDigits; + } + + @NotNull + @Override + 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); + } +} \ No newline at end of file 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 000000000..99340b4ad Binary files /dev/null and b/non-fungible-token-dvp/gradle/wrapper/gradle-wrapper.jar differ 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..b4446eaf8 --- /dev/null +++ b/non-fungible-token-dvp/settings.gradle @@ -0,0 +1,2 @@ +include 'workflows' +include 'contracts' \ 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..5a402822e --- /dev/null +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/FiatCurrencyIssueFlow.java @@ -0,0 +1,51 @@ +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; + +/** + * 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 { + + 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 { + /* Create an instance of the fiat currency token */ + 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(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 new file mode 100644 index 000000000..7d6839476 --- /dev/null +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleInitiatorFlow.java @@ -0,0 +1,81 @@ +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.flows.move.MoveTokensUtilitiesKt; +import com.r3.corda.lib.tokens.workflows.internal.flows.distribution.UpdateDistributionListFlow; +import corda.tokenSDK.samples.states.HouseState; +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; + +/** + * 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 { + + 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 { + /* 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 new file mode 100644 index 000000000..26d5c4c30 --- /dev/null +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseSaleResponderFlow.java @@ -0,0 +1,70 @@ +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.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; + +/* +* 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 { + + private final FlowSession counterpartySession; + + public HouseSaleResponderFlow(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Override + @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 */ + 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 + * 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) { + @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..e492a3401 --- /dev/null +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/HouseTokenCreateAndIssueFlow.java @@ -0,0 +1,81 @@ +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.NonFungibleToken; +import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType; +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 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; +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; + +/** + * 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 { + + 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 { + + /* 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 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 + * 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 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 new file mode 100644 index 000000000..942ff0d84 --- /dev/null +++ b/non-fungible-token-dvp/workflows/src/main/java/corda/tokenSDK/samples/contracts/flows/UpdateHouseValuationFlow.java @@ -0,0 +1,47 @@ +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.rpc.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; + +/** + * 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 { + + 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)); + } +} 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; + } + ); + } +} 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..45378bce8 --- /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 : 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 : 61ec42bc-4fed-4e6f-aeb7-8e93d1b3e471 , holder : PartyC , quantity : 5 + +Redeem tokens via PartyC's terminal specifying the issuer + + start RedeemHouseFungibleTokenFlow tokenId : 61ec42bc-4fed-4e6f-aeb7-8e93d1b3e471 , 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 IssueEvolvableTokenFlow tokenId : 45caf6b2-2342-48bc-9f19-ee6ef0528c1f , recipient : PartyB + +Move tokens to PartyC from PartyB s terminal + + start MoveEvolvableTokenFlow tokenId : 45caf6b2-2342-48bc-9f19-ee6ef0528c1f , recipient : PartyC + +Redeem tokens via PartyC's terminal + + start RedeemHouseToken tokenId : 45caf6b2-2342-48bc-9f19-ee6ef0528c1f, issuer : PartyA 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..543c73c2c --- /dev/null +++ b/real-estate-token/build.gradle @@ -0,0 +1,148 @@ +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' + 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-lib' } + 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 000000000..99340b4ad Binary files /dev/null and b/real-estate-token/gradle/wrapper/gradle-wrapper.jar differ 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..d6bfd3b3e --- /dev/null +++ b/real-estate-token/workflows/src/main/java/com/template/flows/RealEstateEvolvableFungibleTokenFlow.java @@ -0,0 +1,197 @@ +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 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 + return subFlow(new IssueTokens(ImmutableList.of(fungibleToken))); + } + } + + /** + * 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 + return subFlow(new MoveFungibleTokens(partyAndAmount)); + } + } + + /** + * 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 + 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 new file mode 100644 index 000000000..ae2f5d2ab --- /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 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 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 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; + } + ); + } +}