diff --git a/Accounts/README.md b/Accounts/README.md index 81a297d5..f304320a 100644 --- a/Accounts/README.md +++ b/Accounts/README.md @@ -6,7 +6,7 @@ This folder features Corda Accounts sample projects. Learn more about [Accounts] This CorDapp mimics a supply chain transaction, where the deal is incorporated among different teams in the companies on both side of the trade.

- Corda + Corda

### [Tic Tac Thor](./tictacthor): @@ -15,3 +15,7 @@ This CorDapp recreates the game of Tic Tac Toe via Corda. It primarily demonstra

Corda

+ +### [World Cup Ticket Booking](./worldcupticketbooking): + +This sample shows you how to integrate accounts and tokens. This sample talks about a scenario where typically when the Cricket season starts, BCCI (Board of Control for Cricket) starts selling tickets. As of now there are multiple dealers whom the BCCI issues tickets and further these dealers sell tickets to their client. We are trying to simulate similar functionality maintaining the entire issuance and selling of the tickets on Corda Platform. \ No newline at end of file diff --git a/Accounts/constants.properties b/Accounts/constants.properties index 2cd5a28c..adcdb361 100644 --- a/Accounts/constants.properties +++ b/Accounts/constants.properties @@ -1,12 +1,12 @@ cordaReleaseGroup=net.corda cordaCoreReleaseGroup=net.corda -cordaVersion=4.6 -cordaCoreVersion=4.6 +cordaVersion=4.10 +cordaCoreVersion=4.10 gradlePluginsVersion=5.0.12 kotlinVersion=1.2.71 junitVersion=4.12 quasarVersion=0.7.10 -log4jVersion =2.11.2 -platformVersion=8 +log4jVersion =2.17.1 +platformVersion=12 slf4jVersion=1.7.25 nettyVersion=4.1.22.Final diff --git a/Accounts/supplychain/Business Flow.png b/Accounts/supplychain/Business_Flow.png similarity index 100% rename from Accounts/supplychain/Business Flow.png rename to Accounts/supplychain/Business_Flow.png diff --git a/Accounts/supplychain/README.md b/Accounts/supplychain/README.md index cc8730a4..87168170 100644 --- a/Accounts/supplychain/README.md +++ b/Accounts/supplychain/README.md @@ -1,6 +1,6 @@ # Accounts_SupplyChain -For More information regarding the Accounts Library, please read at: https://github.com/corda/accounts/blob/master/docs.md +For more information regarding the Accounts Library, please read at: https://github.com/corda/accounts/blob/master/docs.md This sample describes a mock/simple supply chain business flow. @@ -10,10 +10,15 @@ This sample describes a mock/simple supply chain business flow. From the above chart, you can see the flow is going back and forth between different parties' accounts. Please follow the instruction below to experience the [Accounts](https://training.corda.net/libraries/accounts-lib/) library. -# Setting up + +## Pre-Requisites + +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.9/community/getting-set-up.html). + +## Runnning the nodes Go into the project directory and build the project ``` -./gradlew clean deployNodes +./gradlew clean build deployNodes ``` Run the project ``` @@ -21,7 +26,7 @@ Run the project ``` Now, you should have four Corda terminals opened automatically. -# Shell Instructions (Part 1) - Creating & Sharing Accounts +## Shell Instructions (Part 1) - Creating & Sharing Accounts Go to the Buyer's node terminal and paste in the following code: (You can select all 7 lines and copy to the terminal all at once) ``` flow start CreateNewAccount acctName: BuyerProcurement @@ -33,7 +38,7 @@ flow start ShareAccountTo acctNameShared: BuyerFinance, shareTo: Seller flow start ShareAccountTo acctNameShared: BuyerWarehouse, shareTo: ShippingCo flow start ShareAccountTo acctNameShared: BuyerWarehouse, shareTo: Seller ``` -This is creating 3 accounts under Buyer's node and sharing with their specific conterpartie's node or account. +This is creating 3 accounts under Buyer's node and sharing with their specific counterparty's node or account. Go to the Seller's node terminal and paste in the following code: ``` @@ -45,14 +50,14 @@ flow start ShareAccountTo acctNameShared: SellerSales, shareTo: Buyer flow start ShareAccountTo acctNameShared: SellerFinance, shareTo: Buyer flow start ShareAccountTo acctNameShared: SellerInventory, shareTo: ShippingCo ``` -This is creating 3 accounts under Seller's node and sharing with their specific conterparty's node or account. +This is creating 3 accounts under Seller's node and sharing with their specific counterparty's node or account. [Optional]: You can run a vaultQuery to see the [AccountInfo](https://training.corda.net/libraries/accounts-lib/#design) that been stored at each node by using: ``` run vaultQuery contractStateType: com.r3.corda.lib.accounts.contracts.states.AccountInfo ``` -# Shell Instructions (Part 2) - Executing Business Flows -## Step 1: Seller's sales team send inovice of $500 to Buyer's procurement team +## Shell Instructions (Part 2) - Executing Business Flows +### Step 1: Seller's sales team send invoice for $500 to Buyer's procurement team navigate to Seller's node terminal and run ``` flow start SendInvoice whoAmI: SellerSales, whereTo: BuyerProcurement, amount: 500 @@ -63,42 +68,42 @@ flow start ViewInboxByAccount acctname: BuyerProcurement ``` You see that the invoice state amount 500 is returned. You can also replace the BuyerProcurement with BuyerWarehouse to see that the non-relevant accounts has no visiblity about the invoice state. -## Step 2: Buyer's procurement team will send an internal message to Buyer's Buyer's finance team +### Step 2: Buyer's procurement team will send an internal message to Buyer's Buyer's finance team Navigate to Buyer's node terminal and type in: ``` flow start InternalMessage fromWho: BuyerProcurement, whereTo: BuyerFinance, message: Send 500 to SellerFinance ``` [Optional verification]: run ```flow start ViewInboxByAccount acctname: BuyerFinance``` at Buyer' node terminal -## Step 3: Buyer's finance team send a payment to Seller's finance team +### Step 3: Buyer's finance team send a payment to Seller's finance team Navigate to Buyer's node terminal and type in: ``` flow start SendPayment whoAmI: BuyerFinance, whereTo: SellerFinance, amount: 500 ``` [Optional verification]: run ```flow start ViewInboxByAccount acctname: SellerFinance``` at Seller's node terminal -## Step 4: Seller's finance team send an internal message to Seller's inventory team to instruct them to send the cargo +### Step 4: Seller's finance team send an internal message to Seller's inventory team to instruct them to send the cargo Navigate to Seller's node terminal and type in ``` flow start InternalMessage fromWho: SellerFinance, whereTo: SellerInventory, message: send Cargo to Buyer ``` [Optional verification]: run ```flow start ViewInboxByAccount acctname: SellerInventory``` at Seller's node terminal -## step 5: Seller's inventory team send a shipping work order for shipping company +### step 5: Seller's inventory team send a shipping work order for shipping company Navigate to Seller's node terminal and type in ``` flow start SendShippingRequest whoAmI: SellerInventory, whereTo: BuyerWarehouse, shipper: ShippingCo, Cargo: 10 boxes of Books ``` [Optional verification]: run ```run vaultQuery contractStateType: ShippingRequestState``` at ShippingCo's node terminal -## Step 6: Shipping company sends the cargo to Buyer's warehouse +### Step 6: Shipping company sends the cargo to Buyer's warehouse Navigate to ShippingCo's node terminal and type in ``` flow start SendCargo pickupFrom: SellerInventory, shipTo: BuyerWarehouse, cargo: Books ``` [Optional verification]: run ```flow start ViewInboxByAccount acctname: BuyerWarehouse``` at Buyer's node terminal -## Now, the entire business chain is completed. +### Now, the entire business chain is completed. diff --git a/Accounts/supplychain/build.gradle b/Accounts/supplychain/build.gradle index 67dde5d5..4d70ff49 100644 --- a/Accounts/supplychain/build.gradle +++ b/Accounts/supplychain/build.gradle @@ -26,8 +26,7 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -43,12 +42,12 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } //SDK lib - maven { url 'https://software.r3.com/artifactory/corda-lib' } + maven { url 'https://download.corda.net/maven/corda-lib' } //Gradle Plugins maven { url 'https://repo.gradle.org/gradle/libs-releases' } } @@ -90,6 +89,8 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" + //accounts cordapp "$accounts_release_group:accounts-contracts:$accounts_release_version" diff --git a/Accounts/supplychain/repositories.gradle b/Accounts/supplychain/repositories.gradle index 206b9c68..9797c0ea 100644 --- a/Accounts/supplychain/repositories.gradle +++ b/Accounts/supplychain/repositories.gradle @@ -1,8 +1,7 @@ repositories { mavenLocal() mavenCentral() - jcenter() maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/InternalMessage.java b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/InternalMessage.java index 140fcf18..eadf9f59 100644 --- a/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/InternalMessage.java +++ b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/InternalMessage.java @@ -6,6 +6,7 @@ import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; import com.sun.istack.NotNull; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.supplychain.accountUtilities.NewKeyForAccount; import net.corda.samples.supplychain.contracts.InternalMessageStateContract; import net.corda.samples.supplychain.states.InternalMessageState; @@ -81,13 +82,8 @@ public String call() throws FlowException { InternalMessageState output = new InternalMessageState(message,new AnonymousParty(myKey),targetAcctAnonymousParty); // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); TransactionBuilder txbuilder = new TransactionBuilder(notary) .addOutputState(output) diff --git a/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendCargo.java b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendCargo.java index d59c269c..d51f2527 100644 --- a/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendCargo.java +++ b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendCargo.java @@ -6,6 +6,7 @@ import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; import com.sun.istack.NotNull; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.supplychain.accountUtilities.NewKeyForAccount; import net.corda.samples.supplychain.contracts.CargoStateContract; import net.corda.samples.supplychain.states.CargoState; @@ -58,13 +59,8 @@ public String call() throws FlowException { CargoState output = new CargoState(new AnonymousParty(myKey),targetAcctAnonymousParty,cargo,getOurIdentity()); // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); TransactionBuilder txbuilder = new TransactionBuilder(notary) .addOutputState(output) diff --git a/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendInvoice.java b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendInvoice.java index 6162258a..5d17627c 100644 --- a/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendInvoice.java +++ b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendInvoice.java @@ -6,6 +6,7 @@ import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; import com.sun.istack.NotNull; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.supplychain.accountUtilities.NewKeyForAccount; import net.corda.samples.supplychain.contracts.InvoiceStateContract; import net.corda.samples.supplychain.states.InvoiceState; @@ -58,13 +59,8 @@ public String call() throws FlowException { InvoiceState output = new InvoiceState(amount,new AnonymousParty(myKey),targetAcctAnonymousParty, UUID.randomUUID()); // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); TransactionBuilder txbuilder = new TransactionBuilder(notary) .addOutputState(output) diff --git a/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendPayment.java b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendPayment.java index dfca4452..9a65d6df 100644 --- a/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendPayment.java +++ b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendPayment.java @@ -6,6 +6,7 @@ import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; import com.sun.istack.NotNull; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.supplychain.accountUtilities.NewKeyForAccount; import net.corda.samples.supplychain.contracts.PaymentStateContract; import net.corda.samples.supplychain.states.PaymentState; @@ -57,13 +58,8 @@ public String call() throws FlowException { PaymentState output = new PaymentState(amount,new AnonymousParty(myKey),targetAcctAnonymousParty); // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); TransactionBuilder txbuilder = new TransactionBuilder(notary) .addOutputState(output) diff --git a/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendShippingRequest.java b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendShippingRequest.java index 9db64235..962770ee 100644 --- a/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendShippingRequest.java +++ b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendShippingRequest.java @@ -5,6 +5,7 @@ import com.r3.corda.lib.accounts.workflows.services.AccountService; import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; import com.sun.istack.NotNull; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.supplychain.accountUtilities.NewKeyForAccount; import net.corda.samples.supplychain.contracts.ShippingRequestStateContract; import net.corda.samples.supplychain.states.ShippingRequestState; @@ -84,13 +85,8 @@ public String call() throws FlowException { ShippingRequestState output = new ShippingRequestState(new AnonymousParty(myKey),whereTo,shipper,cargo); // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); TransactionBuilder txbuilder = new TransactionBuilder(notary) .addOutputState(output) diff --git a/Accounts/supplychain/workflows/src/test/java/net/corda/samples/supplychain/FlowTests.java b/Accounts/supplychain/workflows/src/test/java/net/corda/samples/supplychain/FlowTests.java index 5dbb7cc6..5332c1fb 100644 --- a/Accounts/supplychain/workflows/src/test/java/net/corda/samples/supplychain/FlowTests.java +++ b/Accounts/supplychain/workflows/src/test/java/net/corda/samples/supplychain/FlowTests.java @@ -6,6 +6,7 @@ import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; import net.corda.core.contracts.StateAndRef; import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; import net.corda.core.node.NetworkParameters; import net.corda.core.node.services.Vault; import net.corda.core.node.services.vault.QueryCriteria; @@ -13,13 +14,11 @@ import net.corda.samples.supplychain.accountUtilities.ShareAccountTo; import net.corda.samples.supplychain.flows.SendInvoice; import net.corda.samples.supplychain.states.InvoiceState; -import net.corda.testing.node.MockNetwork; -import net.corda.testing.node.MockNetworkParameters; -import net.corda.testing.node.TestCordapp; +import net.corda.testing.node.*; import org.junit.After; import org.junit.Before; import org.junit.Test; -import net.corda.testing.node.StartedMockNode; + import java.util.*; import java.time.Instant; import java.util.concurrent.ExecutionException; @@ -42,7 +41,9 @@ public void setup() { TestCordapp.findCordapp("net.corda.samples.supplychain.contracts"), TestCordapp.findCordapp("net.corda.samples.supplychain.flows"), TestCordapp.findCordapp("com.r3.corda.lib.accounts.contracts"), - TestCordapp.findCordapp("com.r3.corda.lib.accounts.workflows"))).withNetworkParameters(testNetworkParameters)); + TestCordapp.findCordapp("com.r3.corda.lib.accounts.workflows"))).withNetworkParameters(testNetworkParameters) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); a = network.createPartyNode(null); b = network.createPartyNode(null); network.runNetwork(); diff --git a/Accounts/tictacthor/README.md b/Accounts/tictacthor/README.md index b989e03f..5c388345 100644 --- a/Accounts/tictacthor/README.md +++ b/Accounts/tictacthor/README.md @@ -1,31 +1,36 @@ # Tic Tac Thor -This CorDapp recreates the game of Tic Tac Toe via Corda. It primarilly demonstrates how you can have [LinearState](https://docs.corda.net/docs/corda-os/api-states.html#linearstate) transactions between cross-node accounts. +This CorDapp recreates the game of Tic Tac Toe via Corda. It primarily demonstrates how you can have [LinearState](https://docs.corda.net/docs/corda-os/api-states.html#linearstate) transactions between cross-node accounts.

Corda

+ +## Pre-Requisites + +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.9/community/getting-set-up.html). + ## Running the sample Deploy and run the nodes by: ``` -./gradlew deployNodes +./gradlew clean build deployNodes ./build/nodes/runnodes ``` -Then you will need to also start the spring server for the Cordapp by running the following commands seperately: +Then you will need to also start the spring server for the CorDapp by running the following commands separately: `./gradlew bootRunDevRel`will have the DevRel server running on 8080 port , and `./gradlew bootRunSOE`will start the Solution Engineering server on 8090 port -## Operating the Cordapp -Now go to postman and excute the following in order: (All of the API are POST request!) +## Operating the CorDapp +Now go to postman and execute the following in order: (All the APIs are POST requests!) 1. Create an account on DevRel node: `http://localhost:8080/createAccount/PeterLi` 2. Create an account on SoE node: `http://localhost:8090/createAccount/DavidWinner` 3. Peter now requests game with David: `http://localhost:8080/requestGameWith/PeterLi/SolutionEng/DavidWinner` -4. David has to accept the challenege: `http://localhost:8090/acceptGameInvite/DavidWinner/DevRel/PeterLi` +4. David has to accept the challenge: `http://localhost:8090/acceptGameInvite/DavidWinner/DevRel/PeterLi` 5. Game Starts, and Peter makes the first move: `http://localhost:8080/startGameAndFirstMove/PeterLi/DavidWinner/0` 6. David's turn: `http://localhost:8090/submitMove/DavidWinner/PeterLi/4` API Syntax: `http://localhost:8080/submitMove/WHO-AM-I/MY-COUNTERPART/POSITION` -From here, you can start play the game by changing the very last number from the `submitMove`API call. The game board is representated by an 1-D array: What we just ran can transfer into a tic-tac-toe game board like the one we see on the right. +From here, you can start play the game by changing the very last number from the `submitMove`API call. The game board is represented by an 1-D array: What we just ran can transfer into a tic-tac-toe game board like the one we see on the right. ``` │0│1│2│ │O│ │ │ │3│4│5│ -> │ │X│ │ @@ -34,7 +39,7 @@ From here, you can start play the game by changing the very last number from the The Game will automatically end when one player wins the game. You can also run `run vaultQuery contractStateType: net.corda.samples.tictacthor.states.BoardState` at any given time to see the board games stored in vault. -now if you want to fast forward the game, Play the following moves in order: +now if you want to fast-forward the game, Play the following moves in order: According to syntax: we should have `http://localhost:8080/submitMove/PeterLi/DavidWinner/3` for the first move below. ``` * Peter: 3 │O│ │ │ @@ -49,7 +54,7 @@ We can play a bit more about the accounts. Now let's create two accounts, a new * Create an account on SoE node: `http://localhost:8090/createAccount/ThorG` Now, try to have Anthony play a game with Thor while start a new game between Peter and David. It worked! -One key feature about account is that, each account's data is segregated, meaning that it can be enforced that each account will not be able to see other account's data. In this sample cordapp, the game is queried by account name. Therefore, we see that each account only knows about the game that he participated. Account Peter doesn't know anything about the game between Thor and Anthony. +One key feature about account is that, each account's data is segregated, meaning that it can be enforced that each account will not be able to see other account's data. In this sample CorDapp, the game is queried by account name. Therefore, we see that each account only knows about the game that he participated. Account Peter doesn't know anything about the game between Thor and Anthony. ## Credit This project is inspired and evolved from a simple [tic-tac-toe](https://github.com/thorgilman/tictactoe) game on Corda by Thor Gilman. diff --git a/Accounts/tictacthor/build.gradle b/Accounts/tictacthor/build.gradle index 3c1b415c..b33c32b0 100644 --- a/Accounts/tictacthor/build.gradle +++ b/Accounts/tictacthor/build.gradle @@ -31,8 +31,8 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -50,12 +50,12 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } //SDK lib - maven { url 'https://software.r3.com/artifactory/corda-lib' } + maven { url 'https://download.corda.net/maven/corda-lib' } //Gradle Plugins maven { url 'https://repo.gradle.org/gradle/libs-releases' } } @@ -100,6 +100,7 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" //Account lib cordapp "$confidential_id_release_group:ci-workflows:$confidential_id_release_version" @@ -117,7 +118,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { cordapp("$confidential_id_release_group:ci-workflows:$confidential_id_release_version") cordapp("$accounts_release_group:accounts-contracts:$accounts_release_version") cordapp("$accounts_release_group:accounts-workflows:$accounts_release_version") - runSchemaMigration = true + runSchemaMigration = true } node { name "O=Notary,L=London,C=GB" diff --git a/Accounts/tictacthor/clients/build.gradle b/Accounts/tictacthor/clients/build.gradle index 5bc011bd..bbba1aea 100644 --- a/Accounts/tictacthor/clients/build.gradle +++ b/Accounts/tictacthor/clients/build.gradle @@ -8,6 +8,12 @@ sourceSets { } } +configurations { + all { + exclude group: 'ch.qos.logback', module: 'logback-classic' + } +} + dependencies { // Corda dependencies. compile "$corda_release_group:corda-rpc:$corda_release_version" diff --git a/Accounts/tictacthor/repositories.gradle b/Accounts/tictacthor/repositories.gradle index 206b9c68..8be7b630 100644 --- a/Accounts/tictacthor/repositories.gradle +++ b/Accounts/tictacthor/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/EndGameFlow.java b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/EndGameFlow.java index bc6bb5f4..3f9d6ab6 100644 --- a/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/EndGameFlow.java +++ b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/EndGameFlow.java @@ -6,6 +6,7 @@ import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; import com.sun.istack.NotNull; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.tictacthor.accountUtilities.NewKeyForAccount; import net.corda.samples.tictacthor.contracts.BoardContract; import net.corda.samples.tictacthor.states.BoardState; @@ -94,13 +95,8 @@ public String call() throws FlowException { //generating State for transfer // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + Party notary = inputBoardStateAndRef.getState().getNotary(); TransactionBuilder txbuilder = new TransactionBuilder(notary) .addInputState(inputBoardStateAndRef) diff --git a/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/StartGameFlow.java b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/StartGameFlow.java index 4a6f0e0b..76ba4367 100644 --- a/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/StartGameFlow.java +++ b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/StartGameFlow.java @@ -6,6 +6,7 @@ import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; import com.sun.istack.NotNull; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.tictacthor.accountUtilities.NewKeyForAccount; import net.corda.samples.tictacthor.contracts.BoardContract; import net.corda.samples.tictacthor.states.BoardState; @@ -93,13 +94,8 @@ public UniqueIdentifier call() throws FlowException { targetAcctAnonymousParty); // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); TransactionBuilder txbuilder = new TransactionBuilder(notary) .addOutputState(initialBoardState) diff --git a/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/SubmitTurnFlow.java b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/SubmitTurnFlow.java index dbfed5a4..477f3a6a 100644 --- a/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/SubmitTurnFlow.java +++ b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/SubmitTurnFlow.java @@ -106,13 +106,7 @@ public String call() throws FlowException { BoardState outputBoardState = inputBoardState.returnNewBoardAfterMove(new Pair<>(x,y),new AnonymousParty(myKey), targetAcctAnonymousParty); // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + Party notary = inputBoardStateAndRef.getState().getNotary(); TransactionBuilder txbuilder = new TransactionBuilder(notary) .addInputState(inputBoardStateAndRef) diff --git a/Accounts/tictacthor/workflows/src/test/java/net/corda/samples/tictacthor/FlowTests.java b/Accounts/tictacthor/workflows/src/test/java/net/corda/samples/tictacthor/FlowTests.java index 841c3ca0..71f10d38 100644 --- a/Accounts/tictacthor/workflows/src/test/java/net/corda/samples/tictacthor/FlowTests.java +++ b/Accounts/tictacthor/workflows/src/test/java/net/corda/samples/tictacthor/FlowTests.java @@ -6,19 +6,18 @@ import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; import net.corda.core.contracts.StateAndRef; import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; import net.corda.core.node.NetworkParameters; import net.corda.core.node.services.vault.QueryCriteria; import net.corda.samples.tictacthor.accountUtilities.CreateNewAccount; import net.corda.samples.tictacthor.accountUtilities.ShareAccountTo; import net.corda.samples.tictacthor.flows.StartGameFlow; import net.corda.samples.tictacthor.states.BoardState; -import net.corda.testing.node.MockNetwork; -import net.corda.testing.node.MockNetworkParameters; -import net.corda.testing.node.TestCordapp; +import net.corda.testing.node.*; import org.junit.After; import org.junit.Before; import org.junit.Test; -import net.corda.testing.node.StartedMockNode; + import java.util.*; import java.time.Instant; import java.util.concurrent.ExecutionException; @@ -40,7 +39,9 @@ public void setup() { TestCordapp.findCordapp("net.corda.samples.tictacthor.contracts"), TestCordapp.findCordapp("net.corda.samples.tictacthor.flows"), TestCordapp.findCordapp("com.r3.corda.lib.accounts.contracts"), - TestCordapp.findCordapp("com.r3.corda.lib.accounts.workflows"))).withNetworkParameters(testNetworkParameters)); + TestCordapp.findCordapp("com.r3.corda.lib.accounts.workflows"))).withNetworkParameters(testNetworkParameters) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); a = network.createPartyNode(null); b = network.createPartyNode(null); network.runNetwork(); diff --git a/Accounts/worldcupticketbooking/LICENCE b/Accounts/worldcupticketbooking/LICENCE new file mode 100644 index 00000000..3ff572d1 --- /dev/null +++ b/Accounts/worldcupticketbooking/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/Accounts/worldcupticketbooking/README.md b/Accounts/worldcupticketbooking/README.md new file mode 100644 index 00000000..324533db --- /dev/null +++ b/Accounts/worldcupticketbooking/README.md @@ -0,0 +1,209 @@ +# T20 Cricket World Cup Ticket Booking CorDapp + + +## Introduction +This sample shows you how to integrate accounts and tokens. This sample talks about a scenario where typically when the Cricket season starts, BCCI (Board of Control for Cricket) starts selling tickets. +As of now there are multiple dealers whom the BCCI issues tickets and further these dealers sell tickets to their client. We are trying to simulate similar functionality maintaining the entire issuance and selling +of the tickets on Corda Platform. + +## Flow logic of the sample application + +Nodes: + +* BCCI node: source of ticket creation. All the tickets in the market are created by this node. +* Bank Node: source of money. All the money that is transacted between each individual is issued by this node. +* Dealer1 Node: Dealer Agency 1 and it includes + * agent1 account: who will get ticket from the BCCI and sell to others. (To demonstrate same node account token transaction.) + * buyer1 account: who will get the ticket from agent1 and later sell to buyer3 (To demonstrate cross node account token transaction) + * buyer2 account: who will buy the ticket from buyer3. +* Dealer2 Node: Dealer Agency 2 and it includes + * buyer3 account: who will get the ticket from buyer1 and sell to buyer2 + +

+ Corda +

+ +## Pre-Requisites + +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.9/community/getting-set-up.html). + +## Running the sample +Deploy and run the nodes by: +``` +./gradlew clean build deployNodes +./build/nodes/runnodes +``` + +### Step 1 +Run the below flow in Dealer1's interactive node shell. This will create the agent1, buyer1 and buyer2 accounts on the Dealer1 node and share this account info with BCCI, Bank, and Dealer2 node respectively. +``` +flow start CreateAndShareAccountFlow accountName: agent1, partyToShareAccountInfoToList: [BCCI, Dealer2] +flow start CreateAndShareAccountFlow accountName: buyer1, partyToShareAccountInfoToList: [Bank, Dealer2] +flow start CreateAndShareAccountFlow accountName: buyer2, partyToShareAccountInfoToList: [Bank, Dealer2] +``` + +Then let's go to the Dealer2 interactive node shell and create buyer3 account: +``` +flow start CreateAndShareAccountFlow accountName: buyer3, partyToShareAccountInfoToList: [Bank, Dealer1] +``` + +Run the below query to confirm if accounts are created on Dealer1 node. Also run the below query on Bank and BCCI's interactive node shell to confirm if account info is shared with these nodes. + + run vaultQuery contractStateType : com.r3.corda.lib.accounts.contracts.states.AccountInfo + +### Step 2 + +Run the below command on the Bank's interactive node shell, which will issue 20 USD to buyer1 account. +``` +start IssueCashFlow accountName : buyer1 , currency : USD , amount : 10 +start IssueCashFlow accountName : buyer3 , currency : USD , amount : 20 +start IssueCashFlow accountName : buyer2 , currency : USD , amount : 50 +``` + +### Step 3 +``` +flow start QuerybyAccount whoAmI: buyer1 +``` +You can check balance of buyer1 account at Dealer1's interactive node shell. +[Option] You can also run the below command to confirm if 20 USD fungible tokens are stored at Dealer1's node. The current holder field in the output will be an [AnonymousParty](https://docs.r3.com/en/platform/corda/4.9/community/api-identity.html) which specifies an account. +``` +run vaultQuery contractStateType : com.r3.corda.lib.tokens.contracts.states.FungibleToken +``` + + +### Step 4 + +Run the below flow on BCCI's node. BCCI node will create base token type for the T20 Ticket for the match MumbaiIndians Vs RajasthanRoyals. The ticket ID returned from this flow will be needed in the next steps. + + start CreateT20CricketTicketTokenFlow ticketTeam : MumbaiIndiansVsRajasthanRoyals + +You can see your ticket state generated via vault query at the BCCI's node: + + + run vaultQuery contractStateType : com.t20worldcup.states.T20CricketTicket + +### Step 5 + +Run the below flow on BCCI's node to issue a non-fungible token based off the token type which we created in Step5. You will need to replace the `` with the uuid returned from step 6. This token will be issued by the BCCI node to agent1 account on Dealer1 node. + + start IssueNonFungibleTicketFlow tokenId : , dealerAccountName : agent1 + +Switching to the Dealer1's node, you can run the following code to confirm if the token has been issued to the dealer1 account. +``` +flow start QuerybyAccount whoAmI: agent1 + +``` +You can also look for the actual state that is recorded by: + + run vaultQuery contractStateType : com.r3.corda.lib.tokens.contracts.states.NonFungibleToken +Note that, the current holder it will be a key representing the account. + + +### Step 6 +``` +flow start DVPAccountsOnSameNode tokenId: , buyerAccountName: buyer1, sellerAccountName: agent1, costOfTicket: 5, currency: USD +``` + +This is the DVP flow where the buyer(buyer1 account on Dealer1 node) account will pay cash to seller account(agent1 account on Dealer1 node), and the seller account will transfer the ticket token to the buyer. Again, replace the `` with the uuid generated in step 6. + +### Step 7 +Now let's continue the flow logic to initiate a ticket sale between buyer1 and buyer3. Go to Dealer2 node and run the following code: +``` +flow start DVPAccountsHostedOnDifferentNodes tokenId: , buyerAccountName: buyer3, sellerAccountName: buyer1, costOfTicket: 10, currency: USD + +``` +Again, We can check for the current vault status by `flow start QuerybyAccount whoAmI: XXXXX` + +### Step 8 +Lastly, we will finish the flow logic by one last transaction between buyer3(from Dealer2 node) and buyer2(from dealer1 node). Go to the Dealer1 node and run: +``` +flow start DVPAccountsHostedOnDifferentNodes tokenId: , buyerAccountName: buyer2, sellerAccountName: buyer3, costOfTicket: 25, currency: USD +``` +We can finish the demo with the following query to see how much they each get after the full cycle of sales. +At Dealer1 node +``` +flow start QuerybyAccount whoAmI: agent1 +flow start QuerybyAccount whoAmI: buyer1 +flow start QuerybyAccount whoAmI: buyer2 +``` +At Dealer2 node +``` +flow start QuerybyAccount whoAmI: buyer3 + +``` +Confirm who owns the [FungibleToken](https://training.corda.net/libraries/tokens-sdk/#fungibletoken) (cash) and [NonFungibleToken](https://training.corda.net/libraries/tokens-sdk/#nonfungibletoken) (ticket) again by running this on Dealer1's node. + + +## Transfer tokens from one account to other + +For someone who is looking into how to only transfer tokens from one account to other use below steps. + + +### Step 1 +Run the below flow on the Dealer1 node. This will create the agent1, buyer1 and buyer2 accounts on the Dealer1 node and share this account info with BCCI, Bank, and Dealer2 node respectively. + +``` +flow start CreateAndShareAccountFlow accountName: agent1, partyToShareAccountInfoToList: [BCCI, Dealer2] +flow start CreateAndShareAccountFlow accountName: buyer1, partyToShareAccountInfoToList: [Bank, Dealer2] +flow start CreateAndShareAccountFlow accountName: buyer2, partyToShareAccountInfoToList: [Bank, Dealer2] +``` + +Then let's go to the Dealer2 node and create buyer3 account: +``` +flow start CreateAndShareAccountFlow accountName: buyer3, partyToShareAccountInfoToList: [Bank, Dealer1] +``` + +Run the below query to confirm if accounts are created on Dealer1 node. Also run the below query on Bank and BCCI node to confirm if account info is shared with these nodes. + + run vaultQuery contractStateType : com.r3.corda.lib.accounts.contracts.states.AccountInfo + + +### Step 2 + +Run the below command on the Bank node, which will issue 77 USD to buyer1 account. + +``` +start IssueCashFlow accountName : buyer1 , currency : USD , amount : 77 + +``` + +### Step 3 +``` +flow start QuerybyAccount whoAmI: buyer1 +``` +You can check balance of buyer1 account at Dealer1's node +[Option] You can also run the below command to confirm if 20 USD fungible tokens are stored at Dealer1's node. The current holder field in the output will be an AnonymousParty which specifies an account. +``` +run vaultQuery contractStateType : com.r3.corda.lib.tokens.contracts.states.FungibleToken +``` + +### Step 4 + + start MoveTokensBetweenAccounts buyerAccountName : buyer1, sellerAccountName : buyer3 , currency : USD , costOfTicket : 10 + +This will move tokens from account buyer1 to account buyer3 + + + +### Step 5 +``` +flow start QuerybyAccount whoAmI: buyer1 +``` +You can check balance of buyer1 account at Dealer1's node +``` +run vaultQuery contractStateType : com.r3.corda.lib.tokens.contracts.states.FungibleToken +``` + +### Step 6 +``` +flow start QuerybyAccount whoAmI: buyer3 +``` +You can check balance of buyer3 account at Dealer2's node +``` +run vaultQuery contractStateType : com.r3.corda.lib.tokens.contracts.states.FungibleToken +``` +## Further Reading + +For accounts visit https://github.com/corda/accounts. + +For tokens visit https://github.com/corda/token-sdk. diff --git a/Accounts/worldcupticketbooking/TRADEMARK b/Accounts/worldcupticketbooking/TRADEMARK new file mode 100644 index 00000000..d2e056b5 --- /dev/null +++ b/Accounts/worldcupticketbooking/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/Accounts/worldcupticketbooking/build.gradle b/Accounts/worldcupticketbooking/build.gradle new file mode 100644 index 00000000..e567053f --- /dev/null +++ b/Accounts/worldcupticketbooking/build.gradle @@ -0,0 +1,228 @@ +buildscript { + Properties constants = new Properties() + file("$projectDir/../constants.properties").withInputStream { constants.load(it) } + + ext { + + corda_release_group = constants.getProperty("cordaReleaseGroup") + corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") + corda_release_version = constants.getProperty("cordaVersion") + corda_core_release_version = constants.getProperty("cordaCoreVersion") + 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() + + //TODO + //move to constant.prop + //accounts + accounts_release_version = '1.0' + accounts_release_group = 'com.r3.corda.lib.accounts' + confidential_id_release_group = "com.r3.corda.lib.ci" + confidential_id_release_version = "1.0" + + //tokens + tokens_release_version = '1.1' + tokens_release_group = 'com.r3.corda.lib.tokens' + + //springboot + spring_boot_version = '2.0.2.RELEASE' + spring_boot_gradle_plugin_version = '2.0.2.RELEASE' + } + + repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://download.corda.net/maven/corda-releases' } + // maven { url 'http://software.r3.com/artifactory/corda-lib-dev' } + maven { url 'http://software.r3.com/artifactory/corda-lib' } + } + + 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 from: "${rootProject.projectDir}/repositories.gradle" + apply plugin: 'java' + + repositories { + mavenLocal() + + mavenCentral() + maven { url 'https://download.corda.net/maven/corda-dependencies' } + // Can be removed post-release - used to get nightly snapshot build. + maven { url 'https://download.corda.net/maven/corda-lib' } + maven { url 'https://download.corda.net/maven/corda-lib-dev' } + // maven { url 'https://jitpack.io' } + maven { url "https://repo.gradle.org/gradle/libs-releases-local" } + } + + 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 + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} + + + + +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 { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_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") + + 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" + cordaDriver "net.corda:corda-shell:4.10" + + //accounts + cordapp "$accounts_release_group:accounts-contracts:$accounts_release_version" + cordapp "$confidential_id_release_group:ci-workflows:$confidential_id_release_version" + cordapp "$accounts_release_group:accounts-workflows:$accounts_release_version" + + //tokens + 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 "$tokens_release_group:tokens-selection:$tokens_release_version" + + +} + +cordapp { + info { + name "CorDapp t20worldcup" + vendor "Corda Open Source" + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + } +} + +task ganache { + subprojects { + if (it.project.name != "clients") { + dependsOn jar + doLast { + copy { + from "${buildDir}/libs" + into "${rootDir}/build/libs" + } + } + } + } +} + +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + nodeDefaults { + projectCordapp { + deploy = false + } + + //acounts + cordapp project(':contracts') + cordapp project(':workflows') + cordapp("$confidential_id_release_group:ci-workflows:$confidential_id_release_version") + cordapp("$accounts_release_group:accounts-contracts:$accounts_release_version") + cordapp("$accounts_release_group:accounts-workflows:$accounts_release_version") + + //tokens + 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("$tokens_release_group:tokens-selection:$tokens_release_version") + runSchemaMigration = true + } + node { + name "O=Notary,L=London,C=GB" + notary = [validating : false] + p2pPort 10002 + rpcSettings { + address("localhost:10003") + adminAddress("localhost:10043") + } + extraConfig = ['h2Settings.address' : 'localhost:20040'] + } + node { + name "O=Dealer1,L=London,C=GB" + p2pPort 10005 + rpcSettings { + address("localhost:10006") + adminAddress("localhost:10046") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + extraConfig = ['h2Settings.address' : 'localhost:20041'] + } + node { + name "O=BCCI,L=New York,C=US" + p2pPort 10008 + rpcSettings { + address("localhost:10009") + adminAddress("localhost:10049") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + extraConfig = ['h2Settings.address' : 'localhost:20042'] + } + node { + name "O=Bank,L=Delhi,C=IN" + p2pPort 10017 + rpcSettings { + address("localhost:10018") + adminAddress("localhost:10058") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + extraConfig = ['h2Settings.address' : 'localhost:20043'] + } + + node { + name "O=Dealer2,L=Delhi,C=IN" + p2pPort 10090 + rpcSettings { + address("localhost:10091") + adminAddress("localhost:10092") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + extraConfig = ['h2Settings.address' : 'localhost:20093'] + } +} + +task installQuasar(type: Copy) { + destinationDir rootProject.file("lib") + from(configurations.quasar) { + rename 'quasar-core(.*).jar', 'quasar.jar' + } +} + diff --git a/Accounts/worldcupticketbooking/clients/build.gradle b/Accounts/worldcupticketbooking/clients/build.gradle new file mode 100644 index 00000000..c6be5398 --- /dev/null +++ b/Accounts/worldcupticketbooking/clients/build.gradle @@ -0,0 +1,48 @@ +apply plugin: 'org.springframework.boot' + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } +} + +configurations { + all { + exclude group: 'ch.qos.logback', module: 'logback-classic' + } +} + +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" +} + +springBoot { + mainClassName = "com.t20worldcup.webserver.Server" +} + +task runClient(type: JavaExec, dependsOn: assemble) { + classpath = sourceSets.main.runtimeClasspath + main = 'com.t20worldcup.Client' + args 'localhost:10006', 'user1', 'test' +} + +task runServer(type: JavaExec, dependsOn: assemble) { + classpath = sourceSets.main.runtimeClasspath + main = 'com.t20worldcup.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/Accounts/worldcupticketbooking/clients/src/main/java/com/t20worldcup/Client.java b/Accounts/worldcupticketbooking/clients/src/main/java/com/t20worldcup/Client.java new file mode 100644 index 00000000..910c7c67 --- /dev/null +++ b/Accounts/worldcupticketbooking/clients/src/main/java/com/t20worldcup/Client.java @@ -0,0 +1,36 @@ +package com.t20worldcup; + +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/Accounts/worldcupticketbooking/clients/src/main/java/com/t20worldcup/webserver/Controller.java b/Accounts/worldcupticketbooking/clients/src/main/java/com/t20worldcup/webserver/Controller.java new file mode 100644 index 00000000..a851c79f --- /dev/null +++ b/Accounts/worldcupticketbooking/clients/src/main/java/com/t20worldcup/webserver/Controller.java @@ -0,0 +1,89 @@ +package com.t20worldcup.webserver; + +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.tokens.contracts.states.FungibleToken; +import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken; +import com.t20worldcup.flows.GetAllAccounts; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +@RestController +@RequestMapping("/") +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 = "/all-accounts") + private List getAllAccounts() { + List result = null; + try { + result = this.proxy.startTrackedFlowDynamic(GetAllAccounts.class).getReturnValue().get(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + + List accountInfoNames = new ArrayList<>(); + + for(AccountInfo accountInfo : result) { + accountInfoNames.add(accountInfo.getName()); + } + + return accountInfoNames; + } + + @PostMapping(value = "/cash-balance") + private Long getCashBalanceForAccount(String accountId) { + + UUID uuid = UUID.fromString(accountId); + + QueryCriteria criteria = new QueryCriteria.VaultQueryCriteria().withStatus(Vault.StateStatus.UNCONSUMED). + withExternalIds(Arrays.asList(uuid)); + + List> list = this.proxy.vaultQueryByCriteria(criteria, FungibleToken.class).getStates(); + + Long totalBalance = 0L; + + for(StateAndRef stateAndRef : list) { + totalBalance += stateAndRef.getState().getData().getAmount().getQuantity(); + } + + return totalBalance; + } + + @PostMapping(value = "/is-account-owner-of-ticket") + private String isAccountOwnerOfTicket(String accountId, String nonFungibleTokenId) { + + UUID uuid = UUID.fromString(accountId); + + QueryCriteria criteria = new QueryCriteria.VaultQueryCriteria().withStatus(Vault.StateStatus.UNCONSUMED). + withExternalIds(Arrays.asList(uuid)); + + List> list = this.proxy.vaultQueryByCriteria(criteria, NonFungibleToken.class).getStates(); + + for(StateAndRef nonFungibleTokenStateAndRef : list) { + if (nonFungibleTokenStateAndRef.getState().getData().getLinearId().getId().equals(UUID.fromString(nonFungibleTokenId))) { + return "This account does hold the ticket"; + } + } + return "This account does not hold the ticket"; + } +} \ No newline at end of file diff --git a/Accounts/worldcupticketbooking/clients/src/main/java/com/t20worldcup/webserver/NodeRPCConnection.java b/Accounts/worldcupticketbooking/clients/src/main/java/com/t20worldcup/webserver/NodeRPCConnection.java new file mode 100644 index 00000000..af714c22 --- /dev/null +++ b/Accounts/worldcupticketbooking/clients/src/main/java/com/t20worldcup/webserver/NodeRPCConnection.java @@ -0,0 +1,48 @@ +package com.t20worldcup.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/Accounts/worldcupticketbooking/clients/src/main/java/com/t20worldcup/webserver/Starter.java b/Accounts/worldcupticketbooking/clients/src/main/java/com/t20worldcup/webserver/Starter.java new file mode 100644 index 00000000..cb9e63ee --- /dev/null +++ b/Accounts/worldcupticketbooking/clients/src/main/java/com/t20worldcup/webserver/Starter.java @@ -0,0 +1,23 @@ +package com.t20worldcup.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(); + } +} \ No newline at end of file diff --git a/Accounts/worldcupticketbooking/clients/src/main/resources/static/app.js b/Accounts/worldcupticketbooking/clients/src/main/resources/static/app.js new file mode 100644 index 00000000..c58d2de8 --- /dev/null +++ b/Accounts/worldcupticketbooking/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/Accounts/worldcupticketbooking/clients/src/main/resources/static/index.html b/Accounts/worldcupticketbooking/clients/src/main/resources/static/index.html new file mode 100644 index 00000000..758501dd --- /dev/null +++ b/Accounts/worldcupticketbooking/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/Accounts/worldcupticketbooking/config/dev/log4j2.xml b/Accounts/worldcupticketbooking/config/dev/log4j2.xml new file mode 100644 index 00000000..34ba4d45 --- /dev/null +++ b/Accounts/worldcupticketbooking/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/Accounts/worldcupticketbooking/config/test/log4j2.xml b/Accounts/worldcupticketbooking/config/test/log4j2.xml new file mode 100644 index 00000000..cd9926ca --- /dev/null +++ b/Accounts/worldcupticketbooking/config/test/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n + > + + + + + + + + + + + + diff --git a/Accounts/worldcupticketbooking/contracts/build.gradle b/Accounts/worldcupticketbooking/contracts/build.gradle new file mode 100644 index 00000000..a8f6b788 --- /dev/null +++ b/Accounts/worldcupticketbooking/contracts/build.gradle @@ -0,0 +1,37 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + contract { + name "t20worldcup Contracts" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main{ + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + } + test{ + java{ + srcDir 'src/test/java' + java.outputDir = file('bin/test') + } + } +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + cordaCompile "$tokens_release_group:tokens-contracts:$tokens_release_version" +} \ No newline at end of file diff --git a/Accounts/worldcupticketbooking/contracts/src/main/java/com/t20worldcup/contracts/T20CricketTicketContract.java b/Accounts/worldcupticketbooking/contracts/src/main/java/com/t20worldcup/contracts/T20CricketTicketContract.java new file mode 100644 index 00000000..ebd02248 --- /dev/null +++ b/Accounts/worldcupticketbooking/contracts/src/main/java/com/t20worldcup/contracts/T20CricketTicketContract.java @@ -0,0 +1,26 @@ +package com.t20worldcup.contracts; + +import com.r3.corda.lib.tokens.contracts.EvolvableTokenContract; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import org.jetbrains.annotations.NotNull; + +public class T20CricketTicketContract extends EvolvableTokenContract implements Contract { + + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + + } + + @Override + public void additionalCreateChecks(@NotNull LedgerTransaction tx) { + // Write contract validation logic to be performed while creation of token + + } + + @Override + public void additionalUpdateChecks(@NotNull LedgerTransaction tx) { + // Write contract validation logic to be performed while updation of token + } + +} diff --git a/Accounts/worldcupticketbooking/contracts/src/main/java/com/t20worldcup/states/T20CricketTicket.java b/Accounts/worldcupticketbooking/contracts/src/main/java/com/t20worldcup/states/T20CricketTicket.java new file mode 100644 index 00000000..76998c06 --- /dev/null +++ b/Accounts/worldcupticketbooking/contracts/src/main/java/com/t20worldcup/states/T20CricketTicket.java @@ -0,0 +1,63 @@ +package com.t20worldcup.states; + +import com.r3.corda.lib.tokens.contracts.states.EvolvableTokenType; +import com.t20worldcup.contracts.T20CricketTicketContract; +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.util.Arrays; +import java.util.List; + +@BelongsToContract(T20CricketTicketContract.class) +public class T20CricketTicket extends EvolvableTokenType { + + private UniqueIdentifier linearId; + private String ticketTeam; + private Party issuer;//dealer + //private AbstractParty owner;//for the first time dealer then customers + + public T20CricketTicket(UniqueIdentifier linearId, String ticketTeam, Party issuer) { + this.linearId = linearId; + this.ticketTeam = ticketTeam; + this.issuer = issuer; + //this.owner = owner; + } + + @Override + public int getFractionDigits() { + return 0; + } + + @NotNull + @Override + public List getMaintainers() { + return Arrays.asList(this.getIssuer()); + } + +// @NotNull +// @Override +// public List getParticipants() { +// return Arrays.asList(this.getOwner()); +// } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return this.linearId; + } + +// public AbstractParty getOwner() { +// return owner; +// } + + public String getTicketTeam() { + return ticketTeam; + } + + public Party getIssuer() { + return issuer; + } + +} diff --git a/Accounts/worldcupticketbooking/contracts/src/test/java/com/t20worldcup/contracts/ContractTests.java b/Accounts/worldcupticketbooking/contracts/src/test/java/com/t20worldcup/contracts/ContractTests.java new file mode 100644 index 00000000..d5c7394d --- /dev/null +++ b/Accounts/worldcupticketbooking/contracts/src/test/java/com/t20worldcup/contracts/ContractTests.java @@ -0,0 +1,13 @@ +package com.t20worldcup.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/Accounts/worldcupticketbooking/gradle.properties b/Accounts/worldcupticketbooking/gradle.properties new file mode 100644 index 00000000..9c8df25c --- /dev/null +++ b/Accounts/worldcupticketbooking/gradle.properties @@ -0,0 +1,3 @@ +name=Test +group=com.t20worldcup +version=0.1 \ No newline at end of file diff --git a/Accounts/worldcupticketbooking/gradle/wrapper/gradle-wrapper.jar b/Accounts/worldcupticketbooking/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..99340b4a Binary files /dev/null and b/Accounts/worldcupticketbooking/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Accounts/worldcupticketbooking/gradle/wrapper/gradle-wrapper.properties b/Accounts/worldcupticketbooking/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..2a2a3a8e --- /dev/null +++ b/Accounts/worldcupticketbooking/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-5.4.1-all.zip diff --git a/Accounts/worldcupticketbooking/gradlew b/Accounts/worldcupticketbooking/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/Accounts/worldcupticketbooking/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/Accounts/worldcupticketbooking/gradlew.bat b/Accounts/worldcupticketbooking/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/Accounts/worldcupticketbooking/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/Accounts/worldcupticketbooking/images/T20-World-Cup-e1578570579412.jpg b/Accounts/worldcupticketbooking/images/T20-World-Cup-e1578570579412.jpg new file mode 100644 index 00000000..9c7e051a Binary files /dev/null and b/Accounts/worldcupticketbooking/images/T20-World-Cup-e1578570579412.jpg differ diff --git a/Accounts/worldcupticketbooking/images/graph.png b/Accounts/worldcupticketbooking/images/graph.png new file mode 100644 index 00000000..f434fe1e Binary files /dev/null and b/Accounts/worldcupticketbooking/images/graph.png differ diff --git a/Accounts/worldcupticketbooking/images/workflow.png b/Accounts/worldcupticketbooking/images/workflow.png new file mode 100644 index 00000000..bc401c35 Binary files /dev/null and b/Accounts/worldcupticketbooking/images/workflow.png differ diff --git a/Accounts/worldcupticketbooking/repositories.gradle b/Accounts/worldcupticketbooking/repositories.gradle new file mode 100644 index 00000000..072b616b --- /dev/null +++ b/Accounts/worldcupticketbooking/repositories.gradle @@ -0,0 +1,10 @@ +repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://jitpack.io' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } + maven { url 'http://software.r3.com/artifactory/corda-lib-dev' } + maven { url 'http://software.r3.com/artifactory/corda-lib' } +} diff --git a/Accounts/worldcupticketbooking/settings.gradle b/Accounts/worldcupticketbooking/settings.gradle new file mode 100644 index 00000000..2514aca2 --- /dev/null +++ b/Accounts/worldcupticketbooking/settings.gradle @@ -0,0 +1,3 @@ +include 'workflows' +include 'contracts' +include 'clients' \ No newline at end of file diff --git a/Accounts/worldcupticketbooking/workflows/build.gradle b/Accounts/worldcupticketbooking/workflows/build.gradle new file mode 100644 index 00000000..b2f724d5 --- /dev/null +++ b/Accounts/worldcupticketbooking/workflows/build.gradle @@ -0,0 +1,78 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + workflow { + name "t20worldcup Flows" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/dev") + } + } + test { + java { + srcDir 'src/test/java' + java.outputDir = file('bin/main') + } + 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_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":contracts") + + //Account dependencies + cordaCompile "$confidential_id_release_group:ci-workflows:$confidential_id_release_version" + cordaCompile "$accounts_release_group:accounts-workflows:$accounts_release_version" + cordaCompile "$accounts_release_group:accounts-contracts:$accounts_release_version" + + + // Token SDK dependencies. + cordaCompile "$tokens_release_group:tokens-workflows:$tokens_release_version" + cordaCompile "$tokens_release_group:tokens-contracts:$tokens_release_version" + cordaCompile "$tokens_release_group:tokens-money:$tokens_release_version" + cordapp "$tokens_release_group:tokens-selection:$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/Accounts/worldcupticketbooking/workflows/src/integrationTest/java/com/t20worldcup/DriverBasedTest.java b/Accounts/worldcupticketbooking/workflows/src/integrationTest/java/com/t20worldcup/DriverBasedTest.java new file mode 100644 index 00000000..1ba4baba --- /dev/null +++ b/Accounts/worldcupticketbooking/workflows/src/integrationTest/java/com/t20worldcup/DriverBasedTest.java @@ -0,0 +1,48 @@ +package com.t20worldcup; + +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/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/CreateAndShareAccountFlow.java b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/CreateAndShareAccountFlow.java new file mode 100644 index 00000000..cb226518 --- /dev/null +++ b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/CreateAndShareAccountFlow.java @@ -0,0 +1,46 @@ +package com.t20worldcup.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.flows.CreateAccount; +import com.r3.corda.lib.accounts.workflows.flows.ShareAccountInfo; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.InitiatingFlow; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; + +import java.util.List; + +/** + * This flow will create the account on the node on which you run this flow. This is done using inbuilt flow called CreateAccount. + * CreateAccount creates an AccountInfo object which has name, host and id as its fields. This is mapped to Account table in the db. + * For any other party to transact with this account, this AccountInfo will haveto be shared with that Party. + * Hence the Ipl Ticket Dealers create the ticket buyers accounts on their end and share this accountInfo with the Bank node and BCCI node. + */ +@StartableByRPC +@InitiatingFlow +public class CreateAndShareAccountFlow extends FlowLogic { + + private final String accountName; + private final List partyToShareAccountInfoToList; + + public CreateAndShareAccountFlow(String accountName, List partyToShareAccountInfoToList) { + this.accountName = accountName; + this.partyToShareAccountInfoToList = partyToShareAccountInfoToList; + } + + @Override + @Suspendable + public String call() throws FlowException { + + //Call inbuilt CreateAccount flow to create the AccountInfo object + StateAndRef accountInfoStateAndRef = (StateAndRef) subFlow(new CreateAccount(accountName)); + + //Share this AccountInfo object with the parties who want to transact with this account + subFlow(new ShareAccountInfo(accountInfoStateAndRef, partyToShareAccountInfoToList)); + return "" + accountName +"has been created and shared to " +partyToShareAccountInfoToList+"."; + } +} diff --git a/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/CreateT20CricketTicketTokenFlow.java b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/CreateT20CricketTicketTokenFlow.java new file mode 100644 index 00000000..f75f0d25 --- /dev/null +++ b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/CreateT20CricketTicketTokenFlow.java @@ -0,0 +1,51 @@ +package com.t20worldcup.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.t20worldcup.states.T20CricketTicket; +import com.r3.corda.lib.tokens.workflows.flows.rpc.CreateEvolvableTokens; +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.InitiatingFlow; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.identity.CordaX500Name; + +/** + * This flow should be run by BCCI node. BCCI node will take care of issuing the base token type for the Ipl ticket. The token type will be craeted on the BCCI node + * and need not be shared with anyone. Later BCCI will Issue NonFungible tokens on this base type. + * Ipl base type will be of type EvolvableToken. Keep in mind that the maintainer of the EvolvableTokenType is of type Party and not of type AnonymousParty + */ +@StartableByRPC +@InitiatingFlow +public class CreateT20CricketTicketTokenFlow extends FlowLogic { + + private final String ticketTeam; + + public CreateT20CricketTicketTokenFlow(String ticketTeam) { + this.ticketTeam = ticketTeam; + } + + @Override + @Suspendable + public String call() throws FlowException { + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + //create token type by passing in the name of the ipl match. specify the maintainer as BCCI + UniqueIdentifier id = new UniqueIdentifier(); + T20CricketTicket t20CricketTicket = new T20CricketTicket(id, ticketTeam, getOurIdentity()); + + //warp it with transaction state specifying the notary + TransactionState transactionState = new TransactionState(t20CricketTicket, notary); + + //call built in sub flow CreateEvolvableTokens to craete the base type on BCCI node + SignedTransaction stx = subFlow(new CreateEvolvableTokens(transactionState)); + + return "Ticket for "+ticketTeam+" has been created. With Ticket ID: "+ id.toString()+"" + + "\ntxId: "+stx.getId()+""; + } +} diff --git a/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/DVPAccountsHostedOnDifferentNodes.java b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/DVPAccountsHostedOnDifferentNodes.java new file mode 100644 index 00000000..39243ab4 --- /dev/null +++ b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/DVPAccountsHostedOnDifferentNodes.java @@ -0,0 +1,281 @@ +package com.t20worldcup.flows; + + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken; +import com.r3.corda.lib.tokens.workflows.utilities.QueryUtilitiesKt; +import com.t20worldcup.states.T20CricketTicket; +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.UtilitiesKt; +import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; +import com.r3.corda.lib.ci.workflows.SyncKeyMappingFlow; +import com.r3.corda.lib.ci.workflows.SyncKeyMappingFlowHandler; +import com.r3.corda.lib.tokens.contracts.states.FungibleToken; +import com.r3.corda.lib.tokens.contracts.types.TokenPointer; +import com.r3.corda.lib.tokens.contracts.types.TokenType; +import com.r3.corda.lib.tokens.money.FiatCurrency; +import com.r3.corda.lib.tokens.selection.TokenQueryBy; +import com.r3.corda.lib.tokens.selection.database.config.DatabaseSelectionConfigKt; +import com.r3.corda.lib.tokens.selection.database.selector.DatabaseTokenSelection; +import com.r3.corda.lib.tokens.workflows.flows.move.MoveTokensUtilitiesKt; +import kotlin.Pair; +import net.corda.core.contracts.*; +import net.corda.core.flows.*; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.AnonymousParty; +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 org.jetbrains.annotations.NotNull; + +import java.security.PublicKey; +import java.util.*; + + +/** + * This flow shows you how to perform a dvp between accounts hosted on different nodes. Buyer account is on one host and Seller account is on other node. + * Majority of the code which deals with building the transaction for dvp is similar to DVPAccountsOnSameNode.java. + * Few more things to keep in mind - when accounts hosted on different nodes want to transact with each other you need to make sure to + * share the keys which account on one node must created and used. This is done as the other node/account is not aware which key was used to sign + * a shared transaction. To verify this, Node1 needs to share the keys. This can be done by calling SyncKeyMappingFlow. This flow will have to be called from + * both the sides. Once we sync accounts and keys and built the transaction, we can then call CollectSignaturesFlow. + * Look at how I am passing the 4 th parameter to CollectSignaturesFlow which tells the counterparty which all keys were used by the initiator for signing, + * so the counterparty will use remaining keys to sign the transaction. There is one more tricky bit tto this. When we call MoveTokensUtilitiesKt.addMoveTokens, + * internally a new Anonymous key is created and assigned to the current holder. So internally as well token sdk uses Confidential Identity. We will have to + * explicitly sync this key as well, as the counterparty is not aware of this key. + */ +@StartableByRPC +@InitiatingFlow +public class DVPAccountsHostedOnDifferentNodes extends FlowLogic { + + private final String tokenId; + private final String buyerAccountName; + private final String sellerAccountName; + private final String currency; + private final Long costOfTicket; + + public DVPAccountsHostedOnDifferentNodes(String tokenId, String buyerAccountName, String sellerAccountName, Long costOfTicket, String currency) { + this.tokenId = tokenId; + this.buyerAccountName = buyerAccountName; + this.sellerAccountName = sellerAccountName; + this.costOfTicket = costOfTicket; + this.currency = currency; + } + + + //This flow will be initiated by the buyer who wishes to buy the ticket from an account hosted on different node. + //The buyer will call the generate move token , generate the cash token transfer states and send it to selelr. + @Override + @Suspendable + public String call() throws FlowException { + + //Get buyers and sellers account infos + AccountInfo buyerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(buyerAccountName).get(0).getState().getData(); + AccountInfo sellerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(sellerAccountName).get(0).getState().getData(); + + //Generate new keys for buyers and sellers + //make sure to sync these keys with the counterparty by calling SyncKeyMappingFlow as below + AnonymousParty buyerAccount = subFlow(new RequestKeyForAccount(buyerAccountInfo));//mappng saved locally + AnonymousParty sellerAccount = subFlow(new RequestKeyForAccount(sellerAccountInfo));//mappiing requested from counterparty. does the counterparty save i dont think so + + + //Part1 : Move non fungible token - ticket from seller to buyer + //establish session with seller + FlowSession sellerSession = initiateFlow(sellerAccountInfo.getHost()); + + //send uuid, buyer,seller account name to seller + sellerSession.send(tokenId); + sellerSession.send(buyerAccountName); + sellerSession.send(sellerAccountName); + + //buyer will create generate a move tokens state and send this state with new holder(seller) to seller + Amount amount = new Amount(costOfTicket, FiatCurrency.Companion.getInstance(currency)); + + //Buyer Query for token balance. + QueryCriteria queryCriteria = QueryUtilitiesKt.heldTokenAmountCriteria(this.getInstance(currency), buyerAccount).and(QueryUtilitiesKt.sumTokenCriteria()); + List sum = getServiceHub().getVaultService().queryBy(FungibleToken.class, queryCriteria).component5(); + if(sum.size() == 0) + throw new FlowException(buyerAccountName + " has 0 token balance. Please ask the Bank to issue some cash."); + else { + Long tokenBalance = (Long) sum.get(0); + if(tokenBalance < costOfTicket) + throw new FlowException("Available token balance of " + buyerAccountName+ " is less than the cost of the ticket. Please ask the Bank to issue some cash if you wish to buy the ticket "); + } + + //the tokens to move to new account which is the seller account + Pair> partyAndAmount = new Pair(sellerAccount, amount); + + //let's use the DatabaseTokenSelection to get the tokens from the db + DatabaseTokenSelection tokenSelection = new DatabaseTokenSelection( + getServiceHub(), + DatabaseSelectionConfigKt.MAX_RETRIES_DEFAULT, + DatabaseSelectionConfigKt.RETRY_SLEEP_DEFAULT, + DatabaseSelectionConfigKt.RETRY_CAP_DEFAULT, + DatabaseSelectionConfigKt.PAGE_SIZE_DEFAULT + ); + + //call generateMove which gives us 2 stateandrefs with tokens having new owner as seller. + Pair>, List> inputsAndOutputs = + tokenSelection.generateMove(Arrays.asList(partyAndAmount), buyerAccount, new TokenQueryBy(), getRunId().getUuid()); + + //send the generated inputsAndOutputs to the seller + subFlow(new SendStateAndRefFlow(sellerSession, inputsAndOutputs.getFirst())); + sellerSession.send(inputsAndOutputs.getSecond()); + + //sync following keys with seller - buyeraccounts, selleraccounts which we generated above using RequestKeyForAccount, and IMP: also share the anonymouse keys + //created by the above token move method for the holder. + List signers = new ArrayList<>(); + signers.add(buyerAccount); + signers.add(sellerAccount); + + List> inputs = inputsAndOutputs.getFirst(); + for(StateAndRef tokenStateAndRef : inputs) { + signers.add(tokenStateAndRef.getState().getData().getHolder()); + } + + //Sync our associated keys with the conterparties. + subFlow(new SyncKeyMappingFlow(sellerSession, signers)); + + //this is the handler for synckeymapping called by seller. seller must also have created some keys not known to us - buyer + subFlow(new SyncKeyMappingFlowHandler(sellerSession)); + + //recieve the data from counter session in tx formatt. + subFlow(new SignTransactionFlow(sellerSession) { + @Override + protected void checkTransaction(@NotNull SignedTransaction stx) throws FlowException { + // Custom Logic to validate transaction. + } + }); + SignedTransaction stx = subFlow(new ReceiveFinalityFlow(sellerSession)); + return ("The ticket is sold to "+buyerAccountName+""+ "\ntxID: " + stx.getId().toString()); + } + + public TokenType getInstance(String currencyCode) { + Currency currency = Currency.getInstance(currencyCode); + return new TokenType(currency.getCurrencyCode(), 0); + } +} + + + +//seller will get the token cash transfer state from buyer. seller will generate the non fungible transfer - ticket transfer state. Seller will add these two +//inouts and outputs to a transaction and perform dvp. +@InitiatedBy(DVPAccountsHostedOnDifferentNodes.class) +class DVPAccountsHostedOnDifferentNodesResponder extends FlowLogic { + + private final FlowSession otherSide; + + public DVPAccountsHostedOnDifferentNodesResponder(FlowSession otherSide) { + this.otherSide = otherSide; + } + + @Override + @Suspendable + public Void call() throws FlowException { + + //get all the details from the seller + String tokenId = otherSide.receive(String.class).unwrap(t->t); + String buyerAccountName = otherSide.receive(String.class).unwrap(t->t); + String sellerAccountName = otherSide.receive(String.class).unwrap(t->t); + + List> inputs = subFlow(new ReceiveStateAndRefFlow<>(otherSide)); + List moneyReceived = otherSide.receive(List.class).unwrap(value -> value); + + //call SyncKeyMappingHandler for SyncKey Mapping called at buyers side + subFlow(new SyncKeyMappingFlowHandler(otherSide)); + + //Get buyers and sellers account infos + AccountInfo buyerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(buyerAccountName).get(0).getState().getData(); + AccountInfo sellerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(sellerAccountName).get(0).getState().getData(); + + //Generate new keys for buyers and sellers + AnonymousParty buyerAccount = subFlow(new RequestKeyForAccount(buyerAccountInfo)); + AnonymousParty sellerAccount = subFlow(new RequestKeyForAccount(sellerAccountInfo)); + + //query for all tickets + QueryCriteria queryCriteriaForSellerTicketType = new QueryCriteria.VaultQueryCriteria() + .withExternalIds(Arrays.asList(sellerAccountInfo.getIdentifier().getId())) + .withStatus(Vault.StateStatus.UNCONSUMED); + List> allNonfungibleTokens = getServiceHub().getVaultService() + .queryBy(NonFungibleToken.class, queryCriteriaForSellerTicketType).getStates(); + + //Retrieve the one that he wants to sell + StateAndRef matchedNonFungibleToken; + if(allNonfungibleTokens.stream() + .filter(i-> i.getState().getData().getTokenType().getTokenIdentifier().equals(tokenId)) + .findAny().isPresent()) { + matchedNonFungibleToken = allNonfungibleTokens.stream() + .filter(i-> i.getState().getData().getTokenType().getTokenIdentifier().equals(tokenId)) + .findAny().get(); + } + else + throw new FlowException(sellerAccountName + "does not own ticket with token id - " + tokenId); + + String ticketId = matchedNonFungibleToken.getState().getData().getTokenType().getTokenIdentifier(); + + //Query for the ticket Buyer wants to sell. + QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria(null, Arrays.asList(UUID.fromString(ticketId)), null, + Vault.StateStatus.UNCONSUMED, null); + + //grab the t20worldcup off the ledger + StateAndRef stateAndRef = getServiceHub().getVaultService(). + queryBy(T20CricketTicket.class, queryCriteria).getStates().get(0); + + T20CricketTicket evolvableTokenType = stateAndRef.getState().getData(); + + //get the pointer pointer to the T20CricketTicket + TokenPointer tokenPointer = evolvableTokenType.toPointer(evolvableTokenType.getClass()); + + // Obtain a reference to a notary we wish to use. + Party notary = stateAndRef.getState().getNotary(); + + TransactionBuilder transactionBuilder = new TransactionBuilder(notary); + + //part1 of DVP is to transfer the non fungible token from seller to buyer + MoveTokensUtilitiesKt.addMoveNonFungibleTokens(transactionBuilder, getServiceHub(), tokenPointer, buyerAccount); + + //part2 of DVP is to transfer cash - fungible token from buyer to seller and return the change to buyer + MoveTokensUtilitiesKt.addMoveTokens(transactionBuilder, inputs, moneyReceived); + + //sync keys with buyer, again sync for similar members + + List signers = new ArrayList<>(); + signers.add(buyerAccount); + signers.add(sellerAccount); + + for(StateAndRef tokenStateAndRef : inputs) { + signers.add(tokenStateAndRef.getState().getData().getHolder()); + } + + subFlow(new SyncKeyMappingFlow(otherSide, signers)); + + //call filterMyKeys to get the my signers for seller node and pass in as a 4th parameter to CollectSignaturesFlow. + //by doing this we tell CollectSignaturesFlow that these are the signers which have already signed the transaction + List> commandWithPartiesList = transactionBuilder.toLedgerTransaction(getServiceHub()).getCommands(); + + List mySigners = new ArrayList(); + + for(CommandWithParties commandDataCommandWithParties : commandWithPartiesList) { + if(((ArrayList)(getServiceHub().getKeyManagementService().filterMyKeys(commandDataCommandWithParties.getSigners()))).size() > 0) { + mySigners.add(((ArrayList)getServiceHub().getKeyManagementService().filterMyKeys(commandDataCommandWithParties.getSigners())).get(0)); + } + } + + //sign the transaction with the signers we got by calling filterMyKeys + SignedTransaction selfSignedTransaction = getServiceHub().signInitialTransaction(transactionBuilder, mySigners); + + final SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow( + selfSignedTransaction, + Collections.singletonList(otherSide), + mySigners)); + + //call FinalityFlow for finality + subFlow(new FinalityFlow(fullySignedTx, Arrays.asList(otherSide))); + + return null; + } +} + + diff --git a/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/DVPAccountsOnSameNode.java b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/DVPAccountsOnSameNode.java new file mode 100644 index 00000000..e71721e7 --- /dev/null +++ b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/DVPAccountsOnSameNode.java @@ -0,0 +1,195 @@ +package com.t20worldcup.flows; + + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.tokens.contracts.states.FungibleToken; +import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken; +import com.r3.corda.lib.tokens.contracts.types.TokenType; +import com.r3.corda.lib.tokens.workflows.utilities.QueryUtilitiesKt; +import com.t20worldcup.states.T20CricketTicket; +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.UtilitiesKt; +import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; +import com.r3.corda.lib.tokens.contracts.types.TokenPointer; +import com.r3.corda.lib.tokens.money.FiatCurrency; +import com.r3.corda.lib.tokens.workflows.flows.move.MoveTokensUtilitiesKt; +import com.r3.corda.lib.tokens.workflows.internal.flows.finality.ObserverAwareFinalityFlow; +import com.r3.corda.lib.tokens.workflows.types.PartyAndAmount; +import net.corda.core.contracts.Amount; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.flows.*; +import net.corda.core.identity.AnonymousParty; +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 org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.Currency; +import java.util.List; +import java.util.UUID; + +/** + * This is the DVP flow, where the buyer account buys the ticket token from the dealer account and in turn transfers him cash worth of the ticket. + * Once buyer1 buys the token from the dealer, he can further sell this ticket to other buyers. + * Note : this flow handles dvp from account to account on same node. This flow later will be modified if a buyer on dealer1 node wants to buy ticket from + * dealer2 node. + */ +@StartableByRPC +@InitiatingFlow +public class DVPAccountsOnSameNode extends FlowLogic { + + private final String tokenId; + private final String buyerAccountName; + private final String sellerAccountName; + private final String currency; + private final Long costOfTicket; + + public DVPAccountsOnSameNode(String tokenId, String buyerAccountName, String sellerAccountName, Long costOfTicket, String currency) { + this.tokenId = tokenId; + this.buyerAccountName = buyerAccountName; + this.sellerAccountName = sellerAccountName; + this.costOfTicket = costOfTicket; + this.currency = currency; + } + + @Override + @Suspendable + public String call() throws FlowException { + + //Get buyers and sellers account infos + AccountInfo buyerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(buyerAccountName).get(0).getState().getData(); + AccountInfo sellerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(sellerAccountName).get(0).getState().getData(); + + //Generate new keys for buyers and sellers + AnonymousParty buyerAccount = subFlow(new RequestKeyForAccount(buyerAccountInfo)); + AnonymousParty sellerAccount = subFlow(new RequestKeyForAccount(sellerAccountInfo)); + + + //Part1 : Move non fungible token - ticket from seller to buyer + ///All of the Tickets Seller has + QueryCriteria queryCriteriaForSellerTicketType = new QueryCriteria.VaultQueryCriteria() + .withExternalIds(Arrays.asList(sellerAccountInfo.getIdentifier().getId())) + .withStatus(Vault.StateStatus.UNCONSUMED); + List> allNonfungibleTokens = getServiceHub().getVaultService() + .queryBy(NonFungibleToken.class, queryCriteriaForSellerTicketType).getStates(); + + //Retrieve the one that he wants to sell + StateAndRef matchedNonFungibleToken; + + if(allNonfungibleTokens.stream() + .filter(i-> i.getState().getData().getTokenType().getTokenIdentifier().equals(tokenId)) + .findAny().isPresent()) { + matchedNonFungibleToken = allNonfungibleTokens.stream() + .filter(i-> i.getState().getData().getTokenType().getTokenIdentifier().equals(tokenId)) + .findAny().get(); + } + else + throw new FlowException(sellerAccountName + "does not own ticket with token id - " + tokenId); + + String ticketId = matchedNonFungibleToken.getState().getData().getTokenType().getTokenIdentifier(); + + //construct the query criteria and get the base token type + QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria().withUuid(Arrays.asList(UUID.fromString(ticketId))). + withStatus(Vault.StateStatus.UNCONSUMED); + + // grab the ticket off the ledger + StateAndRef stateAndRef = getServiceHub().getVaultService(). + queryBy(T20CricketTicket.class, queryCriteria).getStates().get(0); + + T20CricketTicket evolvableTokenType = stateAndRef.getState().getData(); + + //get the pointer pointer to the T20CricketTicket + TokenPointer tokenPointer = evolvableTokenType.toPointer(evolvableTokenType.getClass()); + + // Obtain a reference to a notary we wish to use. + Party notary = stateAndRef.getState().getNotary(); + + //create a transactionBuilder + TransactionBuilder transactionBuilder = new TransactionBuilder(notary); + + //first part of DVP is to transfer the non fungible token from seller to buyer + //this add inputs and outputs to transactionBuilder + MoveTokensUtilitiesKt.addMoveNonFungibleTokens(transactionBuilder, getServiceHub(), tokenPointer, buyerAccount); + + //Part2 : Move fungible token - cash from buyer to seller + + QueryCriteria queryCriteriaForTokenBalance = QueryUtilitiesKt.heldTokenAmountCriteria(this.getInstance(currency), buyerAccount).and(QueryUtilitiesKt.sumTokenCriteria()); + + List sum = getServiceHub().getVaultService(). + queryBy(FungibleToken.class, queryCriteriaForTokenBalance).component5(); + + if(sum.size() == 0) + throw new FlowException(buyerAccountName + "has 0 token balance. Please ask the Bank to issue some cash."); + else { + Long tokenBalance = (Long) sum.get(0); + if(tokenBalance < costOfTicket) + throw new FlowException("Available token balance of " + buyerAccountName+ " is less than the cost of the ticket. Please ask the Bank to issue some cash if you wish to buy the ticket "); + } + + Amount amount = new Amount(costOfTicket, FiatCurrency.Companion.getInstance(currency)); + + //move money to sellerAccountInfo account. + PartyAndAmount partyAndAmount = new PartyAndAmount(sellerAccount, amount); + + //construct the query criteria and get all available unconsumed fungible tokens which belong to buyers account + QueryCriteria criteria = new QueryCriteria.VaultQueryCriteria().withStatus(Vault.StateStatus.UNCONSUMED). + withExternalIds(Arrays.asList(buyerAccountInfo.getIdentifier().getId())); + + //call utility function to move the fungible token from buyer to seller account + //this also adds inputs and outputs to the transactionBuilder + //till now we have only 1 transaction with 2 inputs and 2 outputs - one moving fungible tokens other moving non fungible tokens between accounts + MoveTokensUtilitiesKt.addMoveFungibleTokens(transactionBuilder, getServiceHub(), Arrays.asList(partyAndAmount), buyerAccount, criteria); + + //self sign the transaction. note : the host party will first self sign the transaction. + SignedTransaction selfSignedTransaction = getServiceHub().signInitialTransaction(transactionBuilder, + Arrays.asList(getOurIdentity().getOwningKey())); + + //establish sessions with buyer and seller. to establish session get the host name from accountinfo object + FlowSession customerSession = initiateFlow(buyerAccountInfo.getHost()); + + FlowSession dealerSession = initiateFlow(sellerAccountInfo.getHost()); + + //Note: though buyer and seller are on the same node still we will have to call CollectSignaturesFlow as the signer is not a Party but an account. + SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(selfSignedTransaction, + Arrays.asList(customerSession, dealerSession))); + + //call ObserverAwareFinalityFlow for finality + SignedTransaction stx = subFlow(new ObserverAwareFinalityFlow(fullySignedTx, Arrays.asList(customerSession, dealerSession))); + + return ("The ticket is sold to "+buyerAccountName+""+ "\ntxID: " + stx.getId().toString()); + } + + public TokenType getInstance(String currencyCode) { + Currency currency = Currency.getInstance(currencyCode); + return new TokenType(currency.getCurrencyCode(), 0); + } +} + +@InitiatedBy(DVPAccountsOnSameNode.class) +class DVPAccountsOnSameNodeResponder extends FlowLogic { + + private final FlowSession otherSide; + + public DVPAccountsOnSameNodeResponder(FlowSession otherSide) { + this.otherSide = otherSide; + } + + @Override + @Suspendable + public Void call() throws FlowException { + + subFlow(new SignTransactionFlow(otherSide) { + @Override + protected void checkTransaction(@NotNull SignedTransaction stx) throws FlowException { + // Custom Logic to validate transaction. + } + }); + + subFlow(new ReceiveFinalityFlow(otherSide)); + + return null; + } +} \ No newline at end of file diff --git a/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/GetAllAccounts.java b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/GetAllAccounts.java new file mode 100644 index 00000000..6df38a8e --- /dev/null +++ b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/GetAllAccounts.java @@ -0,0 +1,37 @@ +package com.t20worldcup.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.UtilitiesKt; +import com.r3.corda.lib.accounts.workflows.services.AccountService; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.InitiatingFlow; +import net.corda.core.flows.StartableByRPC; + +import java.util.ArrayList; +import java.util.List; + +@InitiatingFlow +@StartableByRPC +public class GetAllAccounts extends FlowLogic> { + + @Override + @Suspendable + public List call() throws FlowException { + AccountService accountService = UtilitiesKt.getAccountService(this); + + List> stateAndRefList = accountService.allAccounts(); + + List accountInfoList = new ArrayList<>(); + + + for(StateAndRef stateAndRef : stateAndRefList) { + accountInfoList.add(stateAndRef.getState().getData()); + } + + return accountInfoList; + + } +} diff --git a/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/IssueCashFlow.java b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/IssueCashFlow.java new file mode 100644 index 00000000..1815ca6e --- /dev/null +++ b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/IssueCashFlow.java @@ -0,0 +1,71 @@ +package com.t20worldcup.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.UtilitiesKt; +import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; +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.workflows.flows.rpc.IssueTokens; +import net.corda.core.contracts.Amount; +import net.corda.core.flows.*; +import net.corda.core.identity.AnonymousParty; +import net.corda.core.transactions.SignedTransaction; + +import java.util.Arrays; +import java.util.Currency; + +/** + * This should be run by the bank node. The Bank node issues Cash (Fungible Tokens) to the buyers who want to buy he Ipl ticket. + */ + +@StartableByRPC +@InitiatingFlow +public class IssueCashFlow extends FlowLogic { + + private final String accountName; + private final String currency; + private final Long amount; + + public IssueCashFlow(String accountName, String currency, Long amount) { + this.accountName = accountName; + this.currency = currency; + this.amount = amount; + } + + @Override + @Suspendable + public String call() throws FlowException { + + //Dealer node has already shared accountinfo with the bank when we ran the CreateAndShareAccountFlow. So this bank node will + //have access to AccountInfo of the buyer. Retrieve it using the AccountService. AccountService has certain helper methods, take a look at them. + AccountInfo accountInfo = UtilitiesKt.getAccountService(this).accountInfo(accountName).get(0).getState().getData(); + + //To transact with any account, we have to request for a Key from the node hosting the account. For this we use RequestKeyForAccount inbuilt flow. + //This will return a Public key wrapped in an AnonymousParty class. + AnonymousParty anonymousParty = subFlow(new RequestKeyForAccount(accountInfo)); + + //Get the base token type for issuing fungible tokens + TokenType token = getInstance(currency); + + //issuer will be the bank. Keep in mind the issuer will always be an known legal Party class and not an AnonymousParty. This is by design + IssuedTokenType issuedTokenType = new IssuedTokenType(getOurIdentity(), token); + + //Create a fungible token for issuing cash to account + FungibleToken fungibleToken = new FungibleToken(new Amount(this.amount, issuedTokenType), anonymousParty, null); + + //Issue fungible tokens to specified account + SignedTransaction stx =subFlow(new IssueTokens(Arrays.asList(fungibleToken))); + + return "Issued "+amount+" "+currency+" token(s) to "+accountName+"" + + "\ntxId: "+stx.getId().toString()+""; + } + + public TokenType getInstance(String currencyCode) { + Currency currency = Currency.getInstance(currencyCode); + return new TokenType(currency.getCurrencyCode(), 0); + } +} + + diff --git a/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/IssueNonFungibleTicketFlow.java b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/IssueNonFungibleTicketFlow.java new file mode 100644 index 00000000..a361f276 --- /dev/null +++ b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/IssueNonFungibleTicketFlow.java @@ -0,0 +1,84 @@ +package com.t20worldcup.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.t20worldcup.states.T20CricketTicket; +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.UtilitiesKt; +import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; +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.IssueTokens; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.InitiatingFlow; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.identity.AnonymousParty; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; + +import java.util.Arrays; +import java.util.UUID; + +/** + * This will be run by the BCCI node and it will issue a nonfungible token represnting each ticket to the dealer account. + * Buyers can then buy tickets from the dealer account. + */ +@StartableByRPC +@InitiatingFlow +public class IssueNonFungibleTicketFlow extends FlowLogic { + + private final String tokenId; + //private final int quantity;//TODO handle quantity later + private final String dealerAccountName; + + public IssueNonFungibleTicketFlow(String tokenId, String dealerAccountName) { + this.tokenId = tokenId; + // this.quantity = quantity; + this.dealerAccountName = dealerAccountName; + } + + + @Override + @Suspendable + public String call() throws FlowException { + + //Since dealer has already shared the dealer account with BCCI, BCCI will retrieve this accountinfo from the vault. + AccountInfo dealerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(dealerAccountName).get(0).getState().getData(); + + //Generate the key pair for dealer account so taht BCCI node will be able to transact with issue a token to the dealer account. + AnonymousParty dealerAccount = subFlow(new RequestKeyForAccount(dealerAccountInfo)); + + UUID uuid = UUID.fromString(tokenId); + + //construct the query criteria and get the base token type + QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria(null, Arrays.asList(uuid), null, + Vault.StateStatus.UNCONSUMED, null); + + //grab the created ticket type off the ledger + StateAndRef stateAndRef = getServiceHub().getVaultService(). + queryBy(T20CricketTicket.class, queryCriteria).getStates().get(0); + + + T20CricketTicket evolvableTokenType = stateAndRef.getState().getData(); + + //get the pointer pointer to the T20CricketTicket + TokenPointer tokenPointer = evolvableTokenType.toPointer(evolvableTokenType.getClass()); + + //assign the issuer to the T20CricketTicket type who is the BCCI node + IssuedTokenType issuedTokenType = new IssuedTokenType(getOurIdentity(), tokenPointer); + + //mention the current holder which is now going to be the dealer account + NonFungibleToken nonFungibleToken = new NonFungibleToken(issuedTokenType, dealerAccount, new UniqueIdentifier(), + TransactionUtilitiesKt.getAttachmentIdForGenericParam(tokenPointer)); + + //call built in flow to issue non fungible tokens + SignedTransaction stx = subFlow(new IssueTokens(Arrays.asList(nonFungibleToken))); + return "Issued the ticket of "+evolvableTokenType.getTicketTeam()+" to "+dealerAccountName+"" + + "\ntxId: "+stx.getId().toString()+""; + } +} diff --git a/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/MoveTokensBetweenAccounts.java b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/MoveTokensBetweenAccounts.java new file mode 100644 index 00000000..c2f0ffb2 --- /dev/null +++ b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/MoveTokensBetweenAccounts.java @@ -0,0 +1,143 @@ +package com.t20worldcup.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.UtilitiesKt; +import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; +import com.r3.corda.lib.tokens.contracts.states.FungibleToken; +import com.r3.corda.lib.tokens.contracts.types.TokenType; +import com.r3.corda.lib.tokens.selection.TokenQueryBy; +import com.r3.corda.lib.tokens.selection.database.config.DatabaseSelectionConfigKt; +import com.r3.corda.lib.tokens.selection.database.selector.DatabaseTokenSelection; +import com.r3.corda.lib.tokens.workflows.flows.move.MoveTokensUtilitiesKt; +import com.r3.corda.lib.tokens.workflows.utilities.QueryUtilitiesKt; +import kotlin.Pair; +import net.corda.core.contracts.Amount; +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.CommandWithParties; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.flows.*; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.AnonymousParty; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; + +import java.security.PublicKey; +import java.util.*; + +/** + * This flow only talks about moving fungible tokens from one account to other. + */ +@StartableByRPC +@InitiatingFlow +public class MoveTokensBetweenAccounts extends FlowLogic { + + private final String buyerAccountName; + private final String sellerAccountName; + private final String currency; + private final Long costOfTicket; + + public MoveTokensBetweenAccounts(String buyerAccountName, String sellerAccountName, String currency, Long costOfTicket) { + this.buyerAccountName = buyerAccountName; + this.sellerAccountName = sellerAccountName; + this.currency = currency; + this.costOfTicket = costOfTicket; + } + + @Override + @Suspendable + public String call() throws FlowException { + + //Get buyers and sellers account infos + AccountInfo buyerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(buyerAccountName).get(0).getState().getData(); + AccountInfo sellerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(sellerAccountName).get(0).getState().getData(); + + //Generate new keys for buyers and sellers + AnonymousParty buyerAccount = subFlow(new RequestKeyForAccount(buyerAccountInfo));//mappng saved locally + AnonymousParty sellerAccount = subFlow(new RequestKeyForAccount(sellerAccountInfo));//mappiing requested from counterparty. does the counterparty save i dont think so + + //buyer will create generate a move tokens state and send this state with new holder(seller) to seller + Amount amount = new Amount(costOfTicket, getInstance(currency)); + + //Buyer Query for token balance. + QueryCriteria queryCriteria = QueryUtilitiesKt.heldTokenAmountCriteria(this.getInstance(currency), buyerAccount).and(QueryUtilitiesKt.sumTokenCriteria()); + List sum = getServiceHub().getVaultService().queryBy(FungibleToken.class, queryCriteria).component5(); + if(sum.size() == 0) + throw new FlowException(buyerAccountName + " has 0 token balance. Please ask the Bank to issue some cash."); + else { + Long tokenBalance = (Long) sum.get(0); + if(tokenBalance < costOfTicket) + throw new FlowException("Available token balance of " + buyerAccountName+ " is less than the cost of the ticket. Please ask the Bank to issue some cash if you wish to buy the ticket "); + } + + //the tokens to move to new account which is the seller account + Pair> partyAndAmount = new Pair(sellerAccount, amount); + + //let's use the DatabaseTokenSelection to get the tokens from the db + DatabaseTokenSelection tokenSelection = new DatabaseTokenSelection( + getServiceHub(), + DatabaseSelectionConfigKt.MAX_RETRIES_DEFAULT, + DatabaseSelectionConfigKt.RETRY_SLEEP_DEFAULT, + DatabaseSelectionConfigKt.RETRY_CAP_DEFAULT, + DatabaseSelectionConfigKt.PAGE_SIZE_DEFAULT + ); + + //call generateMove which gives us 2 stateandrefs with tokens having new owner as seller. + Pair>, List> inputsAndOutputs = + tokenSelection.generateMove(Arrays.asList(partyAndAmount), buyerAccount, new TokenQueryBy(), getRunId().getUuid()); + + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + TransactionBuilder transactionBuilder = new TransactionBuilder(notary); + + MoveTokensUtilitiesKt.addMoveTokens(transactionBuilder, inputsAndOutputs.getFirst(), inputsAndOutputs.getSecond()); + + Set mySigners = new HashSet<>(); + + List> commandWithPartiesList = transactionBuilder.toLedgerTransaction(getServiceHub()).getCommands(); + + for(CommandWithParties commandDataCommandWithParties : commandWithPartiesList) { + if(((ArrayList)(getServiceHub().getKeyManagementService().filterMyKeys(commandDataCommandWithParties.getSigners()))).size() > 0) { + mySigners.add(((ArrayList)getServiceHub().getKeyManagementService().filterMyKeys(commandDataCommandWithParties.getSigners())).get(0)); + } + } + + FlowSession sellerSession = initiateFlow(sellerAccountInfo.getHost()); + + //sign the transaction with the signers we got by calling filterMyKeys + SignedTransaction selfSignedTransaction = getServiceHub().signInitialTransaction(transactionBuilder, mySigners); + + //call FinalityFlow for finality + subFlow(new FinalityFlow(selfSignedTransaction, Arrays.asList(sellerSession))); + + return null; + } + + public TokenType getInstance(String currencyCode) { + Currency currency = Currency.getInstance(currencyCode); + return new TokenType(currency.getCurrencyCode(), 0); + } +} + +@InitiatedBy(MoveTokensBetweenAccounts.class) +class MoveTokensBetweenAccountsResponder extends FlowLogic { + + private final FlowSession otherSide; + + public MoveTokensBetweenAccountsResponder(FlowSession otherSide) { + this.otherSide = otherSide; + } + + @Override + @Suspendable + public Void call() throws FlowException { + + subFlow(new ReceiveFinalityFlow(otherSide)); + + return null; + } +} \ No newline at end of file diff --git a/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/QuerybyAccount.java b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/QuerybyAccount.java new file mode 100644 index 00000000..39ccee75 --- /dev/null +++ b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/QuerybyAccount.java @@ -0,0 +1,78 @@ +package com.t20worldcup.flows; +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.UtilitiesKt; +import com.r3.corda.lib.tokens.contracts.states.FungibleToken; +import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken; +import com.r3.corda.lib.tokens.contracts.types.TokenPointer; +import com.t20worldcup.states.T20CricketTicket; +import net.corda.core.contracts.LinearPointer; +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 java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +/** + * This will be run by the BCCI node and it will issue a nonfungible token represnting each ticket to the dealer account. + * Buyers can then buy tickets from the dealer account. + */ +@StartableByRPC +public class QuerybyAccount extends FlowLogic { + private final String whoAmI; + public QuerybyAccount(String whoAmI) { + this.whoAmI = whoAmI; + } + @Override + @Suspendable + public String call() throws FlowException { + AccountInfo myAccount = UtilitiesKt.getAccountService(this).accountInfo(whoAmI).get(0).getState().getData(); + UUID id = myAccount.getIdentifier().getId(); + QueryCriteria.VaultQueryCriteria criteria = new QueryCriteria.VaultQueryCriteria().withExternalIds(Arrays.asList(id)); + + //Ticket + List> ticketList = getServiceHub().getVaultService().queryBy(NonFungibleToken.class,criteria).getStates(); + List myTicketIDs = ticketList.stream().map( it ->it.getState().getData().getTokenType().getTokenIdentifier()).collect(Collectors.toList()); + List tkList = myTicketIDs.stream().map( it -> { + UUID uuid = UUID.fromString(it); + QueryCriteria.LinearStateQueryCriteria queryCriteria = + new QueryCriteria.LinearStateQueryCriteria(null,Arrays.asList(uuid),null, Vault.StateStatus.UNCONSUMED); + StateAndRef stateAndRef = getServiceHub().getVaultService().queryBy(T20CricketTicket.class,queryCriteria).getStates().get(0); + String description = stateAndRef.getState().getData().getTicketTeam(); + return description; + }).collect(Collectors.toList()); + + //Assets + List> asset = getServiceHub().getVaultService().queryBy(FungibleToken.class,criteria).getStates(); + List myMoney = asset.stream().map(it -> { + String money = ""; + money = money+it.getState().getData().getAmount().getQuantity() + " " + it.getState().getData().getTokenType().getTokenIdentifier(); + return money; + }).collect(Collectors.toList()); + + String tickets = "\nI have ticket(s) for: "; + if(ticketList.size() == 0) { + tickets = "I do not own any ticket"; + } else { + for(String item : tkList){ + tickets = tickets + item; + } + } + + + String wallets = "\nI have money of: "; + if(myMoney.size() == 0) { + wallets = "I do not have any money"; + } else { + for(String item: myMoney){ + wallets = wallets + item; + } + } + + return tickets + wallets; + } +} \ No newline at end of file diff --git a/Accounts/worldcupticketbooking/workflows/src/test/java/com/t20worldcup/ContractTests.java b/Accounts/worldcupticketbooking/workflows/src/test/java/com/t20worldcup/ContractTests.java new file mode 100644 index 00000000..90a8a2ff --- /dev/null +++ b/Accounts/worldcupticketbooking/workflows/src/test/java/com/t20worldcup/ContractTests.java @@ -0,0 +1,13 @@ +package com.t20worldcup; + +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/Advanced/README.md b/Advanced/README.md index d8963e99..f5e9271c 100644 --- a/Advanced/README.md +++ b/Advanced/README.md @@ -9,6 +9,8 @@ and [OwnableState](https://docs.corda.net/docs/corda-os/api-states.html#ownables Corda

+### [Due Diligence Cordapp](./duediligence-cordapp): +An capital market themed app that depicts an ideal shareable due diligence process between banks. It uses advance feature attachment functionality to whitelist the trusted auditors. ### [Negotiation Cordapp](./negotiation-cordapp): An application that depicts the businsess negotiation and communication process over a distributed ledger system. @@ -21,6 +23,10 @@ A simple i-owe-you application illustrates all of the steps of creating an oblig This is an imlementation of Secret Santa using Corda as a tool to store multiple game states.It has a material-ui frontend that lets users create and self-service their own secret santa games. The frontend is implemented in ReactJS and the backend is implemented with a Spring Boot server and some corda flows. It is also equipped with an external emailing package(sendgrid), which you can utilze and turn the app into a live app and send the secret santa assignments to your friends' emails. +

+ Corda +

+ ### [Snake and Ladder Game Cordapp](./snakesandladders-cordapp): This sample implements a simple Snakes And Ladder Game on Corda. This cordapp demonstrate the use of multiple features, including Corda Account Library and Oracle service.

diff --git a/Advanced/auction-cordapp/build.gradle b/Advanced/auction-cordapp/build.gradle index a78a991d..4d87e6dc 100644 --- a/Advanced/auction-cordapp/build.gradle +++ b/Advanced/auction-cordapp/build.gradle @@ -20,8 +20,9 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda' } + + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -37,9 +38,10 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://download.corda.net/maven/corda-releases' } maven { url 'https://jitpack.io' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } @@ -87,6 +89,9 @@ dependencies { 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" + + cordaDriver "net.corda:corda-shell:4.10" + } task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { diff --git a/Advanced/auction-cordapp/client/build.gradle b/Advanced/auction-cordapp/client/build.gradle index e9184696..e94a0e39 100644 --- a/Advanced/auction-cordapp/client/build.gradle +++ b/Advanced/auction-cordapp/client/build.gradle @@ -1,5 +1,11 @@ apply plugin: 'org.springframework.boot' +configurations { + all { + exclude group: 'ch.qos.logback', module: 'logback-classic' + } +} + dependencies { // Corda dependencies. compile "$corda_release_group:corda-rpc:$corda_release_version" diff --git a/Advanced/auction-cordapp/contracts/build.gradle b/Advanced/auction-cordapp/contracts/build.gradle index 6d9813f8..d35b4df3 100644 --- a/Advanced/auction-cordapp/contracts/build.gradle +++ b/Advanced/auction-cordapp/contracts/build.gradle @@ -23,7 +23,7 @@ dependencies { // Corda dependencies. cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" - compileOnly "$corda_release_group:corda-testserver-impl:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" cordaRuntime "$corda_release_group:corda-testserver:$corda_release_version" diff --git a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/AuctionDvPFlow.java b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/AuctionDvPFlow.java index da3b9a06..e4321c63 100644 --- a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/AuctionDvPFlow.java +++ b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/AuctionDvPFlow.java @@ -79,13 +79,8 @@ public SignedTransaction call() throws FlowException { .withNewOwner(auctionState.getWinner()); // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = auctionStateAndRef.getState().getNotary(); // Create the transaction builder. TransactionBuilder transactionBuilder = new TransactionBuilder(notary); diff --git a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/CreateAssetFlow.java b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/CreateAssetFlow.java index 2d66e502..dc95df45 100644 --- a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/CreateAssetFlow.java +++ b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/CreateAssetFlow.java @@ -8,6 +8,7 @@ import net.corda.core.transactions.TransactionBuilder; import net.corda.samples.auction.contracts.AssetContract; import net.corda.samples.auction.states.Asset; +import net.corda.core.identity.CordaX500Name; import java.util.Arrays; import java.util.Collections; @@ -42,13 +43,8 @@ public CreateAssetFlow(String title, String description, String imageURL) { @Suspendable public SignedTransaction call() throws FlowException { // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // Create the output states Asset output = new Asset(new UniqueIdentifier(), title, description, imageURL, diff --git a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/CreateAuctionFlow.java b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/CreateAuctionFlow.java index e78675c0..b9b1ccd8 100644 --- a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/CreateAuctionFlow.java +++ b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/CreateAuctionFlow.java @@ -7,12 +7,14 @@ import net.corda.core.contracts.LinearState; import net.corda.core.contracts.UniqueIdentifier; import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; import net.corda.core.utilities.ProgressTracker; import net.corda.samples.auction.contracts.AuctionContract; import net.corda.samples.auction.states.AuctionState; +import net.corda.core.identity.CordaX500Name; import java.time.LocalDateTime; import java.time.ZoneId; @@ -56,13 +58,8 @@ public ProgressTracker getProgressTracker() { @Override public SignedTransaction call() throws FlowException { // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); Party auctioneer = getOurIdentity(); diff --git a/Advanced/auction-cordapp/workflows/src/test/java/net/corda/samples/auction/FlowTests.java b/Advanced/auction-cordapp/workflows/src/test/java/net/corda/samples/auction/FlowTests.java index 50814f39..58b2a115 100644 --- a/Advanced/auction-cordapp/workflows/src/test/java/net/corda/samples/auction/FlowTests.java +++ b/Advanced/auction-cordapp/workflows/src/test/java/net/corda/samples/auction/FlowTests.java @@ -3,16 +3,14 @@ import com.google.common.collect.ImmutableList; import net.corda.core.concurrent.CordaFuture; import net.corda.core.contracts.Amount; +import net.corda.core.identity.CordaX500Name; import net.corda.core.node.NetworkParameters; import net.corda.core.transactions.SignedTransaction; import net.corda.samples.auction.flows.CreateAssetFlow; import net.corda.samples.auction.flows.CreateAuctionFlow; import net.corda.samples.auction.states.Asset; import net.corda.samples.auction.states.AuctionState; -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 net.corda.testing.node.*; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -39,7 +37,8 @@ public void setup() { ) ).withNetworkParameters(new NetworkParameters(4, Collections.emptyList(), 10485760, 10485760 * 50, Instant.now(), 1, - Collections.emptyMap())) + Collections.emptyMap()) + ).withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) ); a = network.createPartyNode(null); b = network.createPartyNode(null); diff --git a/Advanced/constants.properties b/Advanced/constants.properties index 2cd5a28c..adcdb361 100644 --- a/Advanced/constants.properties +++ b/Advanced/constants.properties @@ -1,12 +1,12 @@ cordaReleaseGroup=net.corda cordaCoreReleaseGroup=net.corda -cordaVersion=4.6 -cordaCoreVersion=4.6 +cordaVersion=4.10 +cordaCoreVersion=4.10 gradlePluginsVersion=5.0.12 kotlinVersion=1.2.71 junitVersion=4.12 quasarVersion=0.7.10 -log4jVersion =2.11.2 -platformVersion=8 +log4jVersion =2.17.1 +platformVersion=12 slf4jVersion=1.7.25 nettyVersion=4.1.22.Final diff --git a/Advanced/duediligence-cordapp/LICENCE b/Advanced/duediligence-cordapp/LICENCE new file mode 100644 index 00000000..3ff572d1 --- /dev/null +++ b/Advanced/duediligence-cordapp/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/Advanced/duediligence-cordapp/README.md b/Advanced/duediligence-cordapp/README.md new file mode 100644 index 00000000..7dbbf0cb --- /dev/null +++ b/Advanced/duediligence-cordapp/README.md @@ -0,0 +1,66 @@ +# Due Diligence Cordapp + +This cordapp is an example of how blockchain can work in the capital market industry. Due diligence is commonly the first step of any action in the capital market. In many of the industry, it is also required by the regulators to prevent fraudulent and risky transactions. Each firm will have its own due-diligence process and standards, but at the same time, the cost of executing due diligence is also bared by them individually. + +## App Design + + +The above picture is a high level mock overview of a shareable due diligence DLT app. BankA will initiate the original Corporate Records auditing with an autitor. Then it will share the auditiing report with BankB to save BankB's cost on getting the same report. Wise versa, BankB can work with a different auditor and produce a different report and share with BankA. In the implmentation of this sample cordapp, we will only cover one type of the file auditing. + +Notes: another key feature of this app is whitlisting trusted auditors. It is done by utilizing attachment function in Corda. More samples on how to use attachment can be found in the [Features samples folder](../../Features) + + +## Pre-running the app + +Deploying nodes: `./gradlew clean deployNodes` + +Starting the nodes: `./build/nodes/runnodes` + +Uploading whitelisted Auditors: `./gradlew uploadWhitelists` + + + +## Running the Cordapp +Step #1: At PartyA, file the original Corporate Records auditing process with Auditor(Trusted Auditor) +``` +flow start RequestToValidateCorporateRecordsInitiator validater: Trusted Auditor, numberOfFiles: 10 +``` + +Step #2: Go to the Trusted Auditor Node, validate the auditing request(This step symbolize the auditing process by this third party auditor). Put in the linearId which was returned in Step #1. +``` +flow start ValidateCorporateRecordsInitiator linearId: +``` + +Step #3: Go to PartyA, Do a query to confirm that the request has been validated. You should see the variable `qualification=true`. + +``` +run vaultQuery contractStateType: net.corda.samples.duediligence.states.CorporateRecordsAuditRequest +``` +Then, we will instruct PartyA to share a copy of the auditing result with PartyB: (Again, You would need put in the linearId returned from Step #1). The parameter `trustedAuditorAttachment` is a jar file which records the trusted auditors. If PartyA used an untrusted auditor to accquire the corporate records auditing report. He will be prohibited to share with anyone because it is valueless effort(in this business use case, Of course you can modify the business use cases). +``` +flow start ShareAuditingResultInitiator AuditingResultID: , sendTo: BankB, trustedAuditorAttachment: "8DF3275D80B26B9A45AB022F2FDA4A2ED996449B425F8F2245FA5BCF7D1AC587" +``` +This flow will return the LinearId of the copy of auditing report, you would need this in Step #6. + +Step #4: Go to PartyB, do a query to confirm the delievery of copy of the Auditing Report. +``` +run vaultQuery contractStateType: net.corda.samples.duediligence.states.CopyOfCoporateRecordsAuditRequest +``` +As of now, the sharing of the trusted auditing report is done. What left now for both PartyA and PartyB in this use case is to upload the Corporate Records auditing report into a due-diligence list, which they can share with a regulator.(You can again alter this step to suit any other use cases). + + +Step #5: Go to PartyA, Attach the Corporate Records auditing report into a due-diligence checklist and report to the Regulator. Again, the approvalId is the linearId returned in Step #1. +``` +flow start CreateCheckListAndAddApprovalInitiator reportTo: Regulator, approvalId: + +``` +Step #6: Go to PartyB, Attach the copy of the Corporate Records auditing report into a due-diligence checklist and report to the Regulator. You would need the linearId that is return from Step #5 +``` +flow start CreateCheckListAndAddApprovalInitiator reportTo: Regulator, approvalId: +``` +Step #7: Go to Regulator, do a query on reported due-diligence checklists. You will be able to see both PartyA and PartyB had filed a due-diligence checklist. +``` +run vaultQuery contractStateType: net.corda.samples.duediligence.states.DueDChecklist + +``` +This use of Distributed technology Corda helped PartyB saved the cost of go through corporate records due-diligence process while still reaching a trusted auditing report. diff --git a/Advanced/duediligence-cordapp/TRADEMARK b/Advanced/duediligence-cordapp/TRADEMARK new file mode 100644 index 00000000..d2e056b5 --- /dev/null +++ b/Advanced/duediligence-cordapp/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/Advanced/duediligence-cordapp/build.gradle b/Advanced/duediligence-cordapp/build.gradle new file mode 100644 index 00000000..ef682822 --- /dev/null +++ b/Advanced/duediligence-cordapp/build.gradle @@ -0,0 +1,154 @@ +buildscript {//properties that you need to build the project + Properties constants = new Properties() + file("$projectDir/../constants.properties").withInputStream { constants.load(it) } + + ext { + corda_release_group = constants.getProperty("cordaReleaseGroup") + corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") + corda_release_version = constants.getProperty("cordaVersion") + corda_core_release_version = constants.getProperty("cordaCoreVersion") + 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' + } + + repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://download.corda.net/maven/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 {//Properties that you need to compile your project (The application) + apply from: "${rootProject.projectDir}/repositories.gradle" + apply plugin: 'java' + + repositories { + mavenLocal() + + mavenCentral() + maven { url 'https://download.corda.net/maven/corda-dependencies' } + 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 + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} + +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") + } + } +} +//Module dependencis +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_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") + + 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" + cordaDriver "net.corda:corda-shell:4.10" + +} + + +//Task to deploy the nodes in order to bootstrap a network +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + + /* This property will load the CorDapps to each of the node by default, including the Notary. You can find them + * in the cordapps folder of the node at build/nodes/Notary/cordapps. However, the notary doesn't really understand + * the notion of cordapps. In production, Notary does not need cordapps as well. This is just a short cut to load + * the Corda network bootstrapper. + */ + nodeDefaults { + projectCordapp { + deploy = false + } + cordapp project(':contracts') + cordapp project(':workflows') + runSchemaMigration = true //This configuration is for any CorDapps with custom schema, We will leave this as true to avoid + //problems for developers who are not familiar with Corda. If you are not using custom schemas, you can change + //it to false for quicker project compiling time. + } + node { + name "O=Notary,L=London,C=GB" + notary = [validating : false] + p2pPort 10002 + rpcSettings { + address("localhost:10003") + adminAddress("localhost:10043") + } + } + node { + name "O=BankA,L=London,C=GB" + p2pPort 10005 + rpcSettings { + address("localhost:10006") + adminAddress("localhost:10046") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=BankB,L=New York,C=US" + p2pPort 10008 + rpcSettings { + address("localhost:10009") + adminAddress("localhost:10049") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=Trusted Auditor,L=New York,C=US" + p2pPort 10011 + rpcSettings { + address("localhost:10012") + adminAddress("localhost:10052") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=Regulator,L=New York,C=US" + p2pPort 10014 + rpcSettings { + address("localhost:10015") + adminAddress("localhost:10055") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } +} diff --git a/Advanced/duediligence-cordapp/clients/build.gradle b/Advanced/duediligence-cordapp/clients/build.gradle new file mode 100644 index 00000000..0585ba30 --- /dev/null +++ b/Advanced/duediligence-cordapp/clients/build.gradle @@ -0,0 +1,37 @@ +apply plugin: 'org.springframework.boot' + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } +} + +configurations { + all { + exclude group: 'ch.qos.logback', module: 'logback-classic' + } +} + +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" +} + + +task uploadWhitelists(type: JavaExec, dependsOn: assemble) { + classpath = sourceSets.main.runtimeClasspath + main = 'net.corda.samples.duediligence.client.Client' + args 'localhost:10006', 'localhost:10009', 'localhost:10012' +} \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/clients/src/main/java/net/corda/samples/duediligence/client/Client.java b/Advanced/duediligence-cordapp/clients/src/main/java/net/corda/samples/duediligence/client/Client.java new file mode 100644 index 00000000..0e5fad53 --- /dev/null +++ b/Advanced/duediligence-cordapp/clients/src/main/java/net/corda/samples/duediligence/client/Client.java @@ -0,0 +1,96 @@ +package net.corda.samples.duediligence.client; + +import com.google.common.base.Charsets; +import net.corda.client.rpc.CordaRPCClient; +import net.corda.client.rpc.CordaRPCConnection; +import net.corda.core.crypto.SecureHash; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.utilities.NetworkHostAndPort; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import net.corda.samples.duediligence.Constants; + +import java.io.*; +import java.nio.file.FileAlreadyExistsException; +import java.util.List; +import java.util.jar.JarInputStream; +import java.util.stream.Collectors; + +public class Client { + + private static class Companion { + static Logger logger = LoggerFactory.getLogger(Client.class); + } + + /** + * Uploads the jar of blacklisted counterparties with whom agreements cannot be struck to the node. + */ + public static void main(String[] args) throws IOException { + if (args.length == 0) { + String message = "Usage: uploadBlacklist ..."; + throw new IllegalArgumentException(message.toString()); + } + + for (String arg : args) { + NetworkHostAndPort nodeAddress = NetworkHostAndPort.parse(arg); + CordaRPCConnection rpcConnection = new CordaRPCClient(nodeAddress).start("user1", "test"); + CordaRPCOps proxy = rpcConnection.getProxy(); + + SecureHash attachmentHash = Constants.CORPORATE_JAR_HASH; + + // take relative path using substring of constant BLACKLIST_JAR_PATH, check if node contains blacklist already + if (!proxy.attachmentExists(attachmentHash)) { + System.out.println("Working Directory = " + + System.getProperty("user.dir")); + attachmentHash = uploadAttachment(proxy, Constants.CORPORATE_JAR_PATH); + Companion.logger.info("Corporate Auditor List uploaded to node at " + nodeAddress); + } else { + Companion.logger.info("Node already contains Corporate Auditor List, skipping upload at " + nodeAddress); + } + + JarInputStream attachmentJar = downloadAttachment(proxy, attachmentHash); + Companion.logger.info("Corporate Auditor List downloaded from node at " + nodeAddress); + + checkAttachment(attachmentJar, Constants.CORPORATE_ATTACTMENT_FILE_NAME, Constants.CORPORATE_ATTACHMENT_EXPECTED_CONTENTS); + Companion.logger.info("Attachment contents checked on node at " + nodeAddress); + } + + } + + /** + * Uploads the attachment at [attachmentPath] to the node. + */ + private static SecureHash uploadAttachment(CordaRPCOps proxy, String attachmentPath) throws FileNotFoundException, FileAlreadyExistsException { + FileInputStream attachmentUploadInputStream = new FileInputStream(new File(attachmentPath)); + return proxy.uploadAttachment(attachmentUploadInputStream); + } + + /** + * Downloads the attachment with hash [attachmentHash] from the node. + */ + private static JarInputStream downloadAttachment(CordaRPCOps proxy, SecureHash attachmentHash) throws IOException { + InputStream attachmentDownloadInputStream = proxy.openAttachment(attachmentHash); + return new JarInputStream(attachmentDownloadInputStream); + } + + /** + * Checks the [expectedFileName] and [expectedContents] of the downloaded [attachmentJar]. + */ + private static void checkAttachment(JarInputStream attachmentJar, String expectedFileName, List expectedContents) throws IOException { + String name = attachmentJar.getNextEntry().getName(); + while (!name.equals(expectedFileName)) { + name = attachmentJar.getNextEntry().getName(); + } + + BufferedInputStream bisAttachmentJar = new BufferedInputStream(attachmentJar, (8 * 1024)); + InputStreamReader isrAttachmentJar = new InputStreamReader(bisAttachmentJar, Charsets.UTF_8); + BufferedReader brAttachmentJar = new BufferedReader(isrAttachmentJar); + + List contents = brAttachmentJar.lines().collect(Collectors.toList()); + + if (!contents.equals(expectedContents)) { + throw new IllegalArgumentException("Downloaded JAR did not have the expected contents."); + } + + } +} \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/config/dev/log4j2.xml b/Advanced/duediligence-cordapp/config/dev/log4j2.xml new file mode 100644 index 00000000..34ba4d45 --- /dev/null +++ b/Advanced/duediligence-cordapp/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/Advanced/duediligence-cordapp/config/test/log4j2.xml b/Advanced/duediligence-cordapp/config/test/log4j2.xml new file mode 100644 index 00000000..cd9926ca --- /dev/null +++ b/Advanced/duediligence-cordapp/config/test/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n + > + + + + + + + + + + + + diff --git a/Advanced/duediligence-cordapp/contracts/build.gradle b/Advanced/duediligence-cordapp/contracts/build.gradle new file mode 100644 index 00000000..9d669fa4 --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + contract { + name "Due Diligence Contracts" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main{ + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + } + test{ + java{ + srcDir 'src/test/java' + java.outputDir = file('bin/test') + } + } +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_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/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/Constants.java b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/Constants.java new file mode 100644 index 00000000..24d84b8c --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/Constants.java @@ -0,0 +1,26 @@ +package net.corda.samples.duediligence; + +import net.corda.core.crypto.SecureHash; + +import java.util.Arrays; +import java.util.List; + +public interface Constants { + //Whitelisted Corporate Auditors + String CORPORATE_JAR_PATH = "../contracts/src/main/resources/corporateAuditors.jar"; + SecureHash CORPORATE_JAR_HASH = SecureHash.parse("8DF3275D80B26B9A45AB022F2FDA4A2ED996449B425F8F2245FA5BCF7D1AC587"); + String CORPORATE_ATTACTMENT_FILE_NAME = "whitelistedCorporateAuditors.txt"; + List CORPORATE_ATTACHMENT_EXPECTED_CONTENTS = Arrays.asList( + "Crossland Savings", + "Trusted Auditor" + ); + + //Whitelisted Finance Auditors + String FINANCIAL_JAR_PATH = "../contracts/src/main/resources/financialAuditors.jar"; + SecureHash FINANCIAL_JAR_HASH = SecureHash.parse("DE7635E2AD626BC57D811D065F7841FC35839FA2D0CF095857CACE1579756A1C"); + String FINANCIAL_ATTACTMENT_FILE_NAME = "whitelistedFinancialAuditors.txt"; + List FINANCIAL_ATTACHMENT_EXPECTED_CONTENTS = Arrays.asList( + "Detroit Partners Group", + "Tifton Banking Company" + ); +} diff --git a/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/contracts/CorporateRecordsContract.java b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/contracts/CorporateRecordsContract.java new file mode 100644 index 00000000..c0f892b6 --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/contracts/CorporateRecordsContract.java @@ -0,0 +1,125 @@ +package net.corda.samples.duediligence.contracts; + +import kotlin.text.Charsets; +import net.corda.core.contracts.*; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.core.transactions.LedgerTransaction; +import net.corda.samples.duediligence.states.CopyOfCoporateRecordsAuditRequest; +import net.corda.samples.duediligence.states.CorporateRecordsAuditRequest; +import org.jetbrains.annotations.NotNull; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.jar.JarInputStream; +import java.util.stream.Collectors; + +import static net.corda.core.contracts.ContractsDSL.requireThat; +import static net.corda.samples.duediligence.Constants.CORPORATE_JAR_HASH; + +public class CorporateRecordsContract implements Contract { + + public static final String CorporateRecordsContract_ID = "net.corda.samples.duediligence.contracts.CorporateRecordsContract"; + + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + final CommandWithParties command = tx.getCommands().get(0); + + //Propose request + if (command.getValue() instanceof Commands.Propose) { + requireThat(require -> { + require.using("There are no inputs", tx.getInputs().isEmpty()); + require.using("Only one output state should be created.", tx.getOutputs().size() == 1); + require.using("The single output is of type CorporateRecords State", tx.outputsOfType(CorporateRecordsAuditRequest.class).size() == 1); + return null; + }); + }else if (command.getValue() instanceof Commands.Validate) { //Validate Rules + requireThat(require -> { + require.using("Only one output state should be created.", tx.getOutputs().size() == 1); + require.using("The single output is of type CorporateRecords State", tx.outputsOfType(CorporateRecordsAuditRequest.class).size() == 1); + return null; + }); + }else if (command.getValue() instanceof Commands.Reject) { //Rejection Rules + requireThat(require -> { + require.using("Only one output state should be created.", tx.getOutputs().size() == 1); + require.using("The single output is of type CorporateRecords State", tx.outputsOfType(CorporateRecordsAuditRequest.class).size() == 1); + return null; + }); + } + else if (command.getValue() instanceof Commands.Share) { //Share Rules + + // Check if the report has any value to share among other network participants. + List nonContractAttachments = tx.getAttachments() + .stream() + .filter(p -> !(p instanceof ContractAttachment)) + .map(p -> (Attachment) p) + .collect(Collectors.toList()); + + Attachment attached = nonContractAttachments.get(0); + + ContractsDSL.requireThat(req -> { + req.using("The transaction should have a single non-contract attachment", + nonContractAttachments.size() == 1); + req.using("The jar's hash should be correct", attached.getId().equals(CORPORATE_JAR_HASH)); + return null; + }); + + // Extract the whitelisted company names from the JAR. + List whitelistedCompanies = new ArrayList<>(); + JarInputStream attachmentJar = attached.openAsJAR(); + try { + while (!attachmentJar.getNextEntry().getName().equals("whitelistedCorporateAuditors.txt")) { + // Calling 'getNextEntry()' causes us to scroll through the JAR. + } + InputStreamReader isrWhitelistlist = new InputStreamReader(attachmentJar, Charsets.UTF_8); + BufferedReader brWhitelist = new BufferedReader(isrWhitelistlist, (8 * 1024)); // Note - changed BIR to BR + + String company = brWhitelist.readLine(); + + while (company != null) { + whitelistedCompanies.add(company); + company = brWhitelist.readLine(); + } + } catch (IOException e) { + System.out.println("error reading whitelistedCorporateAuditors.txt"); + } + + // Constraints on the Whitelist parties + //CorporateRecordsAuditRequest corporateRecords = tx.outputsOfType(CorporateRecordsAuditRequest.class).get(0); + //CorporateRecordsAuditRequest corporateRecords = tx.inputsOfType(CorporateRecordsAuditRequest.class).get(0); + StateAndRef recordsStateAndRef = tx.referenceInputRefsOfType(CorporateRecordsAuditRequest.class).get(0); + CorporateRecordsAuditRequest corporateRecords = recordsStateAndRef.getState().getData(); + + List participants = new ArrayList<>(); + List participantsOrgs = new ArrayList<>(); + for (AbstractParty p : corporateRecords.getParticipants()) { + Party participant = (Party) p; + participantsOrgs.add(participant.getName().getOrganisation()); + participants.add(participant); + } + + // overlap is whether any participants in the transaction belong to a whitelisted org. + Set overlap = new HashSet<>(whitelistedCompanies); //Crossland Savings & TCF National Bank Wisconsin + overlap.retainAll(new HashSet<>(participantsOrgs)); // intersection | TCF & PartyA + + ContractsDSL.requireThat(req -> { + req.using("The agreement did not use any whitelisted auditors" + overlap.toString(), (!overlap.isEmpty())); + return null; + }); + } + } + + public interface Commands extends CommandData { + class Propose implements Commands { } + class Validate implements Commands { } + class Reject implements Commands { } + class Share implements Commands { } + class Report implements Commands { } + } + +} diff --git a/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/contracts/DueDChecklistContract.java b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/contracts/DueDChecklistContract.java new file mode 100644 index 00000000..0bc61562 --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/contracts/DueDChecklistContract.java @@ -0,0 +1,37 @@ +package net.corda.samples.duediligence.contracts; + +import net.corda.core.contracts.*; +import net.corda.core.transactions.LedgerTransaction; +import net.corda.samples.duediligence.states.CopyOfCoporateRecordsAuditRequest; +import net.corda.samples.duediligence.states.CorporateRecordsAuditRequest; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +import static net.corda.core.contracts.ContractsDSL.requireThat; + +public class DueDChecklistContract implements Contract { + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + final CommandWithParties command = tx.getCommands().get(0); + + //Propose request + if (command.getValue() instanceof DueDChecklistContract.Commands.Add) { + requireThat(require -> { + StateAndRef input = tx.getInputs().get(0); + if (input.getState().getData().getClass() == CorporateRecordsAuditRequest.class){ + CorporateRecordsAuditRequest request = (CorporateRecordsAuditRequest)input.getState().getData(); + require.using("Qualification Must be True", request.getQualification()); + }else{ + CopyOfCoporateRecordsAuditRequest request = (CopyOfCoporateRecordsAuditRequest) input.getState().getData(); + require.using("Qualification Must be True", request.getQualification()); + } + return null; + }); + } + } + + public interface Commands extends CommandData { + class Add implements Commands { } + } +} diff --git a/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/states/CopyOfCoporateRecordsAuditRequest.java b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/states/CopyOfCoporateRecordsAuditRequest.java new file mode 100644 index 00000000..cb403c35 --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/states/CopyOfCoporateRecordsAuditRequest.java @@ -0,0 +1,74 @@ +package net.corda.samples.duediligence.states; + +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.ContractState; +import net.corda.core.contracts.LinearState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.crypto.SecureHash; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.samples.duediligence.contracts.CorporateRecordsContract; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +@BelongsToContract(CorporateRecordsContract.class) +public class CopyOfCoporateRecordsAuditRequest implements LinearState { + + + //private variables + private Boolean qualification = false; + private Party originalOwner; + private Party copyReceiver; + private UniqueIdentifier originalRequestId; + private SecureHash originalReportTxId; + private UniqueIdentifier linearId; + private Party originalValidater; + + public CopyOfCoporateRecordsAuditRequest(Party originalOwner, Party copyReceiver, + UniqueIdentifier originalRequestId, SecureHash originalReportTxId, + Party originalValidater,Boolean qualification, UniqueIdentifier linearId) { + this.originalOwner = originalOwner; + this.copyReceiver = copyReceiver; + this.originalRequestId = originalRequestId; + this.originalValidater = originalValidater; + this.originalReportTxId = originalReportTxId; + this.linearId = linearId; + this.qualification = qualification; + } + + public Party getOriginalOwner() { + return originalOwner; + } + + public Party getCopyReceiver() { + return copyReceiver; + } + + public UniqueIdentifier getOriginalRequestId() { + return originalRequestId; + } + + public UniqueIdentifier getLinearId() { + return linearId; + } + + public Party getoriginalValidater() { + return originalValidater; + } + + public Boolean getQualification() { + return qualification; + } + + public SecureHash getOriginalReportTxId() { + return originalReportTxId; + } + + @NotNull + @Override + public List getParticipants() { + return Arrays.asList(copyReceiver); + } +} diff --git a/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/states/CorporateRecordsAuditRequest.java b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/states/CorporateRecordsAuditRequest.java new file mode 100644 index 00000000..d6433574 --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/states/CorporateRecordsAuditRequest.java @@ -0,0 +1,79 @@ +package net.corda.samples.duediligence.states; + +import net.corda.core.contracts.LinearState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.serialization.ConstructorForDeserialization; +import net.corda.samples.duediligence.contracts.CorporateRecordsContract; +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.Arrays; +import java.util.List; + +// ********* +// * State * +// ********* +@BelongsToContract(CorporateRecordsContract.class) +public class CorporateRecordsAuditRequest implements LinearState { + /*This can include reviewing incorporation documents, company constitutions, + organisational charts, a list of security holders, employee share plans and any + options granted to acquire securities.*/ + + //private variables + private Boolean qualification = false; + private Party applicant; + private Party validater; + private int numberOfFiles; + private UniqueIdentifier linearId; + + /* Constructor of your Corda state */ + @ConstructorForDeserialization + public CorporateRecordsAuditRequest(Party applicant, Party validater, int numberOfFiles, UniqueIdentifier linearId, Boolean qualification) { + this.applicant = applicant; + this.validater = validater; + this.numberOfFiles = numberOfFiles; + this.linearId = linearId; + this.qualification = qualification; + } + + public CorporateRecordsAuditRequest(Party applicant, Party validater, int numberOfFiles, UniqueIdentifier linearId) { + this.applicant = applicant; + this.validater = validater; + this.numberOfFiles = numberOfFiles; + this.linearId = linearId; + } + + public CorporateRecordsAuditRequest(Party applicant, Party validater, int numberOfFiles) { + this.qualification = qualification; + this.applicant = applicant; + this.validater = validater; + this.numberOfFiles = numberOfFiles; + this.linearId = new UniqueIdentifier(); + } + + //getters + public Boolean getQualification() { return qualification; } + public Party getApplicant() { return applicant; } + public Party getValidater() { return validater; } + public int getNumberOfFiles() { return numberOfFiles; } + + /* This method will indicate who are the participants and required signers when + * this state is used in a transaction. */ + @Override + public List getParticipants() { + return Arrays.asList(applicant,validater); + } + + public void validatedAndApproved (){ + this.qualification = true; + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return this.linearId; + } +} \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/states/DueDChecklist.java b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/states/DueDChecklist.java new file mode 100644 index 00000000..36cf1f54 --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/states/DueDChecklist.java @@ -0,0 +1,84 @@ +package net.corda.samples.duediligence.states; + +import net.corda.core.contracts.LinearState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.serialization.ConstructorForDeserialization; +import net.corda.samples.duediligence.contracts.DueDChecklistContract; +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +// ********* +// * State * +// ********* +@BelongsToContract(DueDChecklistContract.class) +public class DueDChecklist implements LinearState { + + //private variables + private int numberOfapprovalsNeeded; + private String status = "INCOMPLETE"; + private Party operationNode; + private Party reportTo; + private List attachedApprovals = new ArrayList(); + private UniqueIdentifier linearID; + + /* Constructor of your Corda state */ + @ConstructorForDeserialization + public DueDChecklist(int numberOfapprovalsNeeded, String status, Party operationNode, Party reportTo, List attachedApprovals, UniqueIdentifier linearID) { + this.numberOfapprovalsNeeded = numberOfapprovalsNeeded; + this.status = status; + this.operationNode = operationNode; + this.reportTo = reportTo; + this.attachedApprovals = attachedApprovals; + this.linearID = linearID; + } + + //Modification will + public DueDChecklist(int numberOfapprovalsNeeded, Party operationNode, Party reportTo, UniqueIdentifier linearID) { + this.numberOfapprovalsNeeded = numberOfapprovalsNeeded; + this.operationNode = operationNode; + this.reportTo = reportTo; + this.linearID = linearID; + } + + //getters + public int getNumberOfapprovals() { return numberOfapprovalsNeeded; } + public String getStatus() { return status; } + public Party getOperationNode() { return operationNode; } + + public int getNumberOfapprovalsNeeded() { + return numberOfapprovalsNeeded; + } + + public List getAttachedApprovals() { + return attachedApprovals; + } + + public void uploadApproval(UniqueIdentifier approvalId){ + List copyOfExistingList = new ArrayList(); + for (UniqueIdentifier id : attachedApprovals){ + copyOfExistingList.add(id); + } + copyOfExistingList.add(approvalId); + this.attachedApprovals = copyOfExistingList; + } + + + /* This method will indicate who are the participants and required signers when + * this state is used in a transaction. */ + @Override + public List getParticipants() { + return Arrays.asList(operationNode,reportTo); + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return this.linearID; + } +} \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/contracts/src/main/resources/corporateAuditors.jar b/Advanced/duediligence-cordapp/contracts/src/main/resources/corporateAuditors.jar new file mode 100644 index 00000000..b4b25be1 Binary files /dev/null and b/Advanced/duediligence-cordapp/contracts/src/main/resources/corporateAuditors.jar differ diff --git a/Advanced/duediligence-cordapp/contracts/src/main/resources/financialAuditors.jar b/Advanced/duediligence-cordapp/contracts/src/main/resources/financialAuditors.jar new file mode 100644 index 00000000..d50b1452 Binary files /dev/null and b/Advanced/duediligence-cordapp/contracts/src/main/resources/financialAuditors.jar differ diff --git a/Advanced/duediligence-cordapp/contracts/src/main/resources/whitelistedCorporateAuditors.txt b/Advanced/duediligence-cordapp/contracts/src/main/resources/whitelistedCorporateAuditors.txt new file mode 100644 index 00000000..fa5db9de --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/main/resources/whitelistedCorporateAuditors.txt @@ -0,0 +1,2 @@ +Crossland Savings +Trusted Auditor \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/contracts/src/main/resources/whitelistedFinancialAuditors.txt b/Advanced/duediligence-cordapp/contracts/src/main/resources/whitelistedFinancialAuditors.txt new file mode 100644 index 00000000..3a26bc0e --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/main/resources/whitelistedFinancialAuditors.txt @@ -0,0 +1,2 @@ +Detroit Partners Group +Tifton Banking Company \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/contracts/src/test/java/net/corda/samples/duediligence/contracts/ContractTests.java b/Advanced/duediligence-cordapp/contracts/src/test/java/net/corda/samples/duediligence/contracts/ContractTests.java new file mode 100644 index 00000000..e992d98e --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/test/java/net/corda/samples/duediligence/contracts/ContractTests.java @@ -0,0 +1,40 @@ +package net.corda.samples.duediligence.contracts; + +import net.corda.core.identity.CordaX500Name; +import net.corda.samples.duediligence.states.CorporateRecordsAuditRequest; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.node.MockServices; +import org.junit.Test; + +import java.util.Arrays; + +import static net.corda.samples.duediligence.contracts.CorporateRecordsContract.CorporateRecordsContract_ID; +import static net.corda.testing.node.NodeTestUtils.ledger; + +public class ContractTests { + private final MockServices ledgerServices = new MockServices( + Arrays.asList("net.corda.samples.duediligence.contracts") + ); + public static TestIdentity alice = new TestIdentity(new CordaX500Name("Alice", "TestLand", "US")); + public static TestIdentity bob = new TestIdentity(new CordaX500Name("Bob", "TestCity", "US")); + + @Test + public void ProposeTransactionshouldhavezeroinput() { + CorporateRecordsAuditRequest cr = new CorporateRecordsAuditRequest(alice.getParty(),bob.getParty(),10); + + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.input(CorporateRecordsContract_ID, cr); + tx.command(Arrays.asList(alice.getPublicKey(), bob.getPublicKey()), new CorporateRecordsContract.Commands.Propose()); + tx.output(CorporateRecordsContract_ID, cr); + return tx.fails(); + }); + l.transaction(tx -> { + tx.command(Arrays.asList(alice.getPublicKey(), bob.getPublicKey()), new CorporateRecordsContract.Commands.Propose()); + tx.output(CorporateRecordsContract_ID, cr); + return tx.verifies(); // As there are no input sates + }); + return null; + }); + } +} \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/contracts/src/test/java/net/corda/samples/duediligence/contracts/StateTests.java b/Advanced/duediligence-cordapp/contracts/src/test/java/net/corda/samples/duediligence/contracts/StateTests.java new file mode 100644 index 00000000..77320744 --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/test/java/net/corda/samples/duediligence/contracts/StateTests.java @@ -0,0 +1,18 @@ +package net.corda.samples.duediligence.contracts; + +import net.corda.core.identity.Party; +import net.corda.samples.duediligence.states.CorporateRecordsAuditRequest; +import net.corda.testing.node.MockServices; +import org.junit.Test; + +public class StateTests { + private final MockServices ledgerServices = new MockServices(); + + @Test + public void hasFieldOfCorrectType() throws NoSuchFieldException { + // Does the message field exist? + CorporateRecordsAuditRequest.class.getDeclaredField("applicant"); + // Is the message field of the correct type? + assert(CorporateRecordsAuditRequest.class.getDeclaredField("applicant").getType().equals(Party.class)); + } +} \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/due-d diagram.png b/Advanced/duediligence-cordapp/due-d diagram.png new file mode 100644 index 00000000..7dd6f0c8 Binary files /dev/null and b/Advanced/duediligence-cordapp/due-d diagram.png differ diff --git a/Advanced/duediligence-cordapp/gradle.properties b/Advanced/duediligence-cordapp/gradle.properties new file mode 100644 index 00000000..5a45466c --- /dev/null +++ b/Advanced/duediligence-cordapp/gradle.properties @@ -0,0 +1,3 @@ +name=Due diligence Cordapp +group=com.duediligence +version=1.0 \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/gradle/wrapper/gradle-wrapper.jar b/Advanced/duediligence-cordapp/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..99340b4a Binary files /dev/null and b/Advanced/duediligence-cordapp/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Advanced/duediligence-cordapp/gradle/wrapper/gradle-wrapper.properties b/Advanced/duediligence-cordapp/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..0188225a --- /dev/null +++ b/Advanced/duediligence-cordapp/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +#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-5.6.4-all.zip + diff --git a/Advanced/duediligence-cordapp/gradlew b/Advanced/duediligence-cordapp/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/Advanced/duediligence-cordapp/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/Advanced/duediligence-cordapp/gradlew.bat b/Advanced/duediligence-cordapp/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/Advanced/duediligence-cordapp/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/Advanced/duediligence-cordapp/repositories.gradle b/Advanced/duediligence-cordapp/repositories.gradle new file mode 100644 index 00000000..8be7b630 --- /dev/null +++ b/Advanced/duediligence-cordapp/repositories.gradle @@ -0,0 +1,8 @@ +repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://jitpack.io' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } +} diff --git a/Advanced/duediligence-cordapp/settings.gradle b/Advanced/duediligence-cordapp/settings.gradle new file mode 100644 index 00000000..2514aca2 --- /dev/null +++ b/Advanced/duediligence-cordapp/settings.gradle @@ -0,0 +1,3 @@ +include 'workflows' +include 'contracts' +include 'clients' \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/workflows/build.gradle b/Advanced/duediligence-cordapp/workflows/build.gradle new file mode 100644 index 00000000..996f079c --- /dev/null +++ b/Advanced/duediligence-cordapp/workflows/build.gradle @@ -0,0 +1,64 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + workflow { + name "Due Diligence Flows" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/dev") + } + } + test { + java { + srcDir 'src/test/java' + java.outputDir = file('bin/main') + } + 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_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":contracts") +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/workflows/src/integrationTest/java/net/corda/samples/duediligence/DriverBasedTest.java b/Advanced/duediligence-cordapp/workflows/src/integrationTest/java/net/corda/samples/duediligence/DriverBasedTest.java new file mode 100644 index 00000000..17a3d698 --- /dev/null +++ b/Advanced/duediligence-cordapp/workflows/src/integrationTest/java/net/corda/samples/duediligence/DriverBasedTest.java @@ -0,0 +1,48 @@ +package net.corda.samples.duediligence; + +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/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/AddApprovalsToCheckList.java b/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/AddApprovalsToCheckList.java new file mode 100644 index 00000000..a97fdb1f --- /dev/null +++ b/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/AddApprovalsToCheckList.java @@ -0,0 +1,107 @@ +package net.corda.samples.duediligence.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.ContractState; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +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 net.corda.samples.duediligence.contracts.DueDChecklistContract; +import net.corda.samples.duediligence.states.CopyOfCoporateRecordsAuditRequest; +import net.corda.samples.duediligence.states.CorporateRecordsAuditRequest; +import net.corda.samples.duediligence.states.DueDChecklist; + +import javax.naming.ldap.UnsolicitedNotification; +import java.util.Arrays; +import java.util.UUID; + +public class AddApprovalsToCheckList { + + @InitiatingFlow + @StartableByRPC + public static class CreateCheckListAndAddApprovalInitiator extends FlowLogic { + + private Party reportTo; + private UniqueIdentifier approvalId; + + public CreateCheckListAndAddApprovalInitiator(Party reportTo, UniqueIdentifier approvalId) { + this.reportTo = reportTo; + this.approvalId = approvalId; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + //Query the input + QueryCriteria.LinearStateQueryCriteria inputCriteria = new QueryCriteria.LinearStateQueryCriteria() + .withUuid(Arrays.asList(UUID.fromString(approvalId.toString()))) + .withStatus(Vault.StateStatus.UNCONSUMED) + .withRelevancyStatus(Vault.RelevancyStatus.RELEVANT); + StateAndRef inputStateAndRef = getServiceHub().getVaultService().queryBy(ContractState.class, inputCriteria).getStates().get(0); + + //extract the notary + Party notary = inputStateAndRef.getState().getNotary(); + + + //create due-diligence Checklist + DueDChecklist checklist = new DueDChecklist(3,getOurIdentity(),reportTo,new UniqueIdentifier()); + checklist.uploadApproval(this.approvalId); + + TransactionBuilder txBuilder = new TransactionBuilder(notary) + .addInputState(inputStateAndRef) + .addOutputState(checklist) + .addCommand(new DueDChecklistContract.Commands.Add(),Arrays.asList(getOurIdentity().getOwningKey(),reportTo.getOwningKey())); + + // Verify that the transaction is valid. + txBuilder.verify(getServiceHub()); + + // Sign the transaction. + final SignedTransaction partSignedTx = getServiceHub().signInitialTransaction(txBuilder); + + // Send the state to the counterparty, and receive it back with their signature. + FlowSession otherPartySession = initiateFlow(reportTo); + final SignedTransaction fullySignedTx = subFlow( + new CollectSignaturesFlow(partSignedTx, Arrays.asList(otherPartySession), CollectSignaturesFlow.Companion.tracker())); + + // Notarise and record the transaction in both parties' vaults. + if (inputStateAndRef.getState().getData().getClass() == CorporateRecordsAuditRequest.class){ + CorporateRecordsAuditRequest request = (CorporateRecordsAuditRequest) inputStateAndRef.getState().getData(); + return subFlow(new FinalityFlow(fullySignedTx, Arrays.asList(otherPartySession,initiateFlow(request.getValidater())))); + }else{ + CopyOfCoporateRecordsAuditRequest request = (CopyOfCoporateRecordsAuditRequest) inputStateAndRef.getState().getData(); + return subFlow(new FinalityFlow(fullySignedTx, Arrays.asList(otherPartySession,initiateFlow(request.getoriginalValidater()),initiateFlow(request.getOriginalOwner())))); + } + } + } + + @InitiatedBy(CreateCheckListAndAddApprovalInitiator.class) + public static class CreateCheckListAndAddApprovalResponder extends FlowLogic { + //private variable + private FlowSession counterpartySession; + + //Constructor + public CreateCheckListAndAddApprovalResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + } + }); + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } + } + +} diff --git a/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/RequestToValidateCorporateRecords.java b/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/RequestToValidateCorporateRecords.java new file mode 100644 index 00000000..c8b20d09 --- /dev/null +++ b/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/RequestToValidateCorporateRecords.java @@ -0,0 +1,91 @@ +package net.corda.samples.duediligence.flows; + +import co.paralleluniverse.fibers.Suspendable; +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; +import net.corda.samples.duediligence.contracts.CorporateRecordsContract; +import net.corda.samples.duediligence.states.CorporateRecordsAuditRequest; +import net.corda.core.identity.CordaX500Name; + +import java.util.Arrays; +import java.util.List; + +public class RequestToValidateCorporateRecords { + + @InitiatingFlow + @StartableByRPC + public static class RequestToValidateCorporateRecordsInitiator extends FlowLogic { + + //private variables + private Party validater; + private int numberOfFiles; + + + public RequestToValidateCorporateRecordsInitiator(Party validater, int numberOfFiles) { + this.validater = validater; + this.numberOfFiles = numberOfFiles; + + } + + @Suspendable + @Override + public String call() throws FlowException { + //notary + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + //Initiate Corporate Records validation + CorporateRecordsAuditRequest cr = new CorporateRecordsAuditRequest(getOurIdentity(),this.validater,this.numberOfFiles); + + //Build transaction + final TransactionBuilder txBuilder = new TransactionBuilder(notary) + .addOutputState(cr) + .addCommand(new CorporateRecordsContract.Commands.Propose(), + Arrays.asList(getOurIdentity().getOwningKey(),validater.getOwningKey())); + + // Verify that the transaction is valid. + txBuilder.verify(getServiceHub()); + + // Sign the transaction. + final SignedTransaction partSignedTx = getServiceHub().signInitialTransaction(txBuilder); + + // Send the state to the counterparty, and receive it back with their signature. + FlowSession otherPartySession = initiateFlow(validater); + final SignedTransaction fullySignedTx = subFlow( + new CollectSignaturesFlow(partSignedTx, Arrays.asList(otherPartySession), CollectSignaturesFlow.Companion.tracker())); + + // Notarise and record the transaction in both parties' vaults. + subFlow(new FinalityFlow(fullySignedTx, Arrays.asList(otherPartySession))); + return "Corporate Records Auditing Request has sent to: " + cr.getValidater().getName().getOrganisation() + +"\nCase Id: "+ cr.getLinearId(); + } + } + + @InitiatedBy(RequestToValidateCorporateRecordsInitiator.class) + public static class RequestToValidateCorporateRecordsResponder extends FlowLogic { + //private variable + private FlowSession counterpartySession; + + //Constructor + public RequestToValidateCorporateRecordsResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + } + }); + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } + } +} diff --git a/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/ShareAuditingResult.java b/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/ShareAuditingResult.java new file mode 100644 index 00000000..5389ad64 --- /dev/null +++ b/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/ShareAuditingResult.java @@ -0,0 +1,112 @@ +package net.corda.samples.duediligence.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.CommandAndState; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +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 net.corda.samples.duediligence.contracts.CorporateRecordsContract; +import net.corda.samples.duediligence.states.CopyOfCoporateRecordsAuditRequest; +import net.corda.samples.duediligence.states.CorporateRecordsAuditRequest; + +import java.util.Arrays; +import java.util.UUID; + +public class ShareAuditingResult { + + @InitiatingFlow + @StartableByRPC + public static class ShareAuditingResultInitiator extends FlowLogic { + + private UniqueIdentifier AuditingResultID; + private Party sendTo; + private static SecureHash trustedAuditorAttachment; + + + public ShareAuditingResultInitiator(UniqueIdentifier AuditingResultID, Party sendTo, SecureHash trustedAuditorAttachment) { + this.AuditingResultID = AuditingResultID; + this.sendTo = sendTo; + this.trustedAuditorAttachment = trustedAuditorAttachment; + + } + + @Override + @Suspendable + public String call() throws FlowException { + + //Query the input + QueryCriteria.LinearStateQueryCriteria inputCriteria = new QueryCriteria.LinearStateQueryCriteria() + .withUuid(Arrays.asList(UUID.fromString(AuditingResultID.toString()))) + .withStatus(Vault.StateStatus.UNCONSUMED) + .withRelevancyStatus(Vault.RelevancyStatus.RELEVANT); + StateAndRef inputStateAndRef = getServiceHub().getVaultService().queryBy(CorporateRecordsAuditRequest.class, inputCriteria).getStates().get(0); + CorporateRecordsAuditRequest input = (CorporateRecordsAuditRequest) inputStateAndRef.getState().getData(); + + //Send the copy to PartyB. + SignedTransaction originalTx = getServiceHub().getValidatedTransactions().getTransaction(inputStateAndRef.getRef().getTxhash()); + //subFlow(new SendTransactionFlow(initiateFlow(sendTo), originalTx)); + + //extract the notary + Party notary = inputStateAndRef.getState().getNotary(); + //CorporateRecordsAuditRequest imageState = new CorporateRecordsAuditRequest(input.getApplicant(),input.getValidater(),input.getNumberOfFiles(),input.getLinearId(),input.getQualification()); + UniqueIdentifier copyId = new UniqueIdentifier(); + CopyOfCoporateRecordsAuditRequest copyOfResult = new CopyOfCoporateRecordsAuditRequest(input.getApplicant(),sendTo,input.getLinearId(), + originalTx.getId(), input.getValidater(), input.getQualification(), copyId); + + TransactionBuilder txBuilder = new TransactionBuilder(notary) + .addReferenceState(inputStateAndRef.referenced()) + .addOutputState(copyOfResult) + .addCommand(new CorporateRecordsContract.Commands.Share(), Arrays.asList(input.getApplicant().getOwningKey(),sendTo.getOwningKey())) + .addAttachment(trustedAuditorAttachment); + ; + + // Verify that the transaction is valid. + txBuilder.verify(getServiceHub()); + + // Sign the transaction. + final SignedTransaction partSignedTx = getServiceHub().signInitialTransaction(txBuilder); + + // Send the state to the counterparty, and receive it back with their signature. + FlowSession otherPartySession = initiateFlow(sendTo); + final SignedTransaction fullySignedTx = subFlow( + new CollectSignaturesFlow(partSignedTx, Arrays.asList(otherPartySession), CollectSignaturesFlow.Companion.tracker())); + + // Notarise and record the transaction in both parties' vaults. + subFlow(new FinalityFlow(fullySignedTx, Arrays.asList(otherPartySession))); + return "A Copy of Corporate Auditing Report has sent to" + sendTo.getName().getOrganisation() + + "\nID of the Copy: "+ copyId; + } + } + + @InitiatedBy(ShareAuditingResultInitiator.class) + public static class ShareAuditingResultResponder extends FlowLogic { + //private variable + private FlowSession counterpartySession; + + //Constructor + public ShareAuditingResultResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + } + }); + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } + } + +} diff --git a/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/ValidateCorporateRecords.java b/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/ValidateCorporateRecords.java new file mode 100644 index 00000000..ee553798 --- /dev/null +++ b/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/ValidateCorporateRecords.java @@ -0,0 +1,99 @@ +package net.corda.samples.duediligence.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +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 net.corda.samples.duediligence.contracts.CorporateRecordsContract; +import net.corda.samples.duediligence.states.CorporateRecordsAuditRequest; + +import java.util.Arrays; +import java.util.UUID; + +public class ValidateCorporateRecords { + + @InitiatingFlow + @StartableByRPC + public static class ValidateCorporateRecordsInitiator extends FlowLogic { + + private UniqueIdentifier linearId; + + + public ValidateCorporateRecordsInitiator(UniqueIdentifier linearId) { + this.linearId = linearId; + } + + @Suspendable + @Override + public SignedTransaction call() throws FlowException { + + //Query the input + QueryCriteria.LinearStateQueryCriteria inputCriteria = new QueryCriteria.LinearStateQueryCriteria() + .withUuid(Arrays.asList(UUID.fromString(linearId.toString()))) + .withStatus(Vault.StateStatus.UNCONSUMED) + .withRelevancyStatus(Vault.RelevancyStatus.RELEVANT); + StateAndRef inputStateAndRef = getServiceHub().getVaultService().queryBy(CorporateRecordsAuditRequest.class, inputCriteria).getStates().get(0); + CorporateRecordsAuditRequest input = (CorporateRecordsAuditRequest) inputStateAndRef.getState().getData(); + + //extract the notary + Party notary = inputStateAndRef.getState().getNotary(); + + //Creating the output + CorporateRecordsAuditRequest output = new CorporateRecordsAuditRequest(input.getApplicant(),getOurIdentity(),input.getNumberOfFiles(),input.getLinearId()); + + //set validation status to true + output.validatedAndApproved(); + + //Build transaction + final TransactionBuilder txBuilder = new TransactionBuilder(notary) + .addInputState(inputStateAndRef) + .addOutputState(output) + .addCommand(new CorporateRecordsContract.Commands.Validate(), + Arrays.asList(getOurIdentity().getOwningKey(),input.getApplicant().getOwningKey())); + + // Verify that the transaction is valid. + txBuilder.verify(getServiceHub()); + + // Sign the transaction. + final SignedTransaction partSignedTx = getServiceHub().signInitialTransaction(txBuilder); + + // Send the state to the counterparty, and receive it back with their signature. + FlowSession otherPartySession = initiateFlow(input.getApplicant()); + final SignedTransaction fullySignedTx = subFlow( + new CollectSignaturesFlow(partSignedTx, Arrays.asList(otherPartySession), CollectSignaturesFlow.Companion.tracker())); + + // Notarise and record the transaction in both parties' vaults. + return subFlow(new FinalityFlow(fullySignedTx, Arrays.asList(otherPartySession))); + } + } + + @InitiatedBy(ValidateCorporateRecordsInitiator.class) + public static class ValidateCorporateRecordsResponder extends FlowLogic { + //private variable + private FlowSession counterpartySession; + + //Constructor + public ValidateCorporateRecordsResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + } + }); + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } + } +} diff --git a/Advanced/duediligence-cordapp/workflows/src/test/java/net/corda/samples/duediligence/FlowTests.java b/Advanced/duediligence-cordapp/workflows/src/test/java/net/corda/samples/duediligence/FlowTests.java new file mode 100644 index 00000000..ae0438d4 --- /dev/null +++ b/Advanced/duediligence-cordapp/workflows/src/test/java/net/corda/samples/duediligence/FlowTests.java @@ -0,0 +1,66 @@ +package net.corda.samples.duediligence; + +import com.google.common.collect.ImmutableList; +import net.corda.core.contracts.ContractState; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.samples.duediligence.flows.RequestToValidateCorporateRecords; +import net.corda.samples.duediligence.states.CorporateRecordsAuditRequest; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import static org.junit.Assert.assertEquals; + +public class FlowTests { + private MockNetwork network; + private StartedMockNode a; + private StartedMockNode b; + + @Before + public void setup() { + network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.duediligence.contracts"), + TestCordapp.findCordapp("net.corda.samples.duediligence.flows"))) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB"))))); + a = network.createPartyNode(null); + b = network.createPartyNode(null); + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void CreationAndSigningTheAuditingRequest() throws ExecutionException, InterruptedException { + RequestToValidateCorporateRecords.RequestToValidateCorporateRecordsInitiator flow1 = + new RequestToValidateCorporateRecords.RequestToValidateCorporateRecordsInitiator(b.getInfo().getLegalIdentities().get(0),10); + Future future = a.startFlow(flow1); + network.runNetwork(); + String result1 = future.get(); + System.out.println(result1); + int subString = result1.indexOf("Case Id: "); + String ApproalID = result1.substring(subString+9); + System.out.println("-"+ ApproalID+"-"); + + UniqueIdentifier id = UniqueIdentifier.Companion.fromString(ApproalID); + //Query the input + QueryCriteria.LinearStateQueryCriteria inputCriteria = new QueryCriteria.LinearStateQueryCriteria() + .withUuid(Arrays.asList(UUID.fromString(id.toString()))) + .withStatus(Vault.StateStatus.UNCONSUMED) + .withRelevancyStatus(Vault.RelevancyStatus.RELEVANT); + StateAndRef inputStateAndRef = a.getServices().getVaultService().queryBy(ContractState.class, inputCriteria).getStates().get(0); + CorporateRecordsAuditRequest result = (CorporateRecordsAuditRequest)inputStateAndRef.getState().getData(); + assertEquals(result.getLinearId(),id); + } +} diff --git a/Advanced/negotiation-cordapp/build.gradle b/Advanced/negotiation-cordapp/build.gradle index ad92a9bd..d98cff3d 100644 --- a/Advanced/negotiation-cordapp/build.gradle +++ b/Advanced/negotiation-cordapp/build.gradle @@ -20,17 +20,17 @@ buildscript { } repositories { - jcenter() + mavenLocal() mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'http://software.r3.com/artifactory/corda-lib' } maven { url 'http://software.r3.com/artifactory/corda-lib-dev' } - maven { url 'https://software.r3.com/artifactory/corda-releases' } + maven { url 'https://download.corda.net/maven/corda-releases' } // Corda dependencies for the patched Quasar version - maven { url "https://software.r3.com/artifactory/corda-dependencies" } // access to the patched Quasar version + maven { url "https://download.corda.net/maven/corda-dependencies" } // access to the patched Quasar version } dependencies { @@ -47,12 +47,12 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } // Can be removed post-release - used to get nightly snapshot build. - maven { url 'https://software.r3.com/artifactory/corda-lib' } - maven { url 'https://software.r3.com/artifactory/corda-lib-dev' } + maven { url 'https://download.corda.net/maven/corda-lib' } + maven { url 'https://download.corda.net/maven/corda-lib-dev' } maven { url 'https://jitpack.io' } maven { url "https://repo.gradle.org/gradle/libs-releases-local" } } @@ -94,6 +94,8 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" + } cordapp { @@ -111,6 +113,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { nodeDefaults { cordapp project(':contracts') + cordapp project(':workflows') runSchemaMigration = true } diff --git a/Advanced/negotiation-cordapp/gradle.properties b/Advanced/negotiation-cordapp/gradle.properties index e1fac0e5..03a6057a 100644 --- a/Advanced/negotiation-cordapp/gradle.properties +++ b/Advanced/negotiation-cordapp/gradle.properties @@ -1,3 +1,3 @@ name=Negotiation Cordapp -group=com.negotiation +group=net.corda.samples.negotiation version=0.2 diff --git a/Advanced/negotiation-cordapp/repositories.gradle b/Advanced/negotiation-cordapp/repositories.gradle index 206b9c68..9797c0ea 100644 --- a/Advanced/negotiation-cordapp/repositories.gradle +++ b/Advanced/negotiation-cordapp/repositories.gradle @@ -1,8 +1,7 @@ repositories { mavenLocal() mavenCentral() - jcenter() maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ProposalFlow.java b/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ProposalFlow.java index 8cefad5b..45def7ee 100644 --- a/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ProposalFlow.java +++ b/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ProposalFlow.java @@ -15,6 +15,7 @@ import java.security.PublicKey; import java.util.List; +import net.corda.core.identity.CordaX500Name; public class ProposalFlow { @InitiatingFlow @@ -51,13 +52,8 @@ public UniqueIdentifier call() throws FlowException { //Building the transaction // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); TransactionBuilder txBuilder = new TransactionBuilder(notary) .addOutputState(output, ProposalAndTradeContract.ID) diff --git a/Advanced/negotiation-cordapp/workflows/src/test/java/net/corda/samples/negotiation/flows/FlowTestBase.java b/Advanced/negotiation-cordapp/workflows/src/test/java/net/corda/samples/negotiation/flows/FlowTestBase.java index 55b905a4..d6e2fd85 100644 --- a/Advanced/negotiation-cordapp/workflows/src/test/java/net/corda/samples/negotiation/flows/FlowTestBase.java +++ b/Advanced/negotiation-cordapp/workflows/src/test/java/net/corda/samples/negotiation/flows/FlowTestBase.java @@ -2,14 +2,14 @@ import com.google.common.collect.ImmutableList; import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; import net.corda.core.identity.Party; -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 net.corda.testing.driver.VerifierType; +import net.corda.testing.node.*; import org.junit.After; import org.junit.Before; +import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -21,11 +21,11 @@ abstract class FlowTestBase { @Before public void setup() { - network = new MockNetwork(new MockNetworkParameters(ImmutableList.of( - TestCordapp.findCordapp("net.corda.samples.negotiation.flows"), - TestCordapp.findCordapp("net.corda.samples.negotiation.contracts") - ) - )); + network = new MockNetwork(new MockNetworkParameters( + ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.negotiation.flows"), + TestCordapp.findCordapp("net.corda.samples.negotiation.contracts")) + ).withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB"))))); a = network.createPartyNode(null); b = network.createPartyNode(null); diff --git a/Advanced/obligation-cordapp/README.md b/Advanced/obligation-cordapp/README.md index 87830236..952c7f9a 100644 --- a/Advanced/obligation-cordapp/README.md +++ b/Advanced/obligation-cordapp/README.md @@ -1,6 +1,6 @@ # Obligation Cordap -This Cordapp is the complete implementation of our signature IOU (I-owe-you) demonstration. +This CorDapp is the complete implementation of our signature IOU (I-owe-you) demonstration. ## Concepts @@ -9,12 +9,12 @@ An IOU is someone who has cash that is paying it back to someone they owe it to. ### Flows -The first flows are the ones that issue the original cash and assets. You can find that the cash flow at `SelfIssueCashFlow.java` and the IOU issurance in `IOUIssueFlow.java`. +The first flows are the ones that issue the original cash and assets. You can find that the cash flow at `SelfIssueCashFlow.java` and the IOU issuance in `IOUIssueFlow.java`. The next flow is the one that transfers ownership of that asset over to another party. That can be found in `IOUTransferFlow.java`. -Finally, once we have the ability to transfer assets, we just need to settle up. That functiionality can be found here in `IOUSettleFlow.java` +Finally, once we have the ability to transfer assets, we just need to settle up. That functionality can be found here in `IOUSettleFlow.java` diff --git a/Advanced/obligation-cordapp/build.gradle b/Advanced/obligation-cordapp/build.gradle index 9d873ac6..15bd2c09 100644 --- a/Advanced/obligation-cordapp/build.gradle +++ b/Advanced/obligation-cordapp/build.gradle @@ -22,8 +22,8 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -41,9 +41,8 @@ allprojects { repositories { mavenLocal() - jcenter() mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } } @@ -92,6 +91,8 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" + } cordapp { diff --git a/Advanced/obligation-cordapp/clients/build.gradle b/Advanced/obligation-cordapp/clients/build.gradle index bab14a2f..6bf3c08f 100644 --- a/Advanced/obligation-cordapp/clients/build.gradle +++ b/Advanced/obligation-cordapp/clients/build.gradle @@ -1,6 +1,6 @@ repositories { mavenLocal() - jcenter() + mavenCentral() maven { url 'https://jitpack.io' } maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases' } @@ -26,6 +26,12 @@ dependencyManagement { } } +configurations { + all { + exclude group: 'ch.qos.logback', module: 'logback-classic' + } +} + dependencies { // CorDapp dependencies. compile project(":contracts") diff --git a/Advanced/obligation-cordapp/contracts/build.gradle b/Advanced/obligation-cordapp/contracts/build.gradle index 71bf133b..ae474abf 100644 --- a/Advanced/obligation-cordapp/contracts/build.gradle +++ b/Advanced/obligation-cordapp/contracts/build.gradle @@ -33,7 +33,6 @@ dependencies { cordaRuntime "$corda_release_group:corda:$corda_release_version" cordaCompile "$corda_release_group:corda-finance-contracts:$corda_release_version" cordaCompile "$corda_release_group:corda-finance-workflows:$corda_release_version" - compileOnly "$corda_release_group:corda-testserver-impl:$corda_release_version" testCompile "$corda_release_group:corda-node-driver:$corda_release_version" cordapp "$corda_release_group:corda-finance-contracts:$corda_release_version" cordapp "$corda_release_group:corda-finance-workflows:$corda_release_version" diff --git a/Advanced/obligation-cordapp/repositories.gradle b/Advanced/obligation-cordapp/repositories.gradle index 206b9c68..8be7b630 100644 --- a/Advanced/obligation-cordapp/repositories.gradle +++ b/Advanced/obligation-cordapp/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUIssueFlow.java b/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUIssueFlow.java index f068e2ed..9a608cf3 100644 --- a/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUIssueFlow.java +++ b/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUIssueFlow.java @@ -18,6 +18,7 @@ import net.corda.samples.obligation.contracts.IOUContract; import net.corda.samples.obligation.states.IOUState; import static net.corda.samples.obligation.contracts.IOUContract.Commands.*; +import net.corda.core.identity.CordaX500Name; /** * This is the flows which handles issuance of new IOUs on the ledger. @@ -39,14 +40,8 @@ public InitiatorFlow(IOUState state) { @Override public SignedTransaction call() throws FlowException { // Step 1. Get a reference to the notary service on our network and our key pair. - - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // Step 2. Create a new issue command. // Remember that a command is a CommandData object and a list of CompositeKeys diff --git a/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUSettleFlow.java b/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUSettleFlow.java index 0cf47b60..714a0d3e 100644 --- a/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUSettleFlow.java +++ b/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUSettleFlow.java @@ -68,13 +68,7 @@ public SignedTransaction call() throws FlowException { // Step 3. Create a transaction builder. // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + Party notary = inputStateAndRefToSettle.getState().getNotary(); TransactionBuilder tb = new TransactionBuilder(notary); diff --git a/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUTransferFlow.java b/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUTransferFlow.java index c29906f0..4186aabb 100644 --- a/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUTransferFlow.java +++ b/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUTransferFlow.java @@ -66,13 +66,7 @@ public SignedTransaction call() throws FlowException { // Here we get a reference to the default notary and instantiate a transaction builder. // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + Party notary = inputStateAndRefToTransfer.getState().getNotary(); TransactionBuilder tb = new TransactionBuilder(notary); diff --git a/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/SelfIssueCashFlow.java b/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/SelfIssueCashFlow.java index 4e5d69cb..2f0094dc 100644 --- a/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/SelfIssueCashFlow.java +++ b/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/SelfIssueCashFlow.java @@ -9,6 +9,7 @@ import net.corda.finance.contracts.asset.Cash; import net.corda.finance.flows.CashIssueFlow; import java.util.Currency; +import net.corda.core.identity.CordaX500Name; @InitiatingFlow @StartableByRPC @@ -26,13 +27,8 @@ public Cash.State call() throws FlowException { OpaqueBytes issueRef = OpaqueBytes.of("1".getBytes()); // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); /** Create the cash issuance transaction. */ SignedTransaction cashIssueTransaction = subFlow(new CashIssueFlow(amount, issueRef, notary)).getStx(); diff --git a/Advanced/secretsanta-cordapp/build.gradle b/Advanced/secretsanta-cordapp/build.gradle index d67642c8..622a2636 100644 --- a/Advanced/secretsanta-cordapp/build.gradle +++ b/Advanced/secretsanta-cordapp/build.gradle @@ -28,11 +28,10 @@ buildscript { //properties that you need to build the project repositories { mavenLocal() mavenCentral() - jcenter() maven { url 'https://jitpack.io' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } maven { url 'http://software.r3.com/artifactory/corda-lib' } - maven { url 'https://software.r3.com/artifactory/corda-releases' } + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -53,10 +52,10 @@ allprojects {//Properties that you need to compile your project (The application repositories { mavenLocal() - jcenter() + mavenCentral() maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } } tasks.withType(JavaCompile) { @@ -106,6 +105,8 @@ dependencies { cordaCompile "org.slf4j:jul-to-slf4j:$slf4j_version" cordaCompile "org.apache.logging.log4j:log4j-web:${log4j_version}" cordaCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" + cordaDriver "net.corda:corda-shell:4.10" + } //Task to deploy the nodes in order to bootstrap a network diff --git a/Advanced/secretsanta-cordapp/repositories.gradle b/Advanced/secretsanta-cordapp/repositories.gradle index 2874c2ab..7adba3a3 100644 --- a/Advanced/secretsanta-cordapp/repositories.gradle +++ b/Advanced/secretsanta-cordapp/repositories.gradle @@ -1,7 +1,7 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } diff --git a/Advanced/secretsanta-cordapp/workflows/src/main/java/net/corda/samples/secretsanta/flows/CreateSantaSessionFlow.java b/Advanced/secretsanta-cordapp/workflows/src/main/java/net/corda/samples/secretsanta/flows/CreateSantaSessionFlow.java index 0065f0e8..7b9635d5 100644 --- a/Advanced/secretsanta-cordapp/workflows/src/main/java/net/corda/samples/secretsanta/flows/CreateSantaSessionFlow.java +++ b/Advanced/secretsanta-cordapp/workflows/src/main/java/net/corda/samples/secretsanta/flows/CreateSantaSessionFlow.java @@ -14,6 +14,7 @@ import static java.util.Collections.singletonList; import java.util.*; +import net.corda.core.identity.CordaX500Name; /** @@ -65,7 +66,8 @@ public ProgressTracker getProgressTracker() { public SignedTransaction call() throws FlowException { // run an issuance transaction for a new secret santa game progressTracker.setCurrentStep(CREATING); - Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); Party issuer = getOurIdentity(); SantaSessionState newSantaState = new SantaSessionState(playerNames, playerEmails, issuer, owner); diff --git a/Advanced/secretsanta-cordapp/workflows/src/test/java/net/corda/samples/secretsanta/CheckAssignedSantaFlowTests.java b/Advanced/secretsanta-cordapp/workflows/src/test/java/net/corda/samples/secretsanta/CheckAssignedSantaFlowTests.java index f59aa598..8102b6e6 100644 --- a/Advanced/secretsanta-cordapp/workflows/src/test/java/net/corda/samples/secretsanta/CheckAssignedSantaFlowTests.java +++ b/Advanced/secretsanta-cordapp/workflows/src/test/java/net/corda/samples/secretsanta/CheckAssignedSantaFlowTests.java @@ -1,6 +1,7 @@ package net.corda.samples.secretsanta; import com.google.common.collect.ImmutableList; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.secretsanta.flows.CheckAssignedSantaFlow; import net.corda.samples.secretsanta.flows.CreateSantaSessionFlow; import net.corda.samples.secretsanta.states.SantaSessionState; @@ -8,10 +9,7 @@ import net.corda.core.contracts.UniqueIdentifier; import net.corda.core.node.NetworkParameters; import net.corda.core.transactions.SignedTransaction; -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 net.corda.testing.node.*; import org.junit.After; import org.junit.Before; @@ -20,7 +18,7 @@ import java.time.Instant; import java.util.*; -import static org.jgroups.util.Util.assertEquals; +import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; public class CheckAssignedSantaFlowTests { @@ -39,6 +37,7 @@ public void setup() { network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( TestCordapp.findCordapp("net.corda.samples.secretsanta.contracts"), TestCordapp.findCordapp("net.corda.samples.secretsanta.flows"))).withNetworkParameters(testNetworkParameters) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) ); santa = network.createPartyNode(null); diff --git a/Advanced/secretsanta-cordapp/workflows/src/test/java/net/corda/samples/secretsanta/CreateSantaSessionFlowTests.java b/Advanced/secretsanta-cordapp/workflows/src/test/java/net/corda/samples/secretsanta/CreateSantaSessionFlowTests.java index 1b22d6f8..b617bca2 100644 --- a/Advanced/secretsanta-cordapp/workflows/src/test/java/net/corda/samples/secretsanta/CreateSantaSessionFlowTests.java +++ b/Advanced/secretsanta-cordapp/workflows/src/test/java/net/corda/samples/secretsanta/CreateSantaSessionFlowTests.java @@ -1,6 +1,7 @@ package net.corda.samples.secretsanta; import com.google.common.collect.ImmutableList; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.secretsanta.contracts.SantaSessionContract; import net.corda.samples.secretsanta.flows.CreateSantaSessionFlow; import net.corda.samples.secretsanta.states.SantaSessionState; @@ -9,10 +10,7 @@ import net.corda.core.contracts.TransactionState; import net.corda.core.node.NetworkParameters; import net.corda.core.transactions.SignedTransaction; -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 net.corda.testing.node.*; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -22,8 +20,8 @@ import java.util.Arrays; import java.util.LinkedHashMap; -import static org.jgroups.util.Util.assertEquals; -import static org.jgroups.util.Util.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertNull; public class CreateSantaSessionFlowTests { @@ -43,6 +41,7 @@ public void setup() { network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( TestCordapp.findCordapp("net.corda.samples.secretsanta.contracts"), TestCordapp.findCordapp("net.corda.samples.secretsanta.flows"))).withNetworkParameters(testNetworkParameters) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) ); santa = network.createPartyNode(null); diff --git a/Advanced/snakesandladders-cordapp/README.md b/Advanced/snakesandladders-cordapp/README.md index ae80630b..2e5f5b9a 100644 --- a/Advanced/snakesandladders-cordapp/README.md +++ b/Advanced/snakesandladders-cordapp/README.md @@ -2,13 +2,13 @@ This sample implements a simple Snakes And Ladder Game on Corda. -Its a simple game which has a board with numbers from 1 to 100. Each player starts at 1. -Players takes turn to roll a dice and move as many places as they rolled. If a player lands on a number -with a ladder thay climb up using the ladder or if they land in a number with a snake they move down on the board. +It's a simple game which has a board with numbers from 1 to 100. Each player starts at 1. +Players take turn to roll a dice and move as many places as they rolled. If a player lands on a number +with a ladder they climb up using the ladder or if they land in a number with a snake they move down on the board. The player who reach 100 first wins. -The cordapp runs a network having 4 nodes, -1. Classis Games +The CorDapp runs a network having 4 nodes, +1. Classic Games 2. Mega Games 3. Oracle 4. Notary diff --git a/Advanced/snakesandladders-cordapp/build.gradle b/Advanced/snakesandladders-cordapp/build.gradle index 04dde1ae..f30e7f56 100644 --- a/Advanced/snakesandladders-cordapp/build.gradle +++ b/Advanced/snakesandladders-cordapp/build.gradle @@ -28,8 +28,8 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -46,12 +46,12 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } //SDK lib - maven { url 'https://software.r3.com/artifactory/corda-lib' } + maven { url 'https://download.corda.net/maven/corda-lib' } //Gradle Plugins maven { url 'https://repo.gradle.org/gradle/libs-releases' } } @@ -98,6 +98,8 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" + //accounts cordapp "$accounts_release_group:accounts-contracts:$accounts_release_version" diff --git a/Advanced/snakesandladders-cordapp/client/build.gradle b/Advanced/snakesandladders-cordapp/client/build.gradle index 79ce3545..82103fec 100644 --- a/Advanced/snakesandladders-cordapp/client/build.gradle +++ b/Advanced/snakesandladders-cordapp/client/build.gradle @@ -1,5 +1,11 @@ apply plugin: 'org.springframework.boot' +configurations { + all { + exclude group: 'ch.qos.logback', module: 'logback-classic' + } +} + dependencies { // Corda dependencies. compile "$corda_release_group:corda-rpc:$corda_release_version" diff --git a/Advanced/snakesandladders-cordapp/repositories.gradle b/Advanced/snakesandladders-cordapp/repositories.gradle index 206b9c68..8be7b630 100644 --- a/Advanced/snakesandladders-cordapp/repositories.gradle +++ b/Advanced/snakesandladders-cordapp/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateAndShareAccountFlow.java b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateAndShareAccountFlow.java index 008c605b..17c1cb96 100644 --- a/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateAndShareAccountFlow.java +++ b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateAndShareAccountFlow.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.stream.Collectors; +import net.corda.core.identity.CordaX500Name; @StartableByRPC @InitiatingFlow @@ -33,7 +34,9 @@ public String call() throws FlowException { StateAndRef accountInfoStateAndRef = (StateAndRef) subFlow(new CreateAccount(accountName)); - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + Party oracle = getServiceHub().getNetworkMapCache() .getNodeByLegalName(CordaX500Name.parse("O=Oracle,L=Mumbai,C=IN")).getLegalIdentities().get(0); diff --git a/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateBoardConfig.java b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateBoardConfig.java index 705a716c..8af2de27 100644 --- a/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateBoardConfig.java +++ b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateBoardConfig.java @@ -17,6 +17,7 @@ import java.security.SignatureException; import java.util.*; +import net.corda.core.identity.CordaX500Name; public class CreateBoardConfig { private CreateBoardConfig() {} @@ -36,7 +37,9 @@ public Initiator(String player1, String player2) { @Override @Suspendable public SignedTransaction call() throws FlowException { - Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + AccountService accountService = getServiceHub().cordaService(KeyManagementBackedAccountService.class); List> p1accountInfo = accountService.accountInfo(player1); diff --git a/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateGameFlow.java b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateGameFlow.java index ae6845b5..fb664384 100644 --- a/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateGameFlow.java +++ b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateGameFlow.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.List; import java.util.UUID; +import net.corda.core.identity.CordaX500Name; public class CreateGameFlow { @@ -43,7 +44,9 @@ public Initiator(String player1, String player2) { @Override @Suspendable public String call() throws FlowException { - Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + AccountService accountService = getServiceHub().cordaService(KeyManagementBackedAccountService.class); List> p1accountInfo = accountService.accountInfo(player1); diff --git a/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/PlayerMoveFlow.java b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/PlayerMoveFlow.java index 19642828..4712a094 100644 --- a/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/PlayerMoveFlow.java +++ b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/PlayerMoveFlow.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; +import net.corda.core.identity.CordaX500Name; public class PlayerMoveFlow { @@ -52,7 +53,9 @@ public Initiator(String player, String linearId, int diceRolled) { @Override @Suspendable public SignedTransaction call() throws FlowException { - Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + AccountService accountService = getServiceHub().cordaService(KeyManagementBackedAccountService.class); //Get current player account info diff --git a/Advanced/snakesandladders-cordapp/workflows/src/test/java/net/corda/samples/snl/flows/FlowTests.java b/Advanced/snakesandladders-cordapp/workflows/src/test/java/net/corda/samples/snl/flows/FlowTests.java index c047e9f6..7f141371 100644 --- a/Advanced/snakesandladders-cordapp/workflows/src/test/java/net/corda/samples/snl/flows/FlowTests.java +++ b/Advanced/snakesandladders-cordapp/workflows/src/test/java/net/corda/samples/snl/flows/FlowTests.java @@ -3,13 +3,11 @@ import com.google.common.collect.ImmutableList; import com.r3.corda.lib.accounts.workflows.flows.CreateAccount; import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; import net.corda.core.node.NetworkParameters; import net.corda.core.transactions.SignedTransaction; import net.corda.samples.snl.states.BoardConfig; -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 net.corda.testing.node.*; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -36,7 +34,9 @@ public void setup() { TestCordapp.findCordapp("com.r3.corda.lib.ci")) ).withNetworkParameters(new NetworkParameters(4, Collections.emptyList(), 10485760, 10485760 * 50, Instant.now(), 1, - Collections.emptyMap())) + Collections.emptyMap()) + ) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) ); a = network.createPartyNode(null); b = network.createPartyNode(null); diff --git a/Advanced/syndicated-lending/.ci/Jenkinsfile b/Advanced/syndicated-lending/.ci/Jenkinsfile new file mode 100644 index 00000000..7421cafe --- /dev/null +++ b/Advanced/syndicated-lending/.ci/Jenkinsfile @@ -0,0 +1,37 @@ +#!groovy +/** + * Jenkins pipeline to build the java CorDapp template + */ + +/** + * Kill already started job. + * Assume new commit takes precedence and results from previousunfinished builds are not required. + * This feature doesn't play well with disableConcurrentBuilds() option + */ +@Library('corda-shared-build-pipeline-steps') +import static com.r3.build.BuildControl.killAllExistingBuildsForJob +killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) + +pipeline { + agent { + label 'eight-cores' + } + options { + ansiColor('xterm') + timestamps() + timeout(3*60) // 3 hours + buildDiscarder(logRotator(daysToKeepStr: '7', artifactDaysToKeepStr: '7')) + } + stages { + stage('Build') { + steps { + sh './gradlew --no-daemon -s clean build test deployNodes' + } + } + } + post { + cleanup { + deleteDir() + } + } +} diff --git a/Advanced/syndicated-lending/.gitignore b/Advanced/syndicated-lending/.gitignore new file mode 100644 index 00000000..0d75547d --- /dev/null +++ b/Advanced/syndicated-lending/.gitignore @@ -0,0 +1,78 @@ +# Eclipse, ctags, Mac metadata, log files +.classpath +.project +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/ +*/bin/* + +# 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 diff --git a/Advanced/syndicated-lending/.settings/org.eclipse.jdt.core.prefs b/Advanced/syndicated-lending/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..ac86f90c --- /dev/null +++ b/Advanced/syndicated-lending/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1 @@ +org.eclipse.jdt.core.compiler.codegen.methodParameters=generate \ No newline at end of file diff --git a/Advanced/syndicated-lending/FlowDiagram.pptx b/Advanced/syndicated-lending/FlowDiagram.pptx new file mode 100644 index 00000000..c011cc9e Binary files /dev/null and b/Advanced/syndicated-lending/FlowDiagram.pptx differ diff --git a/Advanced/syndicated-lending/LICENCE b/Advanced/syndicated-lending/LICENCE new file mode 100644 index 00000000..3ff572d1 --- /dev/null +++ b/Advanced/syndicated-lending/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/Advanced/syndicated-lending/README.md b/Advanced/syndicated-lending/README.md new file mode 100644 index 00000000..77b8b6c9 --- /dev/null +++ b/Advanced/syndicated-lending/README.md @@ -0,0 +1,102 @@ +

+ Corda +

+ +# Syndicated Lending Sample Cordapp + +This is a sample Cordapp which demonstrate a high level Syndicated Lending scenario on a Corda network. + +Syndicated lending comprises of multiple banks coming together to service the loan requirement of a borrower. +Generally a lead bank is appointed who coordinated the process to forming a syndicate with other participating banks +and agreeing on loan terms. + +# Pre-Requisites + +See https://docs.corda.net/getting-set-up.html. + +# Usage + +## Running the CorDapp + +Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) +``` +./gradlew clean deployNodes +``` +Then type: (to run the nodes) +``` +./build/nodes/runnodes +``` + +## Interacting with the CorDapp + +PeterCo (as a borrower) starts the `SubmitProjectProposalFlow` in order to submit the project details to a group of +lenders and request for funds. + +Go to PeterCo's terminal and run the below command + +``` +start SubmitProjectProposalFlow lenders: [BankOfAshu, BankOfSneha], projectDescription: "Overseas Expansion", projectCost: 10000000, loanAmount: 8000000 +``` + +Validate the project details are created and shared with the lenders successfully by running the vaultQuery command in each +lender's terminal and borrower's terminal. + +``` +run vaultQuery contractStateType: net.corda.samples.lending.states.ProjectState +``` + +Once the lenders have verified the project details and done their due deligence, they could submit bids for loan. + +Goto BankOfAshu's terminal and run the below command. The project-id can be found using the vaultQuery command shown earlier. + +``` +start SubmitLoanBidFlow borrower: PeterCo, loanAmount: 8000000, tenure: 5, rateofInterest: 4.0, transactionFees: 20000, projectIdentifier: +``` + +Validate the loanBid is submitted successfully by running the vaultQuery command below: + +``` +run vaultQuery contractStateType: net.corda.samples.lending.states.LoanBidState +``` + +Now the borrower can inspect the loan terms and approve the loan bid, to start the syndication process. + +Go to PeterCo's terminal and run the below command. The loanbid-identifier can be found using the vaultQuery command used earlier. + +``` +start ApproveLoanBidFlow bidIdentifier: +``` + +One the loan bid has been approved by the borrower, the lender can start the process of creating the syndicate by +acting as the lead bank and approach participating bank for funds. + +Goto BankOfAshu's terminal and run the below command. + +``` +start CreateSyndicateFlow participantBanks: [BankOfSneha, BankOfTom], projectIdentifier: , loanDetailIdentifier: +``` + +Verify the syndicate is created using the below command: + +``` +run vaultQuery contractStateType: net.corda.samples.lending.states.SyndicateState +``` + +On receiving the syndicate creation request, participating banks could verify the project and loan terms and submit +bids for the amount of fund they wish to lend by using the below flow in BankOfSneha or BankOfTom node. + +``` +start SyndicateBidFlow$Initiator syndicateIdentifier: , bidAmount: +``` + +Verify the syndicate bid is successfully created using the below command: + +``` +run vaultQuery contractStateType: net.corda.samples.lending.states.SyndicateBidState +``` + +The lead bank on receiving bids from participating banks could approve the bid using the below flow command. + +``` +start ApproveSyndicateBidFlow bidIdentifier: +``` \ No newline at end of file diff --git a/Advanced/syndicated-lending/TRADEMARK b/Advanced/syndicated-lending/TRADEMARK new file mode 100644 index 00000000..d2e056b5 --- /dev/null +++ b/Advanced/syndicated-lending/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/Advanced/syndicated-lending/build.gradle b/Advanced/syndicated-lending/build.gradle new file mode 100644 index 00000000..68ca4762 --- /dev/null +++ b/Advanced/syndicated-lending/build.gradle @@ -0,0 +1,156 @@ +buildscript {//properties that you need to build the project + Properties constants = new Properties() + file("$projectDir/./constants.properties").withInputStream { constants.load(it) } + + ext { + corda_release_group = constants.getProperty("cordaReleaseGroup") + corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") + corda_release_version = constants.getProperty("cordaVersion") + corda_core_release_version = constants.getProperty("cordaCoreVersion") + 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' + } + + repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://download.corda.net/maven/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 {//Properties that you need to compile your project (The application) + apply from: "${rootProject.projectDir}/repositories.gradle" + apply plugin: 'java' + + repositories { + mavenLocal() + + mavenCentral() + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://download.corda.net/maven/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 + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} + +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") + } + } +} +//Module dependencis +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_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") + + 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" + cordaDriver "net.corda:corda-shell:4.10" + +} + + +//Task to deploy the nodes in order to bootstrap a network +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + + /* This property will load the CorDapps to each of the node by default, including the Notary. You can find them + * in the cordapps folder of the node at build/nodes/Notary/cordapps. However, the notary doesn't really understand + * the notion of cordapps. In production, Notary does not need cordapps as well. This is just a short cut to load + * the Corda network bootstrapper. + */ + nodeDefaults { + projectCordapp { + deploy = false + } + cordapp project(':contracts') + cordapp project(':workflows') + runSchemaMigration = true //This configuration is for any CorDapps with custom schema, We will leave this as true to avoid + //problems for developers who are not familiar with Corda. If you are not using custom schemas, you can change + //it to false for quicker project compiling time. + } + node { + name "O=Notary,L=London,C=GB" + notary = [validating : false] + p2pPort 10002 + rpcSettings { + address("localhost:10003") + adminAddress("localhost:10043") + } + } + node { + name "O=PeterCo,L=London,C=GB" + p2pPort 10005 + rpcSettings { + address("localhost:10006") + adminAddress("localhost:10046") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=BankOfAshu,L=New York,C=US" + p2pPort 10008 + rpcSettings { + address("localhost:10009") + adminAddress("localhost:10049") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=BankOfSneha,L=New York,C=US" + p2pPort 10011 + rpcSettings { + address("localhost:10012") + adminAddress("localhost:10052") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=BankOfTom,L=New York,C=US" + p2pPort 10014 + rpcSettings { + address("localhost:10016") + adminAddress("localhost:10056") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } +} diff --git a/Advanced/syndicated-lending/config/dev/log4j2.xml b/Advanced/syndicated-lending/config/dev/log4j2.xml new file mode 100644 index 00000000..34ba4d45 --- /dev/null +++ b/Advanced/syndicated-lending/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/Advanced/syndicated-lending/config/test/log4j2.xml b/Advanced/syndicated-lending/config/test/log4j2.xml new file mode 100644 index 00000000..cd9926ca --- /dev/null +++ b/Advanced/syndicated-lending/config/test/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n + > + + + + + + + + + + + + diff --git a/Advanced/syndicated-lending/constants.properties b/Advanced/syndicated-lending/constants.properties new file mode 100644 index 00000000..adedb0f3 --- /dev/null +++ b/Advanced/syndicated-lending/constants.properties @@ -0,0 +1,12 @@ +cordaReleaseGroup=net.corda +cordaCoreReleaseGroup=net.corda +cordaVersion=4.8.5 +cordaCoreVersion=4.8.5 +gradlePluginsVersion=5.0.12 +kotlinVersion=1.2.71 +junitVersion=4.12 +quasarVersion=0.7.10 +log4jVersion =2.16.0 +platformVersion=10 +slf4jVersion=1.7.25 +nettyVersion=4.1.22.Final diff --git a/Advanced/syndicated-lending/contracts/build.gradle b/Advanced/syndicated-lending/contracts/build.gradle new file mode 100644 index 00000000..9f2664fb --- /dev/null +++ b/Advanced/syndicated-lending/contracts/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + contract { + name "Lending Contracts" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main{ + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + } + test{ + java{ + srcDir 'src/test/java' + java.outputDir = file('bin/test') + } + } +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_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/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/LoanBidContract.java b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/LoanBidContract.java new file mode 100644 index 00000000..047caa6c --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/LoanBidContract.java @@ -0,0 +1,42 @@ +package net.corda.samples.lending.contracts; + + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import org.jetbrains.annotations.NotNull; + +import static net.corda.core.contracts.ContractsDSL.requireThat; + +public class LoanBidContract implements Contract { + + // This is used to identify our contract when building a transaction. + public static final String ID = "net.corda.samples.lending.contracts.LoanBidContract"; + + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + // Contract Validation Logic Goes Here + final CommandData commandData = tx.getCommands().get(0).getValue(); + + if (commandData instanceof Commands.Submit) { + requireThat(require -> { + /* At here, the loan bid proposal from the competing banks is verified. + These contract rules make sure that each loan bid for project is valid. */ + return null; + }); + }else if (commandData instanceof Commands.Approve) { + requireThat(require -> { + /* At here, the loan bid is verified for approval process. These contract rules make + sure that all the conditions are met for the borrower to approve the sole loan bid for its + project. */ + return null; + }); + } + } + + // Used to indicate the transaction's intent. + public interface Commands extends CommandData { + class Submit implements ProjectContract.Commands {} + class Approve implements ProjectContract.Commands {} + } +} diff --git a/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/ProjectContract.java b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/ProjectContract.java new file mode 100644 index 00000000..3189f63e --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/ProjectContract.java @@ -0,0 +1,40 @@ +package net.corda.samples.lending.contracts; + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import org.jetbrains.annotations.NotNull; + +import static net.corda.core.contracts.ContractsDSL.requireThat; + +// ************ +// * Contract * +// ************ +public class ProjectContract implements Contract { + + // This is used to identify our contract when building a transaction. + public static final String ID = "net.corda.samples.lending.contracts.ProjectContract"; + + + // A transaction is valid if the verify() function of the contract of all the transaction's input and output states + // does not throw an exception. + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + // Contract Validation Logic Goes Here + final CommandData commandData = tx.getCommands().get(0).getValue(); + + if (commandData instanceof Commands.ProposeProject) { + requireThat(require -> { + /*At here, you can structure the rules for creating a project proposal + * this verify method makes sure that all proposed projects from the borrower company + * are sound, so that banks are not going to waste any time on unqualified project proposals*/ + return null; + }); + } + } + + // Used to indicate the transaction's intent. + public interface Commands extends CommandData { + class ProposeProject implements Commands {} + } +} \ No newline at end of file diff --git a/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/SyndicateBidContract.java b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/SyndicateBidContract.java new file mode 100644 index 00000000..bd3738c2 --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/SyndicateBidContract.java @@ -0,0 +1,39 @@ +package net.corda.samples.lending.contracts; + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import org.jetbrains.annotations.NotNull; + +import static net.corda.core.contracts.ContractsDSL.requireThat; + +public class SyndicateBidContract implements Contract { + + // This is used to identify our contract when building a transaction. + public static final String ID = "net.corda.samples.lending.contracts.SyndicateBidContract"; + + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + // Contract Validation Logic Goes Here + final CommandData commandData = tx.getCommands().get(0).getValue(); + + if (commandData instanceof Commands.Submit) { + requireThat(require -> { + /* At here, the syndication bid proposal from the syndication participating banks is verified. + These contract rules make sure that each bid for syndicated loan is valid. */ + return null; + }); + }else if (commandData instanceof Commands.Approve) { + requireThat(require -> { + /* At here, the syndicated bid is verified for approval process. These contract rules make + sure that all the conditions are met for the lead bank to approve the each syndicated bid. */ + return null; + }); + } + } + + public interface Commands extends CommandData { + class Submit implements Commands {} + class Approve implements Commands {} + } +} diff --git a/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/SyndicateContract.java b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/SyndicateContract.java new file mode 100644 index 00000000..d2c15f9c --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/SyndicateContract.java @@ -0,0 +1,38 @@ +package net.corda.samples.lending.contracts; + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.transactions.LedgerTransaction; +import net.corda.samples.lending.states.LoanBidState; +import net.corda.samples.lending.states.SyndicateState; +import org.jetbrains.annotations.NotNull; + +import static net.corda.core.contracts.ContractsDSL.requireThat; + +public class SyndicateContract implements Contract { + + // This is used to identify our contract when building a transaction. + public static final String ID = "net.corda.samples.lending.contracts.SyndicateContract"; + + + // A transaction is valid if the verify() function of the contract of all the transaction's input and output states + // does not throw an exception. + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + // Contract Validation Logic Goes Here + final CommandData commandData = tx.getCommands().get(0).getValue(); + + if (commandData instanceof Commands.Create) { + requireThat(require -> { + /*Here writes the rules for the lead bank's creating the syndication.*/ + + return null; + }); + } + } + + public interface Commands extends CommandData { + class Create implements Commands {} + } +} diff --git a/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/LoanBidState.java b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/LoanBidState.java new file mode 100644 index 00000000..7be802bc --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/LoanBidState.java @@ -0,0 +1,82 @@ +package net.corda.samples.lending.states; + +import net.corda.core.contracts.*; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.samples.lending.contracts.LoanBidContract; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +@BelongsToContract(LoanBidContract.class) +public class LoanBidState implements LinearState { + + private StaticPointer projectDetails; + private UniqueIdentifier uniqueIdentifier; + private Party lender; + private Party borrower; + private int loanAmount; + private int tenure; + private double rateofInterest; + private int transactionFees; + private String status; + + public LoanBidState(StaticPointer projectDetails, UniqueIdentifier uniqueIdentifier, + Party lender, Party borrower, int loanAmount, int tenure, + double rateofInterest, int transactionFees, String status) { + this.projectDetails = projectDetails; + this.uniqueIdentifier = uniqueIdentifier; + this.lender = lender; + this.borrower = borrower; + this.loanAmount = loanAmount; + this.tenure = tenure; + this.rateofInterest = rateofInterest; + this.transactionFees = transactionFees; + this.status = status; + } + + public StaticPointer getProjectDetails() { + return projectDetails; + } + + public Party getLender() { + return lender; + } + + public Party getBorrower() { + return borrower; + } + + public int getLoanAmount() { + return loanAmount; + } + + public int getTenure() { + return tenure; + } + + public double getRateofInterest() { + return rateofInterest; + } + + public int getTransactionFees() { + return transactionFees; + } + + public String getStatus() { + return status; + } + + @NotNull + @Override + public List getParticipants() { + return Arrays.asList(lender, borrower); + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return uniqueIdentifier; + } +} diff --git a/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/ProjectState.java b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/ProjectState.java new file mode 100644 index 00000000..a4240e05 --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/ProjectState.java @@ -0,0 +1,77 @@ +package net.corda.samples.lending.states; + +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.LinearState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.samples.lending.contracts.ProjectContract; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +// ********* +// * ProposalState * +// ********* +@BelongsToContract(ProjectContract.class) +public class ProjectState implements LinearState { + + //private variables + private UniqueIdentifier uniqueIdentifier; + private String projectDescription; + private Party borrower; + private int projectCost; + private int loanAmount; + private List lenders; + + /* Constructor of your Corda state */ + + public ProjectState(UniqueIdentifier uniqueIdentifier, String projectDescription, Party borrower, + int projectCost, int loanAmount, List lenders) { + this.uniqueIdentifier = uniqueIdentifier; + this.projectDescription = projectDescription; + this.borrower = borrower; + this.projectCost = projectCost; + this.loanAmount = loanAmount; + this.lenders = lenders; + } + + // getters + + public String getProjectDescription() { + return projectDescription; + } + + public Party getBorrower() { + return borrower; + } + + public int getProjectCost() { + return projectCost; + } + + public int getLoanAmount() { + return loanAmount; + } + + public List getBidders() { + return lenders; + } + + /* This method will indicate who are the participants when + * this state is used in a transaction. */ + @Override + public List getParticipants() { + List particiants = new ArrayList<>(); + particiants.add(borrower); + particiants.addAll(lenders); + return particiants; + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return uniqueIdentifier; + } +} \ No newline at end of file diff --git a/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/SyndicateBidState.java b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/SyndicateBidState.java new file mode 100644 index 00000000..204289ba --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/SyndicateBidState.java @@ -0,0 +1,66 @@ +package net.corda.samples.lending.states; + +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.LinearPointer; +import net.corda.core.contracts.LinearState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.samples.lending.contracts.SyndicateBidContract; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +@BelongsToContract(SyndicateBidContract.class) +public class SyndicateBidState implements LinearState { + + private UniqueIdentifier uniqueIdentifier; + private LinearPointer syndicateState; + private int bidAmount; + private Party leadBank; + private Party participatBank; + private String status; + + public SyndicateBidState(UniqueIdentifier uniqueIdentifier, LinearPointer syndicateState, int bidAmount, + Party leadBank, Party participatBank, String status) { + this.uniqueIdentifier = uniqueIdentifier; + this.syndicateState = syndicateState; + this.bidAmount = bidAmount; + this.leadBank = leadBank; + this.participatBank = participatBank; + this.status = status; + } + + public LinearPointer getSyndicateState() { + return syndicateState; + } + + public int getBidAmount() { + return bidAmount; + } + + public Party getLeadBank() { + return leadBank; + } + + public Party getParticipatBank() { + return participatBank; + } + + public String getStatus() { + return status; + } + + @NotNull + @Override + public List getParticipants() { + return Arrays.asList(participatBank, leadBank); + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return uniqueIdentifier; + } +} diff --git a/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/SyndicateState.java b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/SyndicateState.java new file mode 100644 index 00000000..0787b300 --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/SyndicateState.java @@ -0,0 +1,62 @@ +package net.corda.samples.lending.states; + +import net.corda.core.contracts.*; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.samples.lending.contracts.SyndicateContract; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +@BelongsToContract(SyndicateContract.class) +public class SyndicateState implements LinearState { + + private UniqueIdentifier uniqueIdentifier; + private Party leadBank; + private List participantBanks; + private LinearPointer projectDetails; + private LinearPointer loanDetails; + + public SyndicateState(UniqueIdentifier uniqueIdentifier, Party leadBank, List participantBanks, + LinearPointer projectDetails, + LinearPointer loanDetails) { + this.uniqueIdentifier = uniqueIdentifier; + this.leadBank = leadBank; + this.participantBanks = participantBanks; + this.projectDetails = projectDetails; + this.loanDetails = loanDetails; + } + + public Party getLeadBank() { + return leadBank; + } + + public List getParticipantBanks() { + return participantBanks; + } + + public LinearPointer getProjectDetails() { + return projectDetails; + } + + public LinearPointer getLoanDetails() { + return loanDetails; + } + + + @NotNull + @Override + public List getParticipants() { + List particiants = new ArrayList<>(); + particiants.add(leadBank); + particiants.addAll(participantBanks); + return particiants; + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return uniqueIdentifier; + } +} diff --git a/Advanced/syndicated-lending/contracts/src/test/java/net/corda/samples/lending/contracts/ContractTests.java b/Advanced/syndicated-lending/contracts/src/test/java/net/corda/samples/lending/contracts/ContractTests.java new file mode 100644 index 00000000..04ad1485 --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/test/java/net/corda/samples/lending/contracts/ContractTests.java @@ -0,0 +1,18 @@ +package net.corda.samples.lending.contracts; + +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.node.MockServices; + +import java.util.Arrays; + +import static net.corda.testing.node.NodeTestUtils.ledger; + + +public class ContractTests { + private final MockServices ledgerServices = new MockServices(Arrays.asList("net.corda.samples.lending")); + TestIdentity alice = new TestIdentity(new CordaX500Name("Alice", "TestLand", "US")); + TestIdentity bob = new TestIdentity(new CordaX500Name("Alice", "TestLand", "US")); + + +} \ No newline at end of file diff --git a/Advanced/syndicated-lending/contracts/src/test/java/net/corda/samples/lending/contracts/StateTests.java b/Advanced/syndicated-lending/contracts/src/test/java/net/corda/samples/lending/contracts/StateTests.java new file mode 100644 index 00000000..e886d011 --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/test/java/net/corda/samples/lending/contracts/StateTests.java @@ -0,0 +1,8 @@ +package net.corda.samples.lending.contracts; + +import net.corda.samples.lending.states.ProjectState; +import org.junit.Test; + +public class StateTests { + +} \ No newline at end of file diff --git a/Advanced/syndicated-lending/gradle.properties b/Advanced/syndicated-lending/gradle.properties new file mode 100644 index 00000000..60bb519a --- /dev/null +++ b/Advanced/syndicated-lending/gradle.properties @@ -0,0 +1,3 @@ +name=Test +group=net.corda.samples.lending +version=0.1 \ No newline at end of file diff --git a/Advanced/syndicated-lending/gradle/wrapper/gradle-wrapper.jar b/Advanced/syndicated-lending/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..99340b4a Binary files /dev/null and b/Advanced/syndicated-lending/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Advanced/syndicated-lending/gradle/wrapper/gradle-wrapper.properties b/Advanced/syndicated-lending/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..0188225a --- /dev/null +++ b/Advanced/syndicated-lending/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +#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-5.6.4-all.zip + diff --git a/Advanced/syndicated-lending/gradlew b/Advanced/syndicated-lending/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/Advanced/syndicated-lending/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/Advanced/syndicated-lending/gradlew.bat b/Advanced/syndicated-lending/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/Advanced/syndicated-lending/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/Advanced/syndicated-lending/repositories.gradle b/Advanced/syndicated-lending/repositories.gradle new file mode 100644 index 00000000..8be7b630 --- /dev/null +++ b/Advanced/syndicated-lending/repositories.gradle @@ -0,0 +1,8 @@ +repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://jitpack.io' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } +} diff --git a/Advanced/syndicated-lending/settings.gradle b/Advanced/syndicated-lending/settings.gradle new file mode 100644 index 00000000..b4446eaf --- /dev/null +++ b/Advanced/syndicated-lending/settings.gradle @@ -0,0 +1,2 @@ +include 'workflows' +include 'contracts' \ No newline at end of file diff --git a/Advanced/syndicated-lending/workflows/build.gradle b/Advanced/syndicated-lending/workflows/build.gradle new file mode 100644 index 00000000..8a229424 --- /dev/null +++ b/Advanced/syndicated-lending/workflows/build.gradle @@ -0,0 +1,64 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + workflow { + name "Lending Flows" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/dev") + } + } + test { + java { + srcDir 'src/test/java' + java.outputDir = file('bin/main') + } + 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_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":contracts") +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} \ No newline at end of file diff --git a/Advanced/syndicated-lending/workflows/src/integrationTest/java/net/corda/samples/lending/DriverBasedTest.java b/Advanced/syndicated-lending/workflows/src/integrationTest/java/net/corda/samples/lending/DriverBasedTest.java new file mode 100644 index 00000000..85ec790f --- /dev/null +++ b/Advanced/syndicated-lending/workflows/src/integrationTest/java/net/corda/samples/lending/DriverBasedTest.java @@ -0,0 +1,48 @@ +package net.corda.samples.lending; + +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/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/ApproveLoanBidFlow.java b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/ApproveLoanBidFlow.java new file mode 100644 index 00000000..776a85c6 --- /dev/null +++ b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/ApproveLoanBidFlow.java @@ -0,0 +1,112 @@ +package net.corda.samples.lending.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.StaticPointer; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.lending.contracts.LoanBidContract; +import net.corda.samples.lending.states.LoanBidState; +import net.corda.samples.lending.states.ProjectState; + +import java.util.Arrays; +import java.util.List; + +public class ApproveLoanBidFlow { + + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic { + + private UniqueIdentifier bidIdentifier; + + //public constructor + public Initiator(UniqueIdentifier bidIdentifier) { + this.bidIdentifier = bidIdentifier; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + List> loanBidStateAndRefs = getServiceHub().getVaultService() + .queryBy(LoanBidState.class).getStates(); + + StateAndRef inputStateAndRef = loanBidStateAndRefs.stream().filter(loanBidStateAndRef -> { + LoanBidState loanBidState = loanBidStateAndRef.getState().getData(); + return loanBidState.getLinearId().equals(bidIdentifier); + }).findAny().orElseThrow(() -> new IllegalArgumentException("Loan Bid Not Found")); + + LoanBidState inputState = inputStateAndRef.getState().getData(); + + final LoanBidState output = new LoanBidState( + new StaticPointer<>(inputStateAndRef.getRef(), ProjectState.class), + inputState.getLinearId(), inputState.getLender(), inputState.getBorrower(), + inputState.getLoanAmount(), inputState.getTenure(), inputState.getRateofInterest(), + inputState.getTransactionFees(), "APPROVED" + ); + + Party notary = inputStateAndRef.getState().getNotary(); + + // Step 3. Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // Step 4. Add the inputs and outputs, as well as a command to the transaction builder. + builder.addInputState(inputStateAndRef); + builder.addOutputState(output); + builder.addCommand(new LoanBidContract.Commands.Approve(), Arrays.asList(inputState.getBorrower().getOwningKey(),inputState.getLender().getOwningKey())); + + // Step 5. Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + FlowSession cpSession = initiateFlow(inputState.getLender()); + + //step 6: collect signatures + SignedTransaction stx = subFlow(new CollectSignaturesFlow(ptx, Arrays.asList(cpSession))); + + + // Step 7. Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(stx, Arrays.asList(cpSession))); + } + } + + @InitiatedBy(Initiator.class) + public static class Responder extends FlowLogic { + //private variable + private FlowSession counterpartySession; + + //Constructor + public Responder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + /* + * SignTransactionFlow will automatically verify the transaction and its signatures before signing it. + * However, just because a transaction is contractually valid doesn’t mean we necessarily want to sign. + * What if we don’t want to deal with the counterparty in question, or the value is too high, + * or we’re not happy with the transaction’s structure? checkTransaction + * allows us to define these additional checks. If any of these conditions are not met, + * we will not sign the transaction - even if the transaction and its signatures are contractually valid. + * ---------- + * For this hello-world cordapp, we will not implement any aditional checks. + * */ + } + }); + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } + } + +} diff --git a/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/ApproveSyndicateBidFlow.java b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/ApproveSyndicateBidFlow.java new file mode 100644 index 00000000..b1702d49 --- /dev/null +++ b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/ApproveSyndicateBidFlow.java @@ -0,0 +1,113 @@ +package net.corda.samples.lending.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.lending.contracts.SyndicateBidContract; +import net.corda.samples.lending.states.SyndicateBidState; + +import java.util.Arrays; +import java.util.List; + +public class ApproveSyndicateBidFlow { + + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic { + private UniqueIdentifier bidIdentifier; + + //public constructor + public Initiator(UniqueIdentifier bidIdentifier) { + this.bidIdentifier = bidIdentifier; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + // Fetch Project + List> syndicateBidStateAndRefs = getServiceHub().getVaultService() + .queryBy(SyndicateBidState.class).getStates(); + + StateAndRef syndicateBidStateAndRef = syndicateBidStateAndRefs.stream().filter(synStateAndRef -> { + SyndicateBidState syndicateBidState = synStateAndRef.getState().getData(); + return syndicateBidState.getLinearId().equals(bidIdentifier); + }).findAny().orElseThrow(() -> new IllegalArgumentException("Syndicate Bid Not Found")); + + SyndicateBidState inputState = syndicateBidStateAndRef.getState().getData(); + + SyndicateBidState outputState = new SyndicateBidState( + inputState.getLinearId(), + inputState.getSyndicateState(), + inputState.getBidAmount(), + inputState.getLeadBank(), + inputState.getParticipatBank(), + "APPROVED" + ); + + Party notary = syndicateBidStateAndRef.getState().getNotary(); + + + // Step 3. Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // Step 4. Add the input and output state, as well as a command to the transaction builder. + builder.addInputState(syndicateBidStateAndRef); + builder.addOutputState(outputState); + builder.addCommand(new SyndicateBidContract.Commands.Submit(), + Arrays.asList(getOurIdentity().getOwningKey(),inputState.getParticipatBank().getOwningKey())); + + // Step 5. Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + FlowSession cpSession = initiateFlow(inputState.getParticipatBank()); + + //step 6: collect signatures + SignedTransaction stx = subFlow(new CollectSignaturesFlow(ptx, Arrays.asList(cpSession))); + + // Step 7. Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(stx, Arrays.asList(cpSession))); + } + } + + @InitiatedBy(Initiator.class) + public static class Responder extends FlowLogic { + //private variable + private FlowSession counterpartySession; + + //Constructor + public Responder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + /* + * SignTransactionFlow will automatically verify the transaction and its signatures before signing it. + * However, just because a transaction is contractually valid doesn’t mean we necessarily want to sign. + * What if we don’t want to deal with the counterparty in question, or the value is too high, + * or we’re not happy with the transaction’s structure? checkTransaction + * allows us to define these additional checks. If any of these conditions are not met, + * we will not sign the transaction - even if the transaction and its signatures are contractually valid. + * ---------- + * For this hello-world cordapp, we will not implement any aditional checks. + * */ + } + }); + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } + } + +} diff --git a/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/CreateSyndicateFlow.java b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/CreateSyndicateFlow.java new file mode 100644 index 00000000..bdf1c264 --- /dev/null +++ b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/CreateSyndicateFlow.java @@ -0,0 +1,116 @@ +package net.corda.samples.lending.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.LinearPointer; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.StaticPointer; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.node.StatesToRecord; +import net.corda.core.node.services.Vault; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.lending.contracts.SyndicateContract; +import net.corda.samples.lending.states.LoanBidState; +import net.corda.samples.lending.states.ProjectState; +import net.corda.samples.lending.states.SyndicateState; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class CreateSyndicateFlow { + + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic { + + private Party leadBank; + private List participantBanks; + private UniqueIdentifier projectIdentifier; + private UniqueIdentifier loanDetailIdentifier; + + //public constructor + public Initiator(List participantBanks, UniqueIdentifier projectIdentifier, UniqueIdentifier loanDetailIdentifier) { + this.participantBanks = participantBanks; + this.projectIdentifier = projectIdentifier; + this.loanDetailIdentifier = loanDetailIdentifier; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + // Step 1. Get a reference to the notary service on our network and our key pair. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + + this.leadBank = getOurIdentity(); + + // Fetch Project + List> projectStateAndRefs = getServiceHub().getVaultService() + .queryBy(ProjectState.class).getStates(); + + StateAndRef projectDetailStateAndRef = projectStateAndRefs.stream().filter(projectStateAndRef -> { + ProjectState projectState = projectStateAndRef.getState().getData(); + return projectState.getLinearId().equals(projectIdentifier); + }).findAny().orElseThrow(() -> new IllegalArgumentException("Project Not Found")); + + // Fetch Loan + List> loanBidStateAndRefs = getServiceHub().getVaultService() + .queryBy(LoanBidState.class).getStates(); + + StateAndRef loanStateAndRef = loanBidStateAndRefs.stream().filter(loanBidStateAndRef -> { + LoanBidState loanBidState = loanBidStateAndRef.getState().getData(); + return loanBidState.getLinearId().equals(loanDetailIdentifier); + }).findAny().orElseThrow(() -> new IllegalArgumentException("Loan Details Not Found")); + + SyndicateState syndicateState = new SyndicateState( + new UniqueIdentifier(), leadBank, participantBanks, + new LinearPointer<>(projectIdentifier, ProjectState.class), + new LinearPointer<>(loanDetailIdentifier, LoanBidState.class) + //Collections.emptyList() + ); + + + // Step 3. Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // Step 4. Add the project as an output state, as well as a command to the transaction builder. + builder.addOutputState(syndicateState); + builder.addCommand(new SyndicateContract.Commands.Create(), Arrays.asList(syndicateState.getLeadBank().getOwningKey())); + + // Step 5. Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + List cpSesions = participantBanks.stream().map(this::initiateFlow).collect(Collectors.toList()); + + // Step 7. Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(ptx, cpSesions)); + } + } + + @InitiatedBy(Initiator.class) + public static class Responder extends FlowLogic { + //private variable + private FlowSession counterpartySession; + + //Constructor + public Responder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, null, StatesToRecord.ALL_VISIBLE)); + return null; + } + } + +} diff --git a/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/SubmitLoanBidFlow.java b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/SubmitLoanBidFlow.java new file mode 100644 index 00000000..fe2f9980 --- /dev/null +++ b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/SubmitLoanBidFlow.java @@ -0,0 +1,108 @@ +package net.corda.samples.lending.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.StatePointer; +import net.corda.core.contracts.StaticPointer; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.lending.contracts.LoanBidContract; +import net.corda.samples.lending.contracts.ProjectContract; +import net.corda.samples.lending.states.LoanBidState; +import net.corda.samples.lending.states.ProjectState; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class SubmitLoanBidFlow { + + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic{ + + private Party borrower; + private Party lender; + private int loanAmount; + private int tenure; + private double rateofInterest; + private int transactionFees; + private UniqueIdentifier projectIdentifier; + + //public constructor + public Initiator(Party borrower, int loanAmount, int tenure, double rateofInterest, int transactionFees, + UniqueIdentifier projectIdentifier) { + this.borrower = borrower; + this.loanAmount = loanAmount; + this.tenure = tenure; + this.rateofInterest = rateofInterest; + this.transactionFees = transactionFees; + this.projectIdentifier = projectIdentifier; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + this.lender = getOurIdentity(); + + + List> projectStateAndRefs = getServiceHub().getVaultService() + .queryBy(ProjectState.class).getStates(); + + StateAndRef inputStateAndRef = projectStateAndRefs.stream().filter(projectStateAndRef -> { + ProjectState projectState = projectStateAndRef.getState().getData(); + return projectState.getLinearId().equals(projectIdentifier); + }).findAny().orElseThrow(() -> new IllegalArgumentException("Project Not Found")); + + Party notary = inputStateAndRef.getState().getNotary(); + + final LoanBidState output = new LoanBidState( + new StaticPointer<>(inputStateAndRef.getRef(), ProjectState.class), + new UniqueIdentifier(), + lender, borrower, + loanAmount, tenure, rateofInterest, transactionFees, + "SUBMITTED" + ); + + // Step 3. Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // Step 4. Add the project as an output state, as well as a command to the transaction builder. + builder.addOutputState(output); + builder.addCommand(new LoanBidContract.Commands.Submit(), Arrays.asList(lender.getOwningKey())); + + // Step 5. Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + FlowSession cpSession = initiateFlow(borrower); + + // Step 7. Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(ptx, Arrays.asList(cpSession))); + } + } + + @InitiatedBy(Initiator.class) + public static class Responder extends FlowLogic{ + //private variable + private FlowSession counterpartySession; + + //Constructor + public Responder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession)); + return null; + } + } + +} diff --git a/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/SubmitProjectProposalFlow.java b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/SubmitProjectProposalFlow.java new file mode 100644 index 00000000..036f7f18 --- /dev/null +++ b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/SubmitProjectProposalFlow.java @@ -0,0 +1,90 @@ +package net.corda.samples.lending.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.lending.contracts.ProjectContract; +import net.corda.samples.lending.states.ProjectState; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class SubmitProjectProposalFlow { + + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic{ + + private Party borrower; + private List lenders; + private String projectDescription; + private int projectCost; + private int loanAmount; + + //public constructor + public Initiator(List lenders, String projectDescription, int projectCost, int loanAmount) { + this.lenders = lenders; + this.projectDescription = projectDescription; + this.projectCost = projectCost; + this.loanAmount = loanAmount; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + this.borrower = getOurIdentity(); + + // Step 1. Get a reference to the notary service on our network and our key pair. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + + //Compose the State that carries the Hello World message + final ProjectState output = new ProjectState(new UniqueIdentifier(), projectDescription, borrower, + projectCost, loanAmount, lenders); + + // Step 3. Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // Step 4. Add the project as an output state, as well as a command to the transaction builder. + builder.addOutputState(output); + builder.addCommand(new ProjectContract.Commands.ProposeProject(), Collections.singletonList(borrower.getOwningKey())); + + // Step 5. Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + List cpSesions = lenders.stream().map(this::initiateFlow).collect(Collectors.toList()); + + // Step 7. Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(ptx, cpSesions)); + } + } + + @InitiatedBy(Initiator.class) + public static class Responder extends FlowLogic{ + //private variable + private FlowSession counterpartySession; + + //Constructor + public Responder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession)); + return null; + } + } + +} diff --git a/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/SubmitSyndicateBidFlow.java b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/SubmitSyndicateBidFlow.java new file mode 100644 index 00000000..b1740ec6 --- /dev/null +++ b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/SubmitSyndicateBidFlow.java @@ -0,0 +1,97 @@ +package net.corda.samples.lending.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.LinearPointer; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.lending.contracts.SyndicateBidContract; +import net.corda.samples.lending.states.SyndicateBidState; +import net.corda.samples.lending.states.SyndicateState; + +import java.util.Arrays; +import java.util.List; + +public class SubmitSyndicateBidFlow { + + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic { + private UniqueIdentifier syndicateIdentifier; + private int bidAmount; + + //public constructor + public Initiator(UniqueIdentifier syndicateIdentifier, int bidAmount) { + this.syndicateIdentifier = syndicateIdentifier; + this.bidAmount = bidAmount; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + // Fetch Project + List> syndicateStateAndRefs = getServiceHub().getVaultService() + .queryBy(SyndicateState.class).getStates(); + + StateAndRef syndicateStateAndRef = syndicateStateAndRefs.stream().filter(synStateAndRef -> { + SyndicateState syndicateState = synStateAndRef.getState().getData(); + return syndicateState.getLinearId().equals(syndicateIdentifier); + }).findAny().orElseThrow(() -> new IllegalArgumentException("Syndicate Not Found")); + + SyndicateState syndicateState = syndicateStateAndRef.getState().getData(); + + SyndicateBidState syndicateBidState = new SyndicateBidState( + new UniqueIdentifier(), + new LinearPointer<>(syndicateIdentifier, SyndicateState.class), + bidAmount, + syndicateState.getLeadBank(), + getOurIdentity(), + "SUBMITTED" + ); + + Party notary = syndicateStateAndRef.getState().getNotary(); + + // Step 3. Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // Step 4. Add the project as an output state, as well as a command to the transaction builder. + builder.addOutputState(syndicateBidState); + builder.addCommand(new SyndicateBidContract.Commands.Submit(), Arrays.asList(getOurIdentity().getOwningKey())); + + // Step 5. Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + FlowSession cpSession = initiateFlow(syndicateState.getLeadBank()); + + // Step 7. Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(ptx, Arrays.asList(cpSession))); + } + } + + @InitiatedBy(Initiator.class) + public static class Responder extends FlowLogic { + //private variable + private FlowSession counterpartySession; + + //Constructor + public Responder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession)); + return null; + } + } + +} diff --git a/Advanced/syndicated-lending/workflows/src/test/java/net/corda/samples/lending/AllInOneTester.java b/Advanced/syndicated-lending/workflows/src/test/java/net/corda/samples/lending/AllInOneTester.java new file mode 100644 index 00000000..bfe225ec --- /dev/null +++ b/Advanced/syndicated-lending/workflows/src/test/java/net/corda/samples/lending/AllInOneTester.java @@ -0,0 +1,113 @@ +package net.corda.samples.lending; + +import com.google.common.collect.ImmutableList; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.node.NetworkParameters; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; +import net.corda.samples.lending.flows.*; +import net.corda.samples.lending.states.LoanBidState; +import net.corda.samples.lending.states.ProjectState; +import net.corda.samples.lending.states.SyndicateBidState; +import net.corda.samples.lending.states.SyndicateState; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.concurrent.Future; + +import static org.junit.Assert.assertEquals; + +public class AllInOneTester { + + private MockNetwork network; + private StartedMockNode bankA; + private StartedMockNode bankB; + private StartedMockNode bankC; + private StartedMockNode borrower; + private NetworkParameters testNetworkParameters = + new NetworkParameters(4, Arrays.asList(), 10485760, (10485760 * 5), Instant.now(),1, new LinkedHashMap<>()); + @Before + public void setup() { + network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.lending.contracts"), + TestCordapp.findCordapp("net.corda.samples.lending.flows"))) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + .withNetworkParameters(testNetworkParameters)); + borrower = network.createPartyNode(null); + bankA = network.createPartyNode(null); + bankB = network.createPartyNode(null); + bankC = network.createPartyNode(null); + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void AllBusinessLogicFlowTests() { + //Create the project + List lenders = new ArrayList(); + lenders.add(bankA.getInfo().getLegalIdentities().get(0)); + lenders.add(bankB.getInfo().getLegalIdentities().get(0)); + SubmitProjectProposalFlow.Initiator createProjectFlow = new SubmitProjectProposalFlow.Initiator(lenders, "oversea expansion", 100, 10); + Future future = borrower.startFlow(createProjectFlow); + network.runNetwork(); + + //successful query means the state is stored at node b's vault. Flow went through. + QueryCriteria inputCriteria = new QueryCriteria.VaultQueryCriteria().withStatus(Vault.StateStatus.UNCONSUMED); + ProjectState project = bankA.getServices().getVaultService().queryBy(ProjectState.class,inputCriteria).getStates().get(0).getState().getData(); + + //place loan bid + UniqueIdentifier projectID = project.getLinearId(); + SubmitLoanBidFlow.Initiator submitLoanBidFlow = new SubmitLoanBidFlow.Initiator(borrower.getInfo().getLegalIdentities().get(0), 7, 5, 4.0, 1, projectID); + Future future2 = bankA.startFlow(submitLoanBidFlow); + network.runNetwork(); + LoanBidState loanBid = borrower.getServices().getVaultService().queryBy(LoanBidState.class,inputCriteria).getStates().get(0).getState().getData(); + + //Borrower approves the loan bid. pick the winning bank + UniqueIdentifier loanBidId = loanBid.getLinearId(); + ApproveLoanBidFlow.Initiator approveLoanBidFlow = new ApproveLoanBidFlow.Initiator(loanBidId); + Future future3 = borrower.startFlow(approveLoanBidFlow); + network.runNetwork(); + LoanBidState loanBidApproved = bankA.getServices().getVaultService().queryBy(LoanBidState.class,inputCriteria).getStates().get(0).getState().getData(); + assertEquals("APPROVED", loanBidApproved.getStatus()); + + //lead bank create Syndication + UniqueIdentifier approveLoanBidID = loanBidApproved.getLinearId(); + List participatingBanks = new ArrayList(); + participatingBanks.add(bankB.getInfo().getLegalIdentities().get(0)); + participatingBanks.add(bankC.getInfo().getLegalIdentities().get(0)); + CreateSyndicateFlow.Initiator createSyndication = new CreateSyndicateFlow.Initiator(participatingBanks, projectID, approveLoanBidID); + Future future4 = bankA.startFlow(createSyndication); + network.runNetwork(); + SyndicateState syndication = bankA.getServices().getVaultService().queryBy(SyndicateState.class,inputCriteria).getStates().get(0).getState().getData(); + + //participating bank submit syndication bid + UniqueIdentifier syndicationID = syndication.getLinearId(); + SubmitSyndicateBidFlow.Initiator submitSyndicationBid = new SubmitSyndicateBidFlow.Initiator(syndicationID,2); + Future future5 = bankB.startFlow(submitSyndicationBid); + network.runNetwork(); + SyndicateBidState syndicatedBid = bankA.getServices().getVaultService().queryBy(SyndicateBidState.class,inputCriteria).getStates().get(0).getState().getData(); + + //Lead bank approves syndication bid + UniqueIdentifier syndicationBidId = syndicatedBid.getLinearId(); + ApproveSyndicateBidFlow.Initiator approveSyndicationBid = new ApproveSyndicateBidFlow.Initiator(syndicationBidId); + Future future6 = bankA.startFlow(approveSyndicationBid); + network.runNetwork(); + SyndicateBidState syndicatedBidApproved = bankA.getServices().getVaultService().queryBy(SyndicateBidState.class,inputCriteria).getStates().get(0).getState().getData(); + assertEquals("APPROVED", syndicatedBidApproved.getStatus()); + + } +} diff --git a/Advanced/syndicated-lending/workflows/src/test/java/net/corda/samples/lending/FlowTests.java b/Advanced/syndicated-lending/workflows/src/test/java/net/corda/samples/lending/FlowTests.java new file mode 100644 index 00000000..2f2ab817 --- /dev/null +++ b/Advanced/syndicated-lending/workflows/src/test/java/net/corda/samples/lending/FlowTests.java @@ -0,0 +1,104 @@ +package net.corda.samples.lending; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.transactions.SignedTransaction; +import net.corda.samples.lending.flows.ApproveLoanBidFlow; +import net.corda.samples.lending.flows.SubmitLoanBidFlow; +import net.corda.samples.lending.flows.SubmitProjectProposalFlow; +import net.corda.samples.lending.states.LoanBidState; +import net.corda.samples.lending.states.ProjectState; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; + +import static org.junit.Assert.*; + +public class FlowTests { + private MockNetwork network; + private StartedMockNode a; + private StartedMockNode b; + private StartedMockNode c; + private StartedMockNode d; + + @Before + public void setup() { + network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.lending.contracts"), + TestCordapp.findCordapp("net.corda.samples.lending.flows"))) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB"))))); + a = network.createPartyNode(null); + b = network.createPartyNode(null); + c = network.createPartyNode(null); + d = network.createPartyNode(null); + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + private ProjectState createProject() throws Exception{ + SubmitProjectProposalFlow.Initiator flow = new SubmitProjectProposalFlow.Initiator( + Arrays.asList( + b.getInfo().getLegalIdentities().get(0), + c.getInfo().getLegalIdentities().get(0)), + "Test Project", 10000000, 8000000); + CordaFuture future = a.startFlow(flow); + network.runNetwork(); + SignedTransaction transaction = future.get(); + ProjectState projectState = (ProjectState) transaction.getTx().getOutput(0); + return projectState; + } + + private LoanBidState submitLoanBid(ProjectState projectState) throws Exception{ + SubmitLoanBidFlow.Initiator flow = new SubmitLoanBidFlow.Initiator( + projectState.getBorrower(), + 8000000, 5, 8, 20000, + projectState.getLinearId() + ); + CordaFuture future = b.startFlow(flow); + network.runNetwork(); + SignedTransaction transaction = future.get(); + LoanBidState loanBidState = (LoanBidState) transaction.getTx().getOutput(0); + return loanBidState; + } + + private LoanBidState approveLoanBid(LoanBidState loanBidState) throws Exception{ + ApproveLoanBidFlow.Initiator flow = new ApproveLoanBidFlow.Initiator(loanBidState.getLinearId()); + CordaFuture future = a.startFlow(flow); + network.runNetwork(); + SignedTransaction transaction = future.get(); + LoanBidState loanBidStateUpdated = (LoanBidState) transaction.getTx().getOutput(0); + return loanBidStateUpdated; + } + + @Test + public void testSubmitProjectProposalFlow() throws Exception{ + ProjectState projectState = createProject(); + assertNotNull(projectState); + } + + @Test + public void testSubmitLoanBidFlow() throws Exception{ + ProjectState projectState = createProject(); + LoanBidState loanBidState = submitLoanBid(projectState); + assertNotNull(loanBidState); + } + + @Test + public void testApproveLoanBidFlow() throws Exception{ + ProjectState projectState = createProject(); + LoanBidState loanBidState = submitLoanBid(projectState); + LoanBidState loanBidStateApproved = approveLoanBid(loanBidState); + assertNotNull(loanBidStateApproved); + assertEquals("APPROVED", loanBidStateApproved.getStatus()); + } + + +} diff --git a/Basic/constants.properties b/Basic/constants.properties index 2cd5a28c..eb9e0ed4 100644 --- a/Basic/constants.properties +++ b/Basic/constants.properties @@ -1,12 +1,12 @@ cordaReleaseGroup=net.corda cordaCoreReleaseGroup=net.corda -cordaVersion=4.6 -cordaCoreVersion=4.6 +cordaVersion=4.10 +cordaCoreVersion=4.10 gradlePluginsVersion=5.0.12 kotlinVersion=1.2.71 junitVersion=4.12 quasarVersion=0.7.10 -log4jVersion =2.11.2 -platformVersion=8 +log4jVersion =2.17.1 +platformVersion=10 slf4jVersion=1.7.25 nettyVersion=4.1.22.Final diff --git a/Basic/cordapp-example/.idea/gradle.xml b/Basic/cordapp-example/.idea/gradle.xml index 9d21d136..7518eaeb 100644 --- a/Basic/cordapp-example/.idea/gradle.xml +++ b/Basic/cordapp-example/.idea/gradle.xml @@ -4,8 +4,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + @@ -210,30 +206,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Basic/cordapp-example/README.md b/Basic/cordapp-example/README.md index 68ddc4ed..f5c93e7f 100644 --- a/Basic/cordapp-example/README.md +++ b/Basic/cordapp-example/README.md @@ -1,7 +1,5 @@ -# cordapp-example +# CorDapp Example -This repo is the implementation of the tutorial cordapp. +This repo is the implementation of the tutorial CorDapp. -You can find that tutorial here: - -https://docs.corda.net/docs/corda-os/4.7/quickstart-deploy.html#running-the-example-cordapp +You can find the tutorial for this example [here](https://docs.r3.com/en/platform/corda/4.10/community/tutorial-cordapp.html). diff --git a/Basic/cordapp-example/build.gradle b/Basic/cordapp-example/build.gradle index 0aaa0341..4a2b09df 100644 --- a/Basic/cordapp-example/build.gradle +++ b/Basic/cordapp-example/build.gradle @@ -22,8 +22,8 @@ buildscript {//properties that you need to build the project repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -40,9 +40,8 @@ allprojects {//Properties that you need to compile your project (The application repositories { mavenLocal() - jcenter() mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } } @@ -69,11 +68,13 @@ sourceSets { } } } -//Module dependencis +//Module dependencies dependencies { // Corda dependencies. cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" - cordaCompile "$corda_release_group:corda-node-api:$corda_release_version" + cordaCompile ("$corda_release_group:corda-node-api:$corda_release_version") { + exclude group: "ch.qos.logback", module: "logback-classic" + } cordaRuntime "$corda_release_group:corda:$corda_release_version" // CorDapp dependencies. @@ -83,6 +84,8 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:$corda_release_version" + } //Task to deploy the nodes in order to bootstrap a network diff --git a/Basic/cordapp-example/clients/build.gradle b/Basic/cordapp-example/clients/build.gradle index e002aa21..bff3b3ff 100644 --- a/Basic/cordapp-example/clients/build.gradle +++ b/Basic/cordapp-example/clients/build.gradle @@ -10,7 +10,9 @@ sourceSets { dependencies { // Corda dependencies. - compile "$corda_release_group:corda-rpc:$corda_release_version" + compile ("$corda_release_group:corda-rpc:$corda_release_version") { + exclude group: "ch.qos.logback", module: "logback-classic" + } compile "net.corda:corda-jackson:$corda_release_version" // CorDapp dependencies. @@ -44,5 +46,11 @@ task runTestClient(type: JavaExec, dependsOn: assemble) { task runPartyAServer(type: JavaExec, dependsOn: assemble) { classpath = sourceSets.main.runtimeClasspath main = 'net.corda.samples.example.webserver.Starter' - args '--server.port=10050', '--config.rpc.host=localhost', '--config.rpc.port=10006', '--config.rpc.username=user1', '--config.rpc.password=test' + args '--server.port=50005', '--config.rpc.host=localhost', '--config.rpc.port=10006', '--config.rpc.username=user1', '--config.rpc.password=test' +} + +task runPartyBServer(type: JavaExec, dependsOn: assemble) { + classpath = sourceSets.main.runtimeClasspath + main = 'net.corda.samples.example.webserver.Starter' + args '--server.port=50006', '--config.rpc.host=localhost', '--config.rpc.port=10009', '--config.rpc.username=user1', '--config.rpc.password=test' } diff --git a/Basic/cordapp-example/contracts/src/main/java/net/corda/samples/example/contracts/IOUContract.java b/Basic/cordapp-example/contracts/src/main/java/net/corda/samples/example/contracts/IOUContract.java index e42f6347..59d9cc85 100644 --- a/Basic/cordapp-example/contracts/src/main/java/net/corda/samples/example/contracts/IOUContract.java +++ b/Basic/cordapp-example/contracts/src/main/java/net/corda/samples/example/contracts/IOUContract.java @@ -13,7 +13,7 @@ import static net.corda.core.contracts.ContractsDSL.requireThat; /** - * A implementation of a basic smart contracts in Corda. + * An implementation of a basic smart contract in Corda. * * This contracts enforces rules regarding the creation of a valid [IOUState], which in turn encapsulates an [IOU]. * @@ -22,13 +22,13 @@ * - One output states: the new [IOU]. * - An Create() command with the public keys of both the lender and the borrower. * - * All contracts must sub-class the [Contract] interface. + * All contracts must subclass the [Contract] interface. */ public class IOUContract implements Contract { public static final String ID = "net.corda.samples.example.contracts.IOUContract"; /** - * The verify() function of all the states' contracts must not throw an exception for a transaction to be + * The "verify()" function of all the states' contracts must not throw an exception for a transaction to be * considered valid. */ @Override diff --git a/Basic/cordapp-example/contracts/src/main/java/net/corda/samples/example/schema/IOUSchemaV1.java b/Basic/cordapp-example/contracts/src/main/java/net/corda/samples/example/schema/IOUSchemaV1.java index 3ef15e1c..7dde06a2 100644 --- a/Basic/cordapp-example/contracts/src/main/java/net/corda/samples/example/schema/IOUSchemaV1.java +++ b/Basic/cordapp-example/contracts/src/main/java/net/corda/samples/example/schema/IOUSchemaV1.java @@ -31,7 +31,7 @@ public String getMigrationResource() { public static class PersistentIOU extends PersistentState { @Column(name = "lender") private final String lender; @Column(name = "borrower") private final String borrower; - @Column(name = "value") private final int value; + @Column(name = "iou_value") private final int value; @Column(name = "linear_id") @Type (type = "uuid-char") private final UUID linearId; diff --git a/Basic/cordapp-example/contracts/src/main/java/net/corda/samples/example/states/IOUState.java b/Basic/cordapp-example/contracts/src/main/java/net/corda/samples/example/states/IOUState.java index 1f8708e9..ab966a81 100644 --- a/Basic/cordapp-example/contracts/src/main/java/net/corda/samples/example/states/IOUState.java +++ b/Basic/cordapp-example/contracts/src/main/java/net/corda/samples/example/states/IOUState.java @@ -30,6 +30,7 @@ public class IOUState implements LinearState, QueryableState { * @param value the value of the IOU. * @param lender the party issuing the IOU. * @param borrower the party receiving and approving the IOU. + * @param linearId the unique identifier of the IOU state. */ public IOUState(Integer value, Party lender, diff --git a/Basic/cordapp-example/repositories.gradle b/Basic/cordapp-example/repositories.gradle index 206b9c68..8be7b630 100644 --- a/Basic/cordapp-example/repositories.gradle +++ b/Basic/cordapp-example/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Basic/cordapp-example/workflows/src/main/java/net/corda/samples/example/flows/ExampleFlow.java b/Basic/cordapp-example/workflows/src/main/java/net/corda/samples/example/flows/ExampleFlow.java index b355aa7e..30ade19d 100644 --- a/Basic/cordapp-example/workflows/src/main/java/net/corda/samples/example/flows/ExampleFlow.java +++ b/Basic/cordapp-example/workflows/src/main/java/net/corda/samples/example/flows/ExampleFlow.java @@ -17,6 +17,7 @@ import java.util.Arrays; import static net.corda.core.contracts.ContractsDSL.requireThat; +import net.corda.core.identity.CordaX500Name; /** * This flow allows two parties (the [Initiator] and the [Acceptor]) to come to an agreement about the IOU encapsulated @@ -25,9 +26,9 @@ * In our simple example, the [Acceptor] always accepts a valid IOU. * * These flows have deliberately been implemented by using only the call() method for ease of understanding. In - * practice we would recommend splitting up the various stages of the flow into sub-routines. + * practice, we would recommend splitting up the various stages of the flow into sub-routines. * - * All methods called within the [FlowLogic] sub-class need to be annotated with the @Suspendable annotation. + * All methods called within the [FlowLogic] subclass need to be annotated with the @Suspendable annotation. */ public class ExampleFlow { @InitiatingFlow @@ -82,13 +83,8 @@ public ProgressTracker getProgressTracker() { public SignedTransaction call() throws FlowException { // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // Stage 1. progressTracker.setCurrentStep(GENERATING_TRANSACTION); diff --git a/Basic/cordapp-example/workflows/src/main/resources/migration/iou.changelog-v1.xml b/Basic/cordapp-example/workflows/src/main/resources/migration/iou.changelog-v1.xml index 660d7b3a..9d5e67b1 100644 --- a/Basic/cordapp-example/workflows/src/main/resources/migration/iou.changelog-v1.xml +++ b/Basic/cordapp-example/workflows/src/main/resources/migration/iou.changelog-v1.xml @@ -7,7 +7,7 @@ - + diff --git a/Basic/cordapp-example/workflows/src/test/java/net/corda/samples/example/FlowTests.java b/Basic/cordapp-example/workflows/src/test/java/net/corda/samples/example/FlowTests.java index 38a65289..7a8a0bf8 100644 --- a/Basic/cordapp-example/workflows/src/test/java/net/corda/samples/example/FlowTests.java +++ b/Basic/cordapp-example/workflows/src/test/java/net/corda/samples/example/FlowTests.java @@ -1,5 +1,6 @@ package net.corda.samples.example; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.example.flows.ExampleFlow; import net.corda.samples.example.states.IOUState; import com.google.common.collect.ImmutableList; @@ -9,10 +10,7 @@ import net.corda.core.contracts.TransactionState; import net.corda.core.contracts.TransactionVerificationException; import net.corda.core.transactions.SignedTransaction; -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 net.corda.testing.node.*; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -34,7 +32,9 @@ public void setup() { network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( TestCordapp.findCordapp("net.corda.samples.example.contracts"), - TestCordapp.findCordapp("net.corda.samples.example.flows")))); + TestCordapp.findCordapp("net.corda.samples.example.flows"))) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); a = network.createPartyNode(null); b = network.createPartyNode(null); network.runNetwork(); diff --git a/Basic/flow-database-access/README.md b/Basic/flow-database-access/README.md index ac15bbdf..9ab64c3b 100644 --- a/Basic/flow-database-access/README.md +++ b/Basic/flow-database-access/README.md @@ -1,5 +1,5 @@ # Flow Database Access CorDapp -This CorDapp provides a simple example of how the node database can be accessed in flows using a [JDBC Connection](https://docs.corda.net/docs/corda-os/api-persistence.html#jdbc-session). In this case, the flows +This CorDapp provides a simple example of how the node database can be accessed in flows using a [JDBC Connection](https://docs.r3.com/en/platform/corda/4.9/community/api-persistence.html#jdbc-session). In this case, the flows maintain a table of cryptocurrency values in the node's database. @@ -7,7 +7,7 @@ maintain a table of cryptocurrency values in the node's database. ### Flows -The CorDapp defines three flows: `AddTokenValueFlow`, `UpdateTokenValueFlow`, and `QueryTokenValueFlow`. Under the hood, the database accesses are managed by the CryptoValuesDatabaseService [CordaService](https://training.corda.net/corda-details/automation/#services). +The CorDapp defines three flows: `AddTokenValueFlow`, `UpdateTokenValueFlow`, and `QueryTokenValueFlow`. Under the hood, database access is managed by the CryptoValuesDatabaseService [CordaService](https://training.corda.net/corda-advanced-concepts/automation/). Be aware that support of database accesses in flows is currently limited: @@ -17,7 +17,7 @@ Be aware that support of database accesses in flows is currently limited: ## Pre-Requisites -For development environment setup, please refer to: [Setup Guide](https://docs.corda.net/getting-set-up.html). +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.9/community/getting-set-up.html). ## Running the nodes @@ -25,7 +25,7 @@ For development environment setup, please refer to: [Setup Guide](https://docs.c Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) ``` -./gradlew clean deployNodes +./gradlew clean build deployNodes ``` Then type: (to run the nodes) ``` diff --git a/Basic/flow-database-access/build.gradle b/Basic/flow-database-access/build.gradle index 2e8ebf8d..b48f23b7 100644 --- a/Basic/flow-database-access/build.gradle +++ b/Basic/flow-database-access/build.gradle @@ -19,8 +19,8 @@ buildscript {//properties that you need to build the project repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -35,9 +35,9 @@ allprojects {//Properties that you need to compile your project (The application repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } } @@ -77,6 +77,8 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" + } task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { diff --git a/Basic/flow-database-access/workflows/build.gradle b/Basic/flow-database-access/workflows/build.gradle index 4fec5f6e..2cc9e4b8 100644 --- a/Basic/flow-database-access/workflows/build.gradle +++ b/Basic/flow-database-access/workflows/build.gradle @@ -11,7 +11,7 @@ jar { repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } diff --git a/Basic/flow-http-access/README.md b/Basic/flow-http-access/README.md index bafa01f4..e3ea56f7 100644 --- a/Basic/flow-http-access/README.md +++ b/Basic/flow-http-access/README.md @@ -20,7 +20,7 @@ Be aware that support of HTTP requests in flows is currently limited: Be careful when making HTTP calls in flows; they have to be blocking. In addition, if the flow fails and is restarted, the HTTP request will be replayed as-is. -You'll find our HTTP request example within HTTPCallFlow.java +You'll find our HTTP request example within [HTTPCallFlow.java](./workflows/src/main/java/net/corda/samples/flowhttp/HttpCallFlow.java) It works mostly as you'd expect, using a request builder to make a request at a client and use the result. @@ -37,14 +37,13 @@ It works mostly as you'd expect, using a request builder to make a request at a } return value; } - ``` ## Pre-Requisites -For development environment setup, please refer to: [Setup Guide](https://docs.corda.net/getting-set-up.html). +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.9/community/getting-set-up.html). ## Running the nodes @@ -52,7 +51,7 @@ For development environment setup, please refer to: [Setup Guide](https://docs.c Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) ``` -./gradlew clean deployNodes +./gradlew clean build deployNodes ``` Then type: (to run the nodes) ``` diff --git a/Basic/flow-http-access/build.gradle b/Basic/flow-http-access/build.gradle index 032376b8..f0c7ffad 100644 --- a/Basic/flow-http-access/build.gradle +++ b/Basic/flow-http-access/build.gradle @@ -21,8 +21,8 @@ buildscript {//properties that you need to build the project repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -38,9 +38,9 @@ allprojects {//Properties that you need to compile your project (The application repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } } @@ -80,6 +80,8 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" + } diff --git a/Basic/flow-http-access/repositories.gradle b/Basic/flow-http-access/repositories.gradle index 206b9c68..8be7b630 100644 --- a/Basic/flow-http-access/repositories.gradle +++ b/Basic/flow-http-access/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Basic/ping-pong/README.md b/Basic/ping-pong/README.md index e61f1985..83a9154a 100644 --- a/Basic/ping-pong/README.md +++ b/Basic/ping-pong/README.md @@ -1,17 +1,17 @@ # Ping-Pong CorDapp This CorDapp allows a node to ping any other node on the network that also has this CorDapp installed. -It demonstrates how to use Corda for messaging and passing data using a [flow](https://docs.corda.net/docs/corda-os/flow-state-machines.html#flow-sessions) without saving any states or using any contracts. +It demonstrates how to use Corda for messaging and passing data using a [flow](https://docs.r3.com/en/platform/corda/4.9/community/api-flows.html) without saving any states or using any contracts. ### Concepts -The `ping` utility is normally used to send an Send ICMP ECHO_REQUEST packets to network hosts. The idea being that the receiving host will echo the message back. +The `ping` utility is normally used to send a Send ICMP ECHO_REQUEST packet to network hosts. The idea being that the receiving host will echo the message back. We can use corda abstractions to accomplish the same thing. -We define a state (the "ping" to be shared), define a contract (the way to make sure our ping is received correctly), and define the flow (the control flow of our cordapp). +We define a state (the "ping" to be shared), define a contract (the way to make sure our ping is received correctly), and define the flow (the control flow of our CorDapp). ## Flows @@ -19,9 +19,9 @@ We define a state (the "ping" to be shared), define a contract (the way to make You'll notice in our code we call these two classes ping and pong, the flow that sends the `"ping"`, and the flow that returns with a `"pong"`. -Take a look at `Ping.java`. +Take a look at [Ping.java](./workflows/src/main/java/net/corda/samples/pingpong/flows/Ping.java). -You'll notice that this flow does what we expect, which is to send an outbound ping, and expect to receive a pong. If we receive a pong, then our flow is sucessful. +You'll notice that this flow does what we expect, which is to send an outbound ping, and expect to receive a pong. If we receive a pong, then our flow is successful. ```java public Void call() throws FlowException { @@ -36,7 +36,7 @@ You'll notice that this flow does what we expect, which is to send an outbound p ``` -And of course we see a similar behavior in [Pong.java](./workflows-java/src/main/java/net/corda/examples/pingpong/flows/Pong.java#L22-L30). +And of course we see a similar behavior in [Pong.java](./workflows/src/main/java/net/corda/samples/pingpong/flows/Pong.java). We expect to receive data from a counterparty that contains a ping, when we receive it, we respond with a pong. @@ -55,7 +55,7 @@ We expect to receive data from a counterparty that contains a ping, when we rece ## Pre-Requisites -For development environment setup, please refer to: [Setup Guide](https://docs.corda.net/getting-set-up.html). +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.9/community/getting-set-up.html). ## Running the nodes @@ -63,7 +63,7 @@ For development environment setup, please refer to: [Setup Guide](https://docs.c Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) ``` -./gradlew clean deployNodes +./gradlew clean build deployNodes ``` Then type: (to run the nodes) ``` diff --git a/Basic/ping-pong/build.gradle b/Basic/ping-pong/build.gradle index 181d6cb0..456f4453 100644 --- a/Basic/ping-pong/build.gradle +++ b/Basic/ping-pong/build.gradle @@ -23,8 +23,8 @@ buildscript { //properties that you need to build the project repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -42,9 +42,9 @@ allprojects { //Properties that you need to compile your project (The applicatio repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } } @@ -89,6 +89,8 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" + } task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { diff --git a/Basic/ping-pong/repositories.gradle b/Basic/ping-pong/repositories.gradle index eb2e21b3..16c786b7 100644 --- a/Basic/ping-pong/repositories.gradle +++ b/Basic/ping-pong/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } \ No newline at end of file diff --git a/Basic/tutorial-applestamp/LICENCE b/Basic/tutorial-applestamp/LICENCE new file mode 100644 index 00000000..3ff572d1 --- /dev/null +++ b/Basic/tutorial-applestamp/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/Basic/tutorial-applestamp/README.md b/Basic/tutorial-applestamp/README.md new file mode 100644 index 00000000..b9d3e236 --- /dev/null +++ b/Basic/tutorial-applestamp/README.md @@ -0,0 +1,80 @@ +# Apple Stamp CorDapp + +This CorDapp allows a node to create and issue an apple stamp to another node. +Example use of an apple stamp: "One stamp can be exchanged for a basket of Honey Crisp Apples". + + +## States +* `AppleStamp`: This is a [LinearState](https://docs.r3.com/en/platform/corda/4.9/community/api-states.html#linearstate) that represents an Apple Stamp that can be issued by one party (issuer) to another party (holder). +* `BasketOfApples`: This state represents a specific basket of apples (description of the brand/type, farm, owner, and weight). To change ownership of this basket of apples, the `changeOwner` function can be used. + +## Contracts +* `AppleStampContract`: This is used to govern the evolution of an `AppleStamp` state. This file includes validation rules governing the `Issue` command for `AppleStamp`. +* `BasketOfApplesContract`: This is used to govern the evolution of an `BasketOfApples` state. This file includes validation rules governing the `packBacket` and the `Redeem` command for a `BasketOfApples`. + +### Flows + +* `CreateAndIssueAppleStampInitiator` and `CreateAndIssueAppleStampResponder` flows are used to create and issue an `AppleStamp` state. It takes 2 arguments as the parameters: the `stampDescription` (String) and the `holder` (Party). +* `PackApplesInitiator` flow is used to create a `BasketOfApples` state in the initiating node's vault. It takes 2 arguments as the parameters: the `appleDescription` (String) and the `weight` (Int). +* `RedeemApplesInitiator` and `RedeemApplesResponder` flows are used to redeem a `BasketOfApples` against an `AppleStamp`. It takes 2 arguments as the parameters: the `buyer` (Party) and the `stampId` (UniqueIdentifier). + +## Pre-Requisites + +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.9/community/getting-set-up.html). + + +## Running the nodes + +Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) +``` +./gradlew clean build deployNodes +``` +Then type: (to run the nodes) +``` +./build/nodes/runnodes +``` +This should open up 3 new tabs in the terminal window with Corda interactive shells. + +One for the Notary, one for Peter, and one for Apple Farm. +(If any of the nodes is missing a Corda interactive shell, from the root folder, navigate to ```./build/node/{missing party node}``` and run ```java -jar corda.jar``` to boot up the Corda interactive shell manually.) + +## Interacting with the CorDapp via the terminal + +1. Navigate to Apple Farm's Corda Interactive Shell and type the following command: +``` +flow start CreateAndIssueAppleStampInitiator stampDescription: "FujiApples", holder: Peter +``` +Apple Farm has now created and issued an Apple Stamp that is redeemable against a basket of fuji apples to Peter. + +2. Next, in Apple Farm's Corda Interactive Shell, type the following command: +``` +flow start PackApplesInitiator appleDescription: "FujiApples", weight: 5 +``` +Apple Farm has now packed one basket of fuji apples. + +3. To check that this `BasketOfApples` state has been successfully stored in Apple Farm's vault, in Apple Farm's Corda Interactive Shell, type the following command: +``` +run vaultQuery contractStateType: com.tutorial.states.BasketOfApples +``` +The output should be the `BasketOfApples` state that you have just created. + +4. If you type the same command into Peter's Corda Interactive Shell, it shouldn't be there as he is not the owner of this state (yet). In order to make him the owner of this basket of fuji apples, he will need to redeem it using his `AppleStamp`. To find out the unique identifier of the `AppleStamp` type the following command into Peter's Corda Interactive Shell: +``` +run vaultQuery contractStateType: com.tutorial.states.AppleStamp +``` +From the output, copy the value of `id` in `linearId` field. + +5. Navigate to Apple Farm's Corda Interactive Shell and type in the following command: +``` +flow start RedeemApplesInitiator buyer: Peter, stampId: {paste the copied linearID here} +``` +Upon completion of the flow, you should see the output `Flow completed with result: SignedTransaction(id=XXXXXXX)`. +Now Peter has successfully redeemed his `AppleStamp` against a `BasketOfApples`, we can double-check to make sure that this `BasketOfApples` is stored in his vault. + +6. In Peter's Corda Interactive Shell, type: +``` +run vaultQuery contractStateType: com.tutorial.states.BasketOfApples +``` +You should now be able to see the respective `BasketOfApples` in Peter's vault. + + diff --git a/Basic/tutorial-applestamp/TRADEMARK b/Basic/tutorial-applestamp/TRADEMARK new file mode 100644 index 00000000..d2e056b5 --- /dev/null +++ b/Basic/tutorial-applestamp/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/Basic/tutorial-applestamp/build.gradle b/Basic/tutorial-applestamp/build.gradle new file mode 100644 index 00000000..81257725 --- /dev/null +++ b/Basic/tutorial-applestamp/build.gradle @@ -0,0 +1,136 @@ +buildscript {//properties that you need to build the project + Properties constants = new Properties() + file("$projectDir/../constants.properties").withInputStream { constants.load(it) } + + ext { + corda_release_group = constants.getProperty("cordaReleaseGroup") + corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") + corda_release_version = constants.getProperty("cordaVersion") + corda_core_release_version = constants.getProperty("cordaCoreVersion") + 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' + } + + repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://download.corda.net/maven/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 {//Properties that you need to compile your project (The application) + apply from: "${rootProject.projectDir}/repositories.gradle" + apply plugin: 'java' + + repositories { + mavenLocal() + + mavenCentral() + maven { url 'https://download.corda.net/maven/corda-dependencies' } + 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 + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} + +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") + } + } +} +//Module dependencis +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_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") + + 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" + cordaDriver "net.corda:corda-shell:4.10" + +} + + +//Task to deploy the nodes in order to bootstrap a network +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + + /* This property will load the CorDapps to each of the node by default, including the Notary. You can find them + * in the cordapps folder of the node at build/nodes/Notary/cordapps. However, the notary doesn't really understand + * the notion of cordapps. In production, Notary does not need cordapps as well. This is just a short cut to load + * the Corda network bootstrapper. + */ + nodeDefaults { + projectCordapp { + deploy = false + } + cordapp project(':contracts') + cordapp project(':workflows') + runSchemaMigration = true //This configuration is for any CorDapps with custom schema, We will leave this as true to avoid + //problems for developers who are not familiar with Corda. If you are not using custom schemas, you can change + //it to false for quicker project compiling time. + } + node { + name "O=Notary,L=London,C=GB" + notary = [validating : false] + p2pPort 10002 + rpcSettings { + address("localhost:10003") + adminAddress("localhost:10043") + } + } + node { + name "O=AppleFarm,L=London,C=GB" + p2pPort 10005 + rpcSettings { + address("localhost:10006") + adminAddress("localhost:10046") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=Peter,L=New York,C=US" + p2pPort 10008 + rpcSettings { + address("localhost:10009") + adminAddress("localhost:10049") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } +} diff --git a/Basic/tutorial-applestamp/config/dev/log4j2.xml b/Basic/tutorial-applestamp/config/dev/log4j2.xml new file mode 100644 index 00000000..34ba4d45 --- /dev/null +++ b/Basic/tutorial-applestamp/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/Basic/tutorial-applestamp/config/test/log4j2.xml b/Basic/tutorial-applestamp/config/test/log4j2.xml new file mode 100644 index 00000000..cd9926ca --- /dev/null +++ b/Basic/tutorial-applestamp/config/test/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n + > + + + + + + + + + + + + diff --git a/Basic/tutorial-applestamp/contracts/build.gradle b/Basic/tutorial-applestamp/contracts/build.gradle new file mode 100644 index 00000000..b23fdd2b --- /dev/null +++ b/Basic/tutorial-applestamp/contracts/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + contract { + name "4.8LTS Tutorial Contracts" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main{ + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + } + test{ + java{ + srcDir 'src/test/java' + java.outputDir = file('bin/test') + } + } +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_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/Basic/tutorial-applestamp/contracts/src/main/java/com/tutorial/contracts/AppleStampContract.java b/Basic/tutorial-applestamp/contracts/src/main/java/com/tutorial/contracts/AppleStampContract.java new file mode 100644 index 00000000..2cb58b67 --- /dev/null +++ b/Basic/tutorial-applestamp/contracts/src/main/java/com/tutorial/contracts/AppleStampContract.java @@ -0,0 +1,44 @@ +package com.tutorial.contracts; + +import com.tutorial.states.AppleStamp; +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import org.jetbrains.annotations.NotNull; + +import static net.corda.core.contracts.ContractsDSL.requireThat; + +public class AppleStampContract implements Contract { + + // This is used to identify our contract when building a transaction. + public static final String ID = "com.tutorial.contracts.AppleStampContract"; + + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + + //Extract the command from the transaction. + final CommandData commandData = tx.getCommands().get(0).getValue(); + + //Verify the transaction according to the intention of the transaction + if (commandData instanceof AppleStampContract.Commands.Issue) { + AppleStamp output = tx.outputsOfType(AppleStamp.class).get(0); + requireThat(require -> { + require.using("This transaction should only have one AppleStamp state as output", tx.getOutputs().size() == 1); + require.using("The output AppleStamp state should have clear description of the type of redeemable goods", !output.getStampDesc().equals("")); + return null; + }); + } else if (commandData instanceof BasketOfApplesContract.Commands.Redeem) { + //Transaction verification will happen in BasketOfApples Contract + } else { + //Unrecognized Command type + throw new IllegalArgumentException("Incorrect type of AppleStamp Commands"); + } + } + + // Used to indicate the transaction's intent. + public interface Commands extends CommandData { + //In our hello-world app, We will have two commands. + class Issue implements AppleStampContract.Commands { + } + } +} diff --git a/Basic/tutorial-applestamp/contracts/src/main/java/com/tutorial/contracts/BasketOfApplesContract.java b/Basic/tutorial-applestamp/contracts/src/main/java/com/tutorial/contracts/BasketOfApplesContract.java new file mode 100644 index 00000000..ecf7efdc --- /dev/null +++ b/Basic/tutorial-applestamp/contracts/src/main/java/com/tutorial/contracts/BasketOfApplesContract.java @@ -0,0 +1,56 @@ +package com.tutorial.contracts; + +import com.tutorial.states.AppleStamp; +import com.tutorial.states.BasketOfApples; +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import org.jetbrains.annotations.NotNull; + +import static net.corda.core.contracts.ContractsDSL.requireThat; + +public class BasketOfApplesContract implements Contract { + + // This is used to identify our contract when building a transaction. + public static final String ID = "com.tutorial.contracts.BasketOfApplesContract"; + + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + //Extract the command from the transaction. + final CommandData commandData = tx.getCommands().get(0).getValue(); + + if (commandData instanceof BasketOfApplesContract.Commands.packBasket) { + BasketOfApples output = tx.outputsOfType(BasketOfApples.class).get(0); + requireThat(require -> { + require.using("This transaction should only output one BasketOfApples state", tx.getOutputs().size() == 1); + require.using("The output BasketOfApples state should have clear description of Apple product", !output.getDescription().equals("")); + require.using("The output BasketOfApples state should have non zero weight", output.getWeight() > 0); + return null; + }); + } else if (commandData instanceof BasketOfApplesContract.Commands.Redeem) { + //Retrieve the output state of the transaction + AppleStamp input = tx.inputsOfType(AppleStamp.class).get(0); + BasketOfApples output = tx.outputsOfType(BasketOfApples.class).get(0); + + //Using Corda DSL function requireThat to replicate conditions-checks + requireThat(require -> { + require.using("This transaction should consume two states", tx.getInputStates().size() == 2); + require.using("The issuer of the Apple stamp should be the producing farm of this basket of apple", input.getIssuer().equals(output.getFarm())); + require.using("The basket of apple has to weight more than 0", output.getWeight() > 0); + return null; + }); + } else { + //Unrecognized Command type + throw new IllegalArgumentException("Incorrect type of BasketOfApples Commands"); + } + } + + // Used to indicate the transaction's intent. + public interface Commands extends CommandData { + class packBasket implements BasketOfApplesContract.Commands { + } + + class Redeem implements BasketOfApplesContract.Commands { + } + } +} diff --git a/Basic/tutorial-applestamp/contracts/src/main/java/com/tutorial/contracts/TemplateContract.java b/Basic/tutorial-applestamp/contracts/src/main/java/com/tutorial/contracts/TemplateContract.java new file mode 100644 index 00000000..b6c71bb4 --- /dev/null +++ b/Basic/tutorial-applestamp/contracts/src/main/java/com/tutorial/contracts/TemplateContract.java @@ -0,0 +1,46 @@ +package com.tutorial.contracts; + +import com.tutorial.states.TemplateState; +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; + +import static net.corda.core.contracts.ContractsDSL.requireThat; + +// ************ +// * Contract * +// ************ +public class TemplateContract implements Contract { + // This is used to identify our contract when building a transaction. + public static final String ID = "com.tutorial.contracts.TemplateContract"; + + // A transaction is valid if the verify() function of the contract of all the transaction's input and output states + // does not throw an exception. + @Override + public void verify(LedgerTransaction tx) { + + /* We can use the requireSingleCommand function to extract command data from transaction. + * However, it is possible to have multiple commands in a single transaction.*/ + //final CommandWithParties command = requireSingleCommand(tx.getCommands(), Commands.class); + final CommandData commandData = tx.getCommands().get(0).getValue(); + + if (commandData instanceof Commands.Send) { + //Retrieve the output state of the transaction + TemplateState output = tx.outputsOfType(TemplateState.class).get(0); + + //Using Corda DSL function requireThat to replicate conditions-checks + requireThat(require -> { + require.using("No inputs should be consumed when sending the Hello-World message.", tx.getInputStates().size() == 0); + require.using("The message must be Hello-World", output.getMsg().equals("Hello-World")); + return null; + }); + } + } + + // Used to indicate the transaction's intent. + public interface Commands extends CommandData { + //In our hello-world app, We will only have one command. + class Send implements Commands { + } + } +} \ No newline at end of file diff --git a/Basic/tutorial-applestamp/contracts/src/main/java/com/tutorial/states/AppleStamp.java b/Basic/tutorial-applestamp/contracts/src/main/java/com/tutorial/states/AppleStamp.java new file mode 100644 index 00000000..bd899291 --- /dev/null +++ b/Basic/tutorial-applestamp/contracts/src/main/java/com/tutorial/states/AppleStamp.java @@ -0,0 +1,68 @@ +package com.tutorial.states; + +import com.tutorial.contracts.AppleStampContract; +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.LinearState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.core.serialization.ConstructorForDeserialization; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +@BelongsToContract(AppleStampContract.class) +public class AppleStamp implements LinearState { + + //Private Variables + private String stampDesc; //For example: "One stamp can exchange for a basket of HoneyCrispy Apple" + private Party issuer; //The person who issued the stamp + private Party holder; //The person who currently owns the stamp + + //LinearState required variable. + private UniqueIdentifier linearID; + + //ALL Corda State required parameter to indicate storing parties + private List participants; + + //Constructor Tips: Command + N in IntelliJ can auto generate constructor. + @ConstructorForDeserialization + public AppleStamp(String stampDesc, Party issuer, Party holder, UniqueIdentifier linearID) { + this.stampDesc = stampDesc; + this.issuer = issuer; + this.holder = holder; + this.linearID = linearID; + this.participants = new ArrayList(); + this.participants.add(issuer); + this.participants.add(holder); + } + + @NotNull + @Override + public List getParticipants() { + return this.participants; + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return this.linearID; + } + + //Getters + public String getStampDesc() { + return stampDesc; + } + + public Party getIssuer() { + return issuer; + } + + public Party getHolder() { + return holder; + } + +} + +//Advanced tutorial add brand and type of apple for more complicated contract writing diff --git a/Basic/tutorial-applestamp/contracts/src/main/java/com/tutorial/states/BasketOfApples.java b/Basic/tutorial-applestamp/contracts/src/main/java/com/tutorial/states/BasketOfApples.java new file mode 100644 index 00000000..869e1d7e --- /dev/null +++ b/Basic/tutorial-applestamp/contracts/src/main/java/com/tutorial/states/BasketOfApples.java @@ -0,0 +1,79 @@ +package com.tutorial.states; + +import com.tutorial.contracts.BasketOfApplesContract; +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 net.corda.core.serialization.ConstructorForDeserialization; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +@BelongsToContract(BasketOfApplesContract.class) +public class BasketOfApples implements ContractState { + + //Private Variables + private String description; //Brand or type + private Party farm; //Origin of the apple + private Party owner; //The person who exchange the basket of apple with the stamp. + private int weight; + + //ALL Corda State required parameter to indicate storing parties + private List participants; + + //Constructors + //Basket of Apple creation. Only farm name is stored. + public BasketOfApples(String description, Party farm, int weight) { + this.description = description; + this.farm = farm; + this.owner=farm; + this.weight = weight; + this.participants = new ArrayList(); + this.participants.add(farm); + } + + //Constructor for object creation during transaction + @ConstructorForDeserialization + public BasketOfApples(String description, Party farm, Party owner, int weight) { + this.description = description; + this.farm = farm; + this.owner = owner; + this.weight = weight; + this.participants = new ArrayList(); + this.participants.add(farm); + this.participants.add(owner); + } + + @NotNull + @Override + public List getParticipants() { + return participants; + } + + //getters + public String getDescription() { + return description; + } + + public Party getFarm() { + return farm; + } + + public Party getOwner() { + return owner; + } + + public int getWeight() { + return weight; + } + + public BasketOfApples changeOwner(Party buyer){ + BasketOfApples newOwnerState = new BasketOfApples(this.description,this.farm,buyer,this.weight); + return newOwnerState; + } + +} + +//Advance version will fill in brand and type. \ No newline at end of file diff --git a/Basic/tutorial-applestamp/contracts/src/main/java/com/tutorial/states/TemplateState.java b/Basic/tutorial-applestamp/contracts/src/main/java/com/tutorial/states/TemplateState.java new file mode 100644 index 00000000..a73bc4f0 --- /dev/null +++ b/Basic/tutorial-applestamp/contracts/src/main/java/com/tutorial/states/TemplateState.java @@ -0,0 +1,41 @@ +package com.tutorial.states; + +import com.tutorial.contracts.TemplateContract; +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 java.util.Arrays; +import java.util.List; + +// ********* +// * State * +// ********* +@BelongsToContract(TemplateContract.class) +public class TemplateState implements ContractState { + + //private variables + private String msg; + private Party sender; + private Party receiver; + + /* Constructor of your Corda state */ + public TemplateState(String msg, Party sender, Party receiver) { + this.msg = msg; + this.sender = sender; + this.receiver = receiver; + } + + //getters + public String getMsg() { return msg; } + public Party getSender() { return sender; } + public Party getReceiver() { return receiver; } + + /* This method will indicate who are the participants and required signers when + * this state is used in a transaction. */ + @Override + public List getParticipants() { + return Arrays.asList(sender,receiver); + } +} \ No newline at end of file diff --git a/Basic/tutorial-applestamp/contracts/src/test/java/com/tutorial/contracts/ContractTests.java b/Basic/tutorial-applestamp/contracts/src/test/java/com/tutorial/contracts/ContractTests.java new file mode 100644 index 00000000..7cdaf0e5 --- /dev/null +++ b/Basic/tutorial-applestamp/contracts/src/test/java/com/tutorial/contracts/ContractTests.java @@ -0,0 +1,81 @@ +package com.tutorial.contracts; + +import com.tutorial.states.AppleStamp; +import com.tutorial.states.TemplateState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.node.MockServices; +import org.junit.Test; + +import java.util.Arrays; + +import static net.corda.testing.node.NodeTestUtils.ledger; + +public class ContractTests { + private final MockServices ledgerServices = new MockServices(Arrays.asList("com.tutorial.contracts")); + TestIdentity alice = new TestIdentity(new CordaX500Name("Alice", "TestLand", "US")); + TestIdentity bob = new TestIdentity(new CordaX500Name("Alice", "TestLand", "US")); + + //Template Tester + @Test + public void issuerAndRecipientCannotHaveSameEmail() { + TemplateState state = new TemplateState("Hello-World", alice.getParty(), bob.getParty()); + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.input(TemplateContract.ID, state); + tx.output(TemplateContract.ID, state); + tx.command(alice.getPublicKey(), new TemplateContract.Commands.Send()); + return tx.fails(); //fails because of having inputs + }); + l.transaction(tx -> { + tx.output(TemplateContract.ID, state); + tx.command(alice.getPublicKey(), new TemplateContract.Commands.Send()); + return tx.verifies(); + }); + return null; + }); + } + + //Basket of Apple cordapp testers + @Test + public void StampIssuanceCanOnlyHaveOneOutput() { + AppleStamp stamp = new AppleStamp("FUji4072", alice.getParty(), bob.getParty(), new UniqueIdentifier()); + AppleStamp stamp2 = new AppleStamp("HoneyCrispy7864", alice.getParty(), bob.getParty(), new UniqueIdentifier()); + + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.output(AppleStampContract.ID, stamp); + tx.output(AppleStampContract.ID, stamp2); + tx.command(alice.getPublicKey(), new AppleStampContract.Commands.Issue()); + return tx.fails(); //fails because of having inputs + }); + l.transaction(tx -> { + tx.output(AppleStampContract.ID, stamp); + tx.command(alice.getPublicKey(), new AppleStampContract.Commands.Issue()); + return tx.verifies(); + }); + return null; + }); + } + + @Test + public void StampMustHaveDescription() { + AppleStamp stamp = new AppleStamp("", alice.getParty(), bob.getParty(), new UniqueIdentifier()); + AppleStamp stamp2 = new AppleStamp("FUji4072", alice.getParty(), bob.getParty(), new UniqueIdentifier()); + + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.output(AppleStampContract.ID, stamp); + tx.command(Arrays.asList(alice.getPublicKey(), bob.getPublicKey()), new AppleStampContract.Commands.Issue()); + return tx.fails(); //fails because of having inputs + }); + l.transaction(tx -> { + tx.output(AppleStampContract.ID, stamp2); + tx.command(Arrays.asList(alice.getPublicKey(), bob.getPublicKey()), new AppleStampContract.Commands.Issue()); + return tx.verifies(); + }); + return null; + }); + } +} \ No newline at end of file diff --git a/Basic/tutorial-applestamp/contracts/src/test/java/com/tutorial/contracts/StateTests.java b/Basic/tutorial-applestamp/contracts/src/test/java/com/tutorial/contracts/StateTests.java new file mode 100644 index 00000000..0166547b --- /dev/null +++ b/Basic/tutorial-applestamp/contracts/src/test/java/com/tutorial/contracts/StateTests.java @@ -0,0 +1,28 @@ +package com.tutorial.contracts; + +import com.tutorial.states.AppleStamp; +import com.tutorial.states.TemplateState; +import net.corda.core.identity.Party; +import org.junit.Test; + +public class StateTests { + + //Mock State test check for if the state has correct parameters type + @Test + public void hasFieldOfCorrectType() throws NoSuchFieldException { + TemplateState.class.getDeclaredField("msg"); + assert (TemplateState.class.getDeclaredField("msg").getType().equals(String.class)); + } + + @Test + public void AppleStampStateHasFieldOfCorrectType() throws NoSuchFieldException { + AppleStamp.class.getDeclaredField("stampDesc"); + assert (AppleStamp.class.getDeclaredField("stampDesc").getType().equals(String.class)); + + AppleStamp.class.getDeclaredField("issuer"); + assert (AppleStamp.class.getDeclaredField("issuer").getType().equals(Party.class)); + + AppleStamp.class.getDeclaredField("holder"); + assert (AppleStamp.class.getDeclaredField("issuer").getType().equals(Party.class)); + } +} \ No newline at end of file diff --git a/Basic/tutorial-applestamp/gradle.properties b/Basic/tutorial-applestamp/gradle.properties new file mode 100644 index 00000000..ef7e526c --- /dev/null +++ b/Basic/tutorial-applestamp/gradle.properties @@ -0,0 +1,3 @@ +name=Test +group=com.tutorial +version=0.1 \ No newline at end of file diff --git a/Basic/tutorial-applestamp/gradle/wrapper/gradle-wrapper.jar b/Basic/tutorial-applestamp/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..99340b4a Binary files /dev/null and b/Basic/tutorial-applestamp/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Basic/tutorial-applestamp/gradle/wrapper/gradle-wrapper.properties b/Basic/tutorial-applestamp/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..0188225a --- /dev/null +++ b/Basic/tutorial-applestamp/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +#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-5.6.4-all.zip + diff --git a/Basic/tutorial-applestamp/gradlew b/Basic/tutorial-applestamp/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/Basic/tutorial-applestamp/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/Basic/tutorial-applestamp/gradlew.bat b/Basic/tutorial-applestamp/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/Basic/tutorial-applestamp/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/Basic/tutorial-applestamp/repositories.gradle b/Basic/tutorial-applestamp/repositories.gradle new file mode 100644 index 00000000..8be7b630 --- /dev/null +++ b/Basic/tutorial-applestamp/repositories.gradle @@ -0,0 +1,8 @@ +repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://jitpack.io' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } +} diff --git a/Basic/tutorial-applestamp/settings.gradle b/Basic/tutorial-applestamp/settings.gradle new file mode 100644 index 00000000..2514aca2 --- /dev/null +++ b/Basic/tutorial-applestamp/settings.gradle @@ -0,0 +1,3 @@ +include 'workflows' +include 'contracts' +include 'clients' \ No newline at end of file diff --git a/Basic/tutorial-applestamp/workflows/build.gradle b/Basic/tutorial-applestamp/workflows/build.gradle new file mode 100644 index 00000000..44d454a0 --- /dev/null +++ b/Basic/tutorial-applestamp/workflows/build.gradle @@ -0,0 +1,64 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + workflow { + name "4.8LTS Tutorial Flows" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/dev") + } + } + test { + java { + srcDir 'src/test/java' + java.outputDir = file('bin/main') + } + 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_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":contracts") +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} \ No newline at end of file diff --git a/Basic/tutorial-applestamp/workflows/src/integrationTest/java/com/tutorial/DriverBasedTest.java b/Basic/tutorial-applestamp/workflows/src/integrationTest/java/com/tutorial/DriverBasedTest.java new file mode 100644 index 00000000..5800309b --- /dev/null +++ b/Basic/tutorial-applestamp/workflows/src/integrationTest/java/com/tutorial/DriverBasedTest.java @@ -0,0 +1,48 @@ +package com.tutorial; + +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/Basic/tutorial-applestamp/workflows/src/main/java/com/tutorial/flows/CreateAndIssueAppleStamp.java b/Basic/tutorial-applestamp/workflows/src/main/java/com/tutorial/flows/CreateAndIssueAppleStamp.java new file mode 100644 index 00000000..1e62a117 --- /dev/null +++ b/Basic/tutorial-applestamp/workflows/src/main/java/com/tutorial/flows/CreateAndIssueAppleStamp.java @@ -0,0 +1,101 @@ +package com.tutorial.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.tutorial.contracts.AppleStampContract; +import com.tutorial.states.AppleStamp; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; + +import java.util.Arrays; + +public class CreateAndIssueAppleStamp { + + @InitiatingFlow + @StartableByRPC + public static class CreateAndIssueAppleStampInitiator extends FlowLogic { + + private String stampDescription; + private Party holder; + + public CreateAndIssueAppleStampInitiator(String stampDescription, Party holder) { + this.stampDescription = stampDescription; + this.holder = holder; + } + + @Suspendable + @Override + public SignedTransaction call() throws FlowException { + + /* Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + //Building the output AppleStamp state + UniqueIdentifier uniqueID = new UniqueIdentifier(); + AppleStamp newStamp = new AppleStamp(this.stampDescription, this.getOurIdentity(), this.holder, uniqueID); + + //Compositing the transaction + TransactionBuilder txBuilder = new TransactionBuilder(notary) + .addOutputState(newStamp) + .addCommand(new AppleStampContract.Commands.Issue(), + Arrays.asList(getOurIdentity().getOwningKey(), holder.getOwningKey())); + + // Verify that the transaction is valid. + txBuilder.verify(getServiceHub()); + + // Sign the transaction. + final SignedTransaction partSignedTx = getServiceHub().signInitialTransaction(txBuilder); + + // Send the state to the counterparty, and receive it back with their signature. + FlowSession otherPartySession = initiateFlow(holder); + final SignedTransaction fullySignedTx = subFlow( + new CollectSignaturesFlow(partSignedTx, Arrays.asList(otherPartySession))); + + // Notarise and record the transaction in both parties' vaults. + return subFlow(new FinalityFlow(fullySignedTx, Arrays.asList(otherPartySession))); + } + } + + @InitiatedBy(CreateAndIssueAppleStampInitiator.class) + public static class CreateAndIssueAppleStampResponder extends FlowLogic { + + //private variable + private FlowSession counterpartySession; + + public CreateAndIssueAppleStampResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Override + @Suspendable + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Override + @Suspendable + protected void checkTransaction(SignedTransaction stx) throws FlowException { + /* + * SignTransactionFlow will automatically verify the transaction and its signatures before signing it. + * However, just because a transaction is contractually valid doesn’t mean we necessarily want to sign. + * What if we don’t want to deal with the counterparty in question, or the value is too high, + * or we’re not happy with the transaction’s structure? checkTransaction + * allows us to define these additional checks. If any of these conditions are not met, + * we will not sign the transaction - even if the transaction and its signatures are contractually valid. + * ---------- + * For this hello-world cordapp, we will not implement any additional checks. + * */ + } + }); + + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } + } +} + +//flow start CreateAndIssueAppleStampInitiator stampDescription: Fuji0472, holder: Peter +//run vaultQuery contractStateType: com.tutorial.states.AppleStamp \ No newline at end of file diff --git a/Basic/tutorial-applestamp/workflows/src/main/java/com/tutorial/flows/PackageApples.java b/Basic/tutorial-applestamp/workflows/src/main/java/com/tutorial/flows/PackageApples.java new file mode 100644 index 00000000..22c1838e --- /dev/null +++ b/Basic/tutorial-applestamp/workflows/src/main/java/com/tutorial/flows/PackageApples.java @@ -0,0 +1,57 @@ +package com.tutorial.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.tutorial.contracts.BasketOfApplesContract; +import com.tutorial.states.BasketOfApples; +import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; + +import java.util.Collections; + +public class PackageApples { + + @InitiatingFlow + @StartableByRPC + public static class PackApplesInitiator extends FlowLogic { + + private String appleDescription; + private int weight; + + public PackApplesInitiator(String appleDescription, int weight) { + this.appleDescription = appleDescription; + this.weight = weight; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + //Create the output object + BasketOfApples basket = new BasketOfApples(this.appleDescription, this.getOurIdentity(), this.weight); + + //Building transaction + TransactionBuilder txBuilder = new TransactionBuilder(notary) + .addOutputState(basket) + .addCommand(new BasketOfApplesContract.Commands.packBasket(), this.getOurIdentity().getOwningKey()); + + // Verify the transaction + txBuilder.verify(getServiceHub()); + + // Sign the transaction + SignedTransaction signedTransaction = getServiceHub().signInitialTransaction(txBuilder); + + // Notarise the transaction and record the states in the ledger. + return subFlow(new FinalityFlow(signedTransaction, Collections.emptyList())); + } + } +} + +//flow start PackApplesInitiator appleDescription: Fuji4072, weight: 10 +//run vaultQuery contractStateType: com.tutorial.states.BasketOfApples \ No newline at end of file diff --git a/Basic/tutorial-applestamp/workflows/src/main/java/com/tutorial/flows/RedeemApples.java b/Basic/tutorial-applestamp/workflows/src/main/java/com/tutorial/flows/RedeemApples.java new file mode 100644 index 00000000..08b39beb --- /dev/null +++ b/Basic/tutorial-applestamp/workflows/src/main/java/com/tutorial/flows/RedeemApples.java @@ -0,0 +1,107 @@ +package com.tutorial.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.tutorial.contracts.BasketOfApplesContract; +import com.tutorial.states.AppleStamp; +import com.tutorial.states.BasketOfApples; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +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.Arrays; +import java.util.UUID; + +public class RedeemApples { + + @InitiatingFlow + @StartableByRPC + public static class RedeemApplesInitiator extends FlowLogic { + + private Party buyer; + private UniqueIdentifier stampId; + + public RedeemApplesInitiator(Party buyer, UniqueIdentifier stampId) { + this.buyer = buyer; + this.stampId = stampId; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + //Query the AppleStamp + QueryCriteria.LinearStateQueryCriteria inputCriteria = new QueryCriteria.LinearStateQueryCriteria() + .withUuid(Arrays.asList(UUID.fromString(stampId.toString()))) + .withStatus(Vault.StateStatus.UNCONSUMED) + .withRelevancyStatus(Vault.RelevancyStatus.RELEVANT); + StateAndRef appleStampStateAndRef = getServiceHub().getVaultService().queryBy(AppleStamp.class, inputCriteria).getStates().get(0); + + //Query output BasketOfApples + QueryCriteria outputCriteria = new QueryCriteria.VaultQueryCriteria() + .withStatus(Vault.StateStatus.UNCONSUMED) + .withRelevancyStatus(Vault.RelevancyStatus.RELEVANT); + StateAndRef BasketOfApplesStateAndRef = getServiceHub().getVaultService().queryBy(BasketOfApples.class, outputCriteria).getStates().get(0); + BasketOfApples originalBasketOfApples = (BasketOfApples) BasketOfApplesStateAndRef.getState().getData(); + + //Modify output to address the owner change + BasketOfApples output = originalBasketOfApples.changeOwner(buyer); + + /* Obtain a reference to a notary we wish to use.*/ + Party notary = BasketOfApplesStateAndRef.getState().getNotary(); + + //Build Transaction + TransactionBuilder txBuilder = new TransactionBuilder(notary) + .addInputState(appleStampStateAndRef) + .addInputState(BasketOfApplesStateAndRef) + .addOutputState(output, BasketOfApplesContract.ID) + .addCommand(new BasketOfApplesContract.Commands.Redeem(), + Arrays.asList(getOurIdentity().getOwningKey(), this.buyer.getOwningKey())); + + // Verify that the transaction is valid. + txBuilder.verify(getServiceHub()); + + // Sign the transaction. + final SignedTransaction partSignedTx = getServiceHub().signInitialTransaction(txBuilder); + + // Send the state to the counterparty, and receive it back with their signature. + FlowSession otherPartySession = initiateFlow(buyer); + final SignedTransaction fullySignedTx = subFlow( + new CollectSignaturesFlow(partSignedTx, Arrays.asList(otherPartySession))); + + // Notarise and record the transaction in both parties' vaults. + SignedTransaction result = subFlow(new FinalityFlow(fullySignedTx, Arrays.asList(otherPartySession))); + + return result; + } + } + + @InitiatedBy(RedeemApplesInitiator.class) + public static class RedeemApplesResponder extends FlowLogic { + //private variable + private FlowSession counterpartySession; + + public RedeemApplesResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Override + @Suspendable + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + } + }); + + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } + } +} +//flow start RedeemApplesInitiator buyer: Peter, stampId: \ No newline at end of file diff --git a/Basic/tutorial-applestamp/workflows/src/main/java/com/tutorial/flows/TemplateInitiator.java b/Basic/tutorial-applestamp/workflows/src/main/java/com/tutorial/flows/TemplateInitiator.java new file mode 100644 index 00000000..0d0052af --- /dev/null +++ b/Basic/tutorial-applestamp/workflows/src/main/java/com/tutorial/flows/TemplateInitiator.java @@ -0,0 +1,79 @@ +package com.tutorial.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.tutorial.contracts.TemplateContract; +import com.tutorial.states.TemplateState; +import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.core.utilities.ProgressTracker; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +// ****************** +// * TemplateInitiator flow * +// ****************** +@InitiatingFlow +@StartableByRPC +public class TemplateInitiator extends FlowLogic { + + // We will not use these ProgressTracker for this Hello-World sample + private final ProgressTracker progressTracker = new ProgressTracker(); + + @Override + public ProgressTracker getProgressTracker() { + return progressTracker; + } + + //private variables + private Party sender; + private Party receiver; + + //public constructor + public TemplateInitiator(Party sendTo) { + this.receiver = sendTo; + } + + @Suspendable + @Override + public SignedTransaction call() throws FlowException { + //Hello World message + String msg = "Hello-World"; + this.sender = getOurIdentity(); + + // Step 1. Get a reference to the notary service on our network and our key pair. + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + //Compose the State that carries the Hello World message + final TemplateState output = new TemplateState(msg, sender, receiver); + + // Step 3. Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // Step 4. Add the iou as an output state, as well as a command to the transaction builder. + builder.addOutputState(output); + builder.addCommand(new TemplateContract.Commands.Send(), Arrays.asList(this.sender.getOwningKey(), this.receiver.getOwningKey())); + + + // Step 5. Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + + // Step 6. Collect the other party's signature using the SignTransactionFlow. + List otherParties = output.getParticipants().stream().map(el -> (Party) el).collect(Collectors.toList()); + otherParties.remove(getOurIdentity()); + List sessions = otherParties.stream().map(el -> initiateFlow(el)).collect(Collectors.toList()); + + SignedTransaction stx = subFlow(new CollectSignaturesFlow(ptx, sessions)); + + // Step 7. Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(stx, sessions)); + } +} diff --git a/Basic/tutorial-applestamp/workflows/src/main/java/com/tutorial/flows/TemplateResponder.java b/Basic/tutorial-applestamp/workflows/src/main/java/com/tutorial/flows/TemplateResponder.java new file mode 100644 index 00000000..2bc0da62 --- /dev/null +++ b/Basic/tutorial-applestamp/workflows/src/main/java/com/tutorial/flows/TemplateResponder.java @@ -0,0 +1,44 @@ +package com.tutorial.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.flows.*; +import net.corda.core.transactions.SignedTransaction; + +// ****************** +// * TemplateResponder flow * +// ****************** +@InitiatedBy(TemplateInitiator.class) +public class TemplateResponder extends FlowLogic { + + //private variable + private FlowSession counterpartySession; + + //Constructor + public TemplateResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + /* + * SignTransactionFlow will automatically verify the transaction and its signatures before signing it. + * However, just because a transaction is contractually valid doesn’t mean we necessarily want to sign. + * What if we don’t want to deal with the counterparty in question, or the value is too high, + * or we’re not happy with the transaction’s structure? checkTransaction + * allows us to define these additional checks. If any of these conditions are not met, + * we will not sign the transaction - even if the transaction and its signatures are contractually valid. + * ---------- + * For this hello-world cordapp, we will not implement any aditional checks. + * */ + } + }); + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } +} diff --git a/Basic/tutorial-applestamp/workflows/src/test/java/com/tutorial/CreateAndIssueAppleStampTest.java b/Basic/tutorial-applestamp/workflows/src/test/java/com/tutorial/CreateAndIssueAppleStampTest.java new file mode 100644 index 00000000..b467771d --- /dev/null +++ b/Basic/tutorial-applestamp/workflows/src/test/java/com/tutorial/CreateAndIssueAppleStampTest.java @@ -0,0 +1,68 @@ +package com.tutorial; + +import com.google.common.collect.ImmutableList; +import com.tutorial.flows.CreateAndIssueAppleStamp; +import com.tutorial.flows.TemplateInitiator; +import com.tutorial.states.AppleStamp; +import com.tutorial.states.TemplateState; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.Future; + +public class CreateAndIssueAppleStampTest { + private MockNetwork network; + private StartedMockNode a; + private StartedMockNode b; + + @Before + public void setup() { + network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("com.tutorial.contracts"), + TestCordapp.findCordapp("com.tutorial.flows"))) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); + a = network.createPartyNode(null); + b = network.createPartyNode(null); + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void dummyTest() { + TemplateInitiator flow = new TemplateInitiator(b.getInfo().getLegalIdentities().get(0)); + Future future = a.startFlow(flow); + network.runNetwork(); + + //successful query means the state is stored at node b's vault. Flow went through. + QueryCriteria inputCriteria = new QueryCriteria.VaultQueryCriteria().withStatus(Vault.StateStatus.UNCONSUMED); + TemplateState state = b.getServices().getVaultService() + .queryBy(TemplateState.class, inputCriteria).getStates().get(0).getState().getData(); + } + + @Test + public void CreateAndIssueAppleStampTest() { + CreateAndIssueAppleStamp.CreateAndIssueAppleStampInitiator flow1 = + new CreateAndIssueAppleStamp.CreateAndIssueAppleStampInitiator( + "HoneyCrispy 4072", this.b.getInfo().getLegalIdentities().get(0)); + Future future1 = a.startFlow(flow1); + network.runNetwork(); + + //successful query means the state is stored at node b's vault. Flow went through. + QueryCriteria inputCriteria = new QueryCriteria.VaultQueryCriteria() + .withStatus(Vault.StateStatus.UNCONSUMED); + AppleStamp state = b.getServices().getVaultService() + .queryBy(AppleStamp.class, inputCriteria).getStates().get(0).getState().getData(); + assert (state.getStampDesc().equals("HoneyCrispy 4072")); + } +} diff --git a/Basic/tutorial-applestamp/workflows/src/test/java/com/tutorial/FarmerSelfCreateBasketOfApplesTest.java b/Basic/tutorial-applestamp/workflows/src/test/java/com/tutorial/FarmerSelfCreateBasketOfApplesTest.java new file mode 100644 index 00000000..4365a852 --- /dev/null +++ b/Basic/tutorial-applestamp/workflows/src/test/java/com/tutorial/FarmerSelfCreateBasketOfApplesTest.java @@ -0,0 +1,56 @@ +package com.tutorial; + +import com.google.common.collect.ImmutableList; +import com.tutorial.flows.PackageApples; +import com.tutorial.states.BasketOfApples; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.Future; + +public class FarmerSelfCreateBasketOfApplesTest { + private MockNetwork network; + private StartedMockNode a; + private StartedMockNode b; + + @Before + public void setup() { + network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("com.tutorial.contracts"), + TestCordapp.findCordapp("com.tutorial.flows"))) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB"))))); + a = network.createPartyNode(null); + b = network.createPartyNode(null); + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void createBasketOfApples() { + PackageApples.PackApplesInitiator flow1 = new PackageApples.PackApplesInitiator("Fuji4072", 10); + Future future = a.startFlow(flow1); + network.runNetwork(); + + //successful query means the state is stored at node b's vault. Flow went through. + QueryCriteria inputCriteria = new QueryCriteria.VaultQueryCriteria().withStatus(Vault.StateStatus.UNCONSUMED); + BasketOfApples state = a.getServices().getVaultService() + .queryBy(BasketOfApples.class, inputCriteria).getStates().get(0).getState().getData(); + + System.out.println("-------------------------"); + System.out.println(state.getOwner()); + System.out.println("-------------------------"); + + assert (state.getDescription().equals("Fuji4072")); + } +} + diff --git a/Basic/tutorial-applestamp/workflows/src/test/java/com/tutorial/FlowTests.java b/Basic/tutorial-applestamp/workflows/src/test/java/com/tutorial/FlowTests.java new file mode 100644 index 00000000..de7fc6cf --- /dev/null +++ b/Basic/tutorial-applestamp/workflows/src/test/java/com/tutorial/FlowTests.java @@ -0,0 +1,50 @@ +package com.tutorial; + +import com.google.common.collect.ImmutableList; +import com.tutorial.flows.TemplateInitiator; +import com.tutorial.states.TemplateState; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.Future; + +public class FlowTests { + private MockNetwork network; + private StartedMockNode a; + private StartedMockNode b; + + @Before + public void setup() { + network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("com.tutorial.contracts"), + TestCordapp.findCordapp("com.tutorial.flows"))) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); + a = network.createPartyNode(null); + b = network.createPartyNode(null); + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void dummyTest() { + TemplateInitiator flow = new TemplateInitiator(b.getInfo().getLegalIdentities().get(0)); + Future future = a.startFlow(flow); + network.runNetwork(); + + //successful query means the state is stored at node b's vault. Flow went through. + QueryCriteria inputCriteria = new QueryCriteria.VaultQueryCriteria().withStatus(Vault.StateStatus.UNCONSUMED); + TemplateState state = b.getServices().getVaultService() + .queryBy(TemplateState.class, inputCriteria).getStates().get(0).getState().getData(); + } +} diff --git a/Basic/tutorial-applestamp/workflows/src/test/java/com/tutorial/RedeemApplesWithStampTest.java b/Basic/tutorial-applestamp/workflows/src/test/java/com/tutorial/RedeemApplesWithStampTest.java new file mode 100644 index 00000000..fc7a9dc5 --- /dev/null +++ b/Basic/tutorial-applestamp/workflows/src/test/java/com/tutorial/RedeemApplesWithStampTest.java @@ -0,0 +1,73 @@ +package com.tutorial; + +import com.google.common.collect.ImmutableList; +import com.tutorial.flows.CreateAndIssueAppleStamp; +import com.tutorial.flows.PackageApples; +import com.tutorial.flows.RedeemApples; +import com.tutorial.states.AppleStamp; +import com.tutorial.states.BasketOfApples; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +public class RedeemApplesWithStampTest { + private MockNetwork network; + private StartedMockNode a; + private StartedMockNode b; + + @Before + public void setup() { + network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("com.tutorial.contracts"), + TestCordapp.findCordapp("com.tutorial.flows"))) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); + a = network.createPartyNode(null); + b = network.createPartyNode(null); + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void buyerRedeemBasketOfApples() throws ExecutionException, InterruptedException { + //Create Basket of Apples + PackageApples.PackApplesInitiator createBasketOfApples = new PackageApples.PackApplesInitiator("Fuji4072", 10); + Future future = a.startFlow(createBasketOfApples); + network.runNetwork(); + + //Issue Apple Stamp + CreateAndIssueAppleStamp.CreateAndIssueAppleStampInitiator issueAppleStamp = + new CreateAndIssueAppleStamp.CreateAndIssueAppleStampInitiator( + "Fuji4072", this.b.getInfo().getLegalIdentities().get(0)); + Future future1 = a.startFlow(issueAppleStamp); + network.runNetwork(); + + AppleStamp issuedStamp = (AppleStamp) future1.get().getTx().getOutputStates().get(0); + UniqueIdentifier id = issuedStamp.getLinearId(); + + //Redeem Basket of Apples with stamp + RedeemApples.RedeemApplesInitiator redeemApples = new RedeemApples.RedeemApplesInitiator(b.getInfo().getLegalIdentities().get(0), id); + Future future2 = a.startFlow(redeemApples); + network.runNetwork(); + + //successful query means the state is stored at node b's vault. Flow went through. + QueryCriteria outputCriteria = new QueryCriteria.VaultQueryCriteria().withStatus(Vault.StateStatus.UNCONSUMED); + BasketOfApples state = b.getServices().getVaultService() + .queryBy(BasketOfApples.class, outputCriteria).getStates().get(0).getState().getData(); + + assert (state.getDescription().equals("Fuji4072")); + } +} diff --git a/Basic/tutorial-jarsigning/LICENCE b/Basic/tutorial-jarsigning/LICENCE new file mode 100644 index 00000000..3ff572d1 --- /dev/null +++ b/Basic/tutorial-jarsigning/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/Basic/tutorial-jarsigning/README.md b/Basic/tutorial-jarsigning/README.md new file mode 100644 index 00000000..f0565f5b --- /dev/null +++ b/Basic/tutorial-jarsigning/README.md @@ -0,0 +1,69 @@ +# Network Bootstrapper Tutorial + +This tutorial demonstrates how to sign a contract jar with your own keystore. + +A CorDapp will most likely have two jars, one contract jar and one workflow jar. Since all the data and transactional rules are defined in the contract, when transacting over the Corda Network, we will need to check the hashes of the contract jars. Hence, when speaking of signing a CorDapp, we are most likely talking about signing the contract jar. + +The signing option is defined in the build.gradle file of the /workflows and /contracts folder. +``` +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + workflow { + name "4.8LTS Tutorial Flows" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } + signing { + enabled false + } +} +``` +In this example, we disable the signing for the workflow jar. And, for the contract jar, we will add custom keystore to use for signing. +``` +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + contract { + name "4.8LTS Tutorial Contracts" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } + signing { + enabled true + options { + Properties constants = new Properties() + file("$projectDir/../gradle.properties").withInputStream { constants.load(it) } + keystore getProperty('jar.sign.keystore') + alias "cordapp-signer" + storepass getProperty('jar.sign.password') + keypass getProperty('jar.sign.password') + storetype "PKCS12" + } + } +} +``` +In the terminal, create a private key in JKS format (replace the X500 name with yours, and use the same [password] value for both storepass and keypass): + +``` +keytool -keystore jarSignKeystore.jks -keyalg RSA -genkey -dname "OU=, O=, L=, C=" -storepass [password] -keypass [password] -alias cordapp-signer +``` + +Migrate the JKS key to PKCS12 format. You will be prompted for 2 passwords, use the same value that you used to create the JKS key for both values: + +``` +keytool -importkeystore -srckeystore jarSignKeystore.jks -destkeystore jarSignKeystore.pkcs12 -deststoretype pkcs12 +``` + +As you can see, we are importing in variables from the gradle.properties file in the root directory. They are the keystore path and the password for the keystore +``` +jar.sign.keystore = path to PKCS12 file +jar.sign.password = password of PKCS12 file +``` +Once you have edited all the above fields, you can simply gradle task build to execute the building and signing of the jar. +``` +./gradlew build +``` +You will have a signed jar with your keystore. diff --git a/Basic/tutorial-jarsigning/TRADEMARK b/Basic/tutorial-jarsigning/TRADEMARK new file mode 100644 index 00000000..aa7e20f6 --- /dev/null +++ b/Basic/tutorial-jarsigning/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/. diff --git a/Basic/tutorial-jarsigning/build.gradle b/Basic/tutorial-jarsigning/build.gradle new file mode 100644 index 00000000..b2dcf146 --- /dev/null +++ b/Basic/tutorial-jarsigning/build.gradle @@ -0,0 +1,145 @@ +buildscript { //properties that you need to build the project + + Properties constants = new Properties() + file("$projectDir/./constants.properties").withInputStream { constants.load(it) } + + ext { + corda_release_group = constants.getProperty("cordaReleaseGroup") + corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") + corda_release_version = constants.getProperty("cordaVersion") + corda_core_release_version = constants.getProperty("cordaCoreVersion") + 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' + } + + repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://download.corda.net/maven/corda-releases' } + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + 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 { //Properties that you need to compile your project (The application) + apply from: "${rootProject.projectDir}/repositories.gradle" + apply plugin: 'kotlin' + + repositories { + mavenLocal() + + mavenCentral() + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://jitpack.io' } + //SDK lib + maven { url 'https://download.corda.net/maven/corda-lib' } + } + + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { + kotlinOptions { + languageVersion = "1.2" + apiVersion = "1.2" + jvmTarget = "1.8" + javaParameters = true // Useful for reflection. + } + } + + 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") + } + } +} + +//Module dependencis +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$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") + + 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" + cordaDriver "net.corda:corda-shell:4.10" + + +} + +//Task to deploy the nodes in order to bootstrap a network +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + /* This property will load the CorDapps to each of the node by default, including the Notary. You can find them + * in the cordapps folder of the node at build/nodes/Notary/cordapps. However, the notary doesn't really understand + * the notion of cordapps. In production, Notary does not need cordapps as well. This is just a short cut to load + * the Corda network bootstrapper. + */ + nodeDefaults { + projectCordapp { + deploy = false + } + cordapp project(':contracts') + cordapp project(':workflows') + runSchemaMigration = true //This configuration is for any CorDapps with custom schema, We will leave this as true to avoid + //problems for developers who are not familiar with Corda. If you are not using custom schemas, you can change + //it to false for quicker project compiling time. + } + node { + name "O=Notary,L=London,C=GB" + notary = [validating : false] + p2pPort 10002 + rpcSettings { + address("localhost:10003") + adminAddress("localhost:10043") + } + } + node { + name "O=AppleFarm,L=London,C=GB" + p2pPort 10005 + rpcSettings { + address("localhost:10006") + adminAddress("localhost:10046") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=Peter,L=New York,C=US" + p2pPort 10008 + rpcSettings { + address("localhost:10009") + adminAddress("localhost:10049") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + +} diff --git a/Basic/tutorial-jarsigning/config/dev/log4j2.xml b/Basic/tutorial-jarsigning/config/dev/log4j2.xml new file mode 100644 index 00000000..34ba4d45 --- /dev/null +++ b/Basic/tutorial-jarsigning/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/Basic/tutorial-jarsigning/config/test/log4j2.xml b/Basic/tutorial-jarsigning/config/test/log4j2.xml new file mode 100644 index 00000000..cd9926ca --- /dev/null +++ b/Basic/tutorial-jarsigning/config/test/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n + > + + + + + + + + + + + + diff --git a/Basic/tutorial-jarsigning/constants.properties b/Basic/tutorial-jarsigning/constants.properties new file mode 100644 index 00000000..5a20bdc4 --- /dev/null +++ b/Basic/tutorial-jarsigning/constants.properties @@ -0,0 +1,12 @@ +cordaReleaseGroup=net.corda +cordaCoreReleaseGroup=net.corda +cordaVersion=4.9 +cordaCoreVersion=4.9 +gradlePluginsVersion=5.0.12 +kotlinVersion=1.2.71 +junitVersion=4.12 +quasarVersion=0.7.10 +log4jVersion =2.17.1 +platformVersion=10 +slf4jVersion=1.7.25 +nettyVersion=4.1.22.Final diff --git a/Basic/tutorial-jarsigning/contracts/build.gradle b/Basic/tutorial-jarsigning/contracts/build.gradle new file mode 100644 index 00000000..b1f81911 --- /dev/null +++ b/Basic/tutorial-jarsigning/contracts/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'net.corda.plugins.cordapp' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + contract { + name "4.8LTS Tutorial Contracts" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } + signing { + enabled true + options { + Properties constants = new Properties() + file("../gradle.properties").withInputStream { constants.load(it) } + keystore getProperty('jar.sign.keystore') + alias "cordapp-signer" + storepass getProperty('jar.sign.password') + keypass getProperty('jar.sign.password') + storetype "PKCS12" + } + } +} + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" +} diff --git a/Basic/tutorial-jarsigning/contracts/src/main/kotlin/com/tutorial/contracts/AppleStampContract.kt b/Basic/tutorial-jarsigning/contracts/src/main/kotlin/com/tutorial/contracts/AppleStampContract.kt new file mode 100644 index 00000000..c9f73048 --- /dev/null +++ b/Basic/tutorial-jarsigning/contracts/src/main/kotlin/com/tutorial/contracts/AppleStampContract.kt @@ -0,0 +1,42 @@ +package com.tutorial.contracts + +import com.tutorial.states.AppleStamp +import net.corda.core.contracts.CommandData +import net.corda.core.contracts.Contract +import net.corda.core.contracts.Requirements +import net.corda.core.contracts.requireThat +import net.corda.core.transactions.LedgerTransaction + +//Domain Specific Language +class AppleStampContract : Contract { + @Throws(IllegalArgumentException::class) + override fun verify(tx: LedgerTransaction) { + + //Extract the command from the transaction. + val commandData = tx.commands[0].value + + //Verify the transaction according to the intention of the transaction + when (commandData) { + is Commands.Issue -> requireThat { + val output = tx.outputsOfType(AppleStamp::class.java)[0] + "This transaction should only have one AppleStamp state as output".using(tx.outputs.size == 1) + "The output AppleStamp state should have clear description of the type of redeemable goods".using(output.stampDesc != "") + null + } + is BasketOfApplesContract.Commands.Redeem-> requireThat { + //Transaction verification will happen in BasketOfApples Contract + } + } + } + + // Used to indicate the transaction's intent. + interface Commands : CommandData { + //In our hello-world app, We will have two commands. + class Issue : Commands + } + + companion object { + // This is used to identify our contract when building a transaction. + const val ID = "com.tutorial.contracts.AppleStampContract" + } +} \ No newline at end of file diff --git a/Basic/tutorial-jarsigning/contracts/src/main/kotlin/com/tutorial/contracts/BasketOfApplesContract.kt b/Basic/tutorial-jarsigning/contracts/src/main/kotlin/com/tutorial/contracts/BasketOfApplesContract.kt new file mode 100644 index 00000000..44052574 --- /dev/null +++ b/Basic/tutorial-jarsigning/contracts/src/main/kotlin/com/tutorial/contracts/BasketOfApplesContract.kt @@ -0,0 +1,44 @@ +package com.tutorial.contracts + +import com.tutorial.states.AppleStamp +import com.tutorial.states.BasketOfApples +import net.corda.core.contracts.CommandData +import net.corda.core.contracts.Contract +import net.corda.core.contracts.requireThat +import net.corda.core.transactions.LedgerTransaction + +class BasketOfApplesContract : Contract { + @Throws(IllegalArgumentException::class) + override fun verify(tx: LedgerTransaction) { + //Extract the command from the transaction. + val commandData = tx.commands[0].value + val output = tx.outputsOfType(BasketOfApples::class.java)[0] + + when (commandData) { + is Commands.packBasket -> requireThat { + "This transaction should only output one BasketOfApples state".using(tx.outputs.size == 1) + "The output BasketOfApples state should have clear description of Apple product".using(output.description != "") + "The output BasketOfApples state should have non zero weight".using(output.weight > 0) + null + } + is Commands.Redeem -> requireThat { + val input = tx.inputsOfType(AppleStamp::class.java)[0] + "This transaction should consume two states".using(tx.inputStates.size == 2) + "The issuer of the Apple stamp should be the producing farm of this basket of apple".using(input.issuer.equals(output.farm)) + "The basket of apple has to weight more than 0".using(output.weight > 0) + null + } + } + } + + // Used to indicate the transaction's intent. + interface Commands : CommandData { + class packBasket : Commands + class Redeem : Commands + } + + companion object { + // This is used to identify our contract when building a transaction. + const val ID = "com.tutorial.contracts.BasketOfApplesContract" + } +} \ No newline at end of file diff --git a/Basic/tutorial-jarsigning/contracts/src/main/kotlin/com/tutorial/contracts/TemplateContract.kt b/Basic/tutorial-jarsigning/contracts/src/main/kotlin/com/tutorial/contracts/TemplateContract.kt new file mode 100644 index 00000000..e8a1d0a7 --- /dev/null +++ b/Basic/tutorial-jarsigning/contracts/src/main/kotlin/com/tutorial/contracts/TemplateContract.kt @@ -0,0 +1,36 @@ +package com.tutorial.contracts + +import com.tutorial.states.TemplateState +import net.corda.core.contracts.CommandData +import net.corda.core.contracts.Contract +import net.corda.core.contracts.requireSingleCommand +import net.corda.core.transactions.LedgerTransaction +import net.corda.core.contracts.requireThat +// ************ +// * Contract * +// ************ +class TemplateContract : Contract { + companion object { + // Used to identify our contract when building a transaction. + const val ID = "com.tutorial.contracts.TemplateContract" + } + + // A transaction is valid if the verify() function of the contract of all the transaction's input and output states + // does not throw an exception. + override fun verify(tx: LedgerTransaction) { + // Verification logic goes here. + val command = tx.commands.requireSingleCommand() + val output = tx.outputsOfType().first() + when (command.value) { + is Commands.Create -> requireThat { + "No inputs should be consumed when sending the Hello-World message.".using(tx.inputStates.isEmpty()) + "The message must be Hello-World".using(output.msg == "Hello-World") + } + } + } + + // Used to indicate the transaction's intent. + interface Commands : CommandData { + class Create : Commands + } +} \ No newline at end of file diff --git a/Basic/tutorial-jarsigning/contracts/src/main/kotlin/com/tutorial/states/AppleStamp.kt b/Basic/tutorial-jarsigning/contracts/src/main/kotlin/com/tutorial/states/AppleStamp.kt new file mode 100644 index 00000000..5def4572 --- /dev/null +++ b/Basic/tutorial-jarsigning/contracts/src/main/kotlin/com/tutorial/states/AppleStamp.kt @@ -0,0 +1,19 @@ +package com.tutorial.states + +import com.tutorial.contracts.AppleStampContract +import net.corda.core.contracts.BelongsToContract +import net.corda.core.contracts.LinearState +import net.corda.core.contracts.UniqueIdentifier +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.Party +import net.corda.core.serialization.ConstructorForDeserialization +import java.util.ArrayList + +@BelongsToContract(AppleStampContract::class) +class AppleStamp @ConstructorForDeserialization constructor( + val stampDesc : String,//For example: "One stamp can exchange for a basket of HoneyCrispy Apple" + val issuer: Party, //The person who issued the stamp + val holder: Party, //The person who currently owns the stamp + override val linearId: UniqueIdentifier,//LinearState required variable. + override val participants: List = listOf(issuer,holder) + ) : LinearState \ No newline at end of file diff --git a/Basic/tutorial-jarsigning/contracts/src/main/kotlin/com/tutorial/states/BasketOfApples.kt b/Basic/tutorial-jarsigning/contracts/src/main/kotlin/com/tutorial/states/BasketOfApples.kt new file mode 100644 index 00000000..d1350ce9 --- /dev/null +++ b/Basic/tutorial-jarsigning/contracts/src/main/kotlin/com/tutorial/states/BasketOfApples.kt @@ -0,0 +1,29 @@ +package com.tutorial.states + +import com.tutorial.contracts.BasketOfApplesContract +import net.corda.core.contracts.BelongsToContract +import net.corda.core.contracts.ContractState +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.Party + +@BelongsToContract(BasketOfApplesContract::class) +class BasketOfApples(var description : String, //Brand or type + var farm : Party, //Origin of the apple + var owner: Party, //The person who exchange the basket of apple with the stamp. + var weight: Int) + : ContractState { + + //Secondary Constructor + constructor(description: String, farm: Party, weight: Int) : this( + description = description, + farm = farm, + owner = farm, + weight = weight + ) + + override var participants: List = listOf(farm,owner) + + fun changeOwner(buyer: Party): BasketOfApples { + return BasketOfApples(description, farm, buyer, weight) + } +} \ No newline at end of file diff --git a/Basic/tutorial-jarsigning/contracts/src/main/kotlin/com/tutorial/states/TemplateState.kt b/Basic/tutorial-jarsigning/contracts/src/main/kotlin/com/tutorial/states/TemplateState.kt new file mode 100644 index 00000000..d89af8a0 --- /dev/null +++ b/Basic/tutorial-jarsigning/contracts/src/main/kotlin/com/tutorial/states/TemplateState.kt @@ -0,0 +1,17 @@ +package com.tutorial.states + +import com.tutorial.contracts.TemplateContract +import net.corda.core.contracts.BelongsToContract +import net.corda.core.contracts.ContractState +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.Party + +// ********* +// * State * +// ********* +@BelongsToContract(TemplateContract::class) +data class TemplateState(val msg: String, + val sender: Party, + val receiver: Party, + override val participants: List = listOf(sender,receiver) +) : ContractState diff --git a/Basic/tutorial-jarsigning/contracts/src/test/kotlin/com/tutorial/contracts/ContractTests.kt b/Basic/tutorial-jarsigning/contracts/src/test/kotlin/com/tutorial/contracts/ContractTests.kt new file mode 100644 index 00000000..b67ac095 --- /dev/null +++ b/Basic/tutorial-jarsigning/contracts/src/test/kotlin/com/tutorial/contracts/ContractTests.kt @@ -0,0 +1,92 @@ +package com.tutorial.contracts + +import com.tutorial.states.AppleStamp +import net.corda.core.identity.CordaX500Name +import net.corda.testing.core.TestIdentity +import net.corda.testing.node.MockServices +import net.corda.testing.node.ledger +import org.junit.Test +import com.tutorial.states.TemplateState +import net.corda.core.contracts.UniqueIdentifier +import java.security.PublicKey +import java.util.* + +class ContractTests { + private val ledgerServices: MockServices = MockServices(listOf("com.tutorial")) + var alice = TestIdentity(CordaX500Name("Alice", "TestLand", "US")) + var bob = TestIdentity(CordaX500Name("Alice", "TestLand", "US")) + + @Test + fun issuerAndRecipientCannotHaveSameEmail() { + val state = TemplateState("Hello-World", alice.party, bob.party) + ledgerServices.ledger { + // Should fail bid price is equal to previous highest bid + transaction { + //failing transaction + input(TemplateContract.ID, state) + output(TemplateContract.ID, state) + command(alice.publicKey, TemplateContract.Commands.Create()) + fails() + } + //pass + transaction { + //passing transaction + output(TemplateContract.ID, state) + command(alice.publicKey, TemplateContract.Commands.Create()) + verifies() + } + } + } + + @Test + fun stampIssuanceCanOnlyHaveOneOutput(){ + val stamp = AppleStamp("FUji4072", alice.party, bob.party, UniqueIdentifier()) + val stamp2 = AppleStamp("HoneyCrispy7864", alice.party, bob.party, UniqueIdentifier()) + + ledgerServices.ledger { + // Should fail bid price is equal to previous highest bid + transaction { + //failing transaction + output(AppleStampContract.ID, stamp) + output(AppleStampContract.ID, stamp2) + command(alice.publicKey, AppleStampContract.Commands.Issue()) + fails() + } + //pass + transaction { + //passing transaction + output(AppleStampContract.ID, stamp) + command(alice.publicKey, AppleStampContract.Commands.Issue()) + verifies() + } + } + } + + @Test + fun StampMustHaveDescription(){ + val stamp = AppleStamp("", alice.party, bob.party, UniqueIdentifier()) + val stamp2 = AppleStamp("FUji4072", alice.party, bob.party, UniqueIdentifier()) + + ledgerServices.ledger { + // Should fail bid price is equal to previous highest bid + transaction { + //failing transaction + output(AppleStampContract.ID, stamp) + command(Arrays.asList(alice.publicKey, bob.publicKey), AppleStampContract.Commands.Issue()) + fails() + } + //pass + transaction { + //passing transaction + output(AppleStampContract.ID, stamp2) + command(Arrays.asList(alice.publicKey, bob.publicKey), AppleStampContract.Commands.Issue()) + verifies() + } + } + } + + + + + +} \ No newline at end of file diff --git a/Basic/tutorial-jarsigning/contracts/src/test/kotlin/com/tutorial/contracts/StateTests.kt b/Basic/tutorial-jarsigning/contracts/src/test/kotlin/com/tutorial/contracts/StateTests.kt new file mode 100644 index 00000000..5777a684 --- /dev/null +++ b/Basic/tutorial-jarsigning/contracts/src/test/kotlin/com/tutorial/contracts/StateTests.kt @@ -0,0 +1,30 @@ +package com.tutorial.contracts + +import com.tutorial.states.AppleStamp +import com.tutorial.states.TemplateState +import net.corda.core.identity.Party +import org.junit.Test +import kotlin.test.assertEquals + +class StateTests { + @Test + fun hasFieldOfCorrectType() { + // Does the field exist? + TemplateState::class.java.getDeclaredField("msg") + // Is the field of the correct type? + assertEquals(TemplateState::class.java.getDeclaredField("msg").type, String()::class.java) + } + + @Test + fun AppleStampStateHasFieldOfCorrectType(){ + AppleStamp::class.java.getDeclaredField("stampDesc") + assert(AppleStamp::class.java.getDeclaredField("stampDesc").type == String::class.java) + + AppleStamp::class.java.getDeclaredField("issuer") + assert(AppleStamp::class.java.getDeclaredField("issuer").type == Party::class.java) + + AppleStamp::class.java.getDeclaredField("holder") + assert(AppleStamp::class.java.getDeclaredField("issuer").type == Party::class.java) + } + +} \ No newline at end of file diff --git a/Basic/tutorial-jarsigning/gradle.properties b/Basic/tutorial-jarsigning/gradle.properties new file mode 100644 index 00000000..be646102 --- /dev/null +++ b/Basic/tutorial-jarsigning/gradle.properties @@ -0,0 +1,7 @@ +name=Test +group=com.tutorial +version=0.1 +kotlin.incremental=false + +jar.sign.keystore = path to PKCS12 file +jar.sign.password = password of PKCS12 file \ No newline at end of file diff --git a/Basic/tutorial-jarsigning/gradle/wrapper/gradle-wrapper.jar b/Basic/tutorial-jarsigning/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..99340b4a Binary files /dev/null and b/Basic/tutorial-jarsigning/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Basic/tutorial-jarsigning/gradle/wrapper/gradle-wrapper.properties b/Basic/tutorial-jarsigning/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..ae01072d --- /dev/null +++ b/Basic/tutorial-jarsigning/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-5.6.4-all.zip diff --git a/Basic/tutorial-jarsigning/gradlew b/Basic/tutorial-jarsigning/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/Basic/tutorial-jarsigning/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/Basic/tutorial-jarsigning/gradlew.bat b/Basic/tutorial-jarsigning/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/Basic/tutorial-jarsigning/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/Basic/tutorial-jarsigning/repositories.gradle b/Basic/tutorial-jarsigning/repositories.gradle new file mode 100644 index 00000000..8be7b630 --- /dev/null +++ b/Basic/tutorial-jarsigning/repositories.gradle @@ -0,0 +1,8 @@ +repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://jitpack.io' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } +} diff --git a/Basic/tutorial-jarsigning/settings.gradle b/Basic/tutorial-jarsigning/settings.gradle new file mode 100644 index 00000000..2514aca2 --- /dev/null +++ b/Basic/tutorial-jarsigning/settings.gradle @@ -0,0 +1,3 @@ +include 'workflows' +include 'contracts' +include 'clients' \ No newline at end of file diff --git a/Basic/tutorial-jarsigning/workflows/build.gradle b/Basic/tutorial-jarsigning/workflows/build.gradle new file mode 100644 index 00000000..1375782f --- /dev/null +++ b/Basic/tutorial-jarsigning/workflows/build.gradle @@ -0,0 +1,60 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + workflow { + name "4.8LTS Tutorial Flows" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } + signing { + enabled false + } +} + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } + test { + resources { + srcDir rootProject.file("config/test") + } + } + integrationTest { + kotlin { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integrationTest/kotlin') + } + } +} + +configurations { + integrationTestCompile.extendsFrom testCompile + integrationTestRuntime.extendsFrom testRuntime +} + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" + testCompile "junit:junit:$junit_version" + + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":contracts") +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} \ No newline at end of file diff --git a/Basic/tutorial-jarsigning/workflows/src/integrationTest/kotlin/com/tutorial/DriverBasedTest.kt b/Basic/tutorial-jarsigning/workflows/src/integrationTest/kotlin/com/tutorial/DriverBasedTest.kt new file mode 100644 index 00000000..12388f30 --- /dev/null +++ b/Basic/tutorial-jarsigning/workflows/src/integrationTest/kotlin/com/tutorial/DriverBasedTest.kt @@ -0,0 +1,47 @@ +package com.tutorial + +import net.corda.core.identity.CordaX500Name +import net.corda.core.utilities.getOrThrow +import net.corda.testing.core.TestIdentity +import net.corda.testing.driver.DriverDSL +import net.corda.testing.driver.DriverParameters +import net.corda.testing.driver.NodeHandle +import net.corda.testing.driver.driver +import org.junit.Test +import java.util.concurrent.Future +import kotlin.test.assertEquals + +class DriverBasedTest { + private val bankA = TestIdentity(CordaX500Name("BankA", "", "GB")) + private val bankB = TestIdentity(CordaX500Name("BankB", "", "US")) + + @Test + fun `node test`() = withDriver { + // Start a pair of nodes and wait for them both to be ready. + val (partyAHandle, partyBHandle) = startNodes(bankA, bankB) + + // 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(bankB.name, partyAHandle.resolveName(bankB.name)) + assertEquals(bankA.name, partyBHandle.resolveName(bankA.name)) + } + + // Runs a test inside the Driver DSL, which provides useful functions for starting nodes, etc. + private fun withDriver(test: DriverDSL.() -> Unit) = driver( + DriverParameters(isDebug = true, startNodesInProcess = true) + ) { test() } + + // Makes an RPC call to retrieve another node's name from the network map. + private fun NodeHandle.resolveName(name: CordaX500Name) = rpc.wellKnownPartyFromX500Name(name)!!.name + + // Resolves a list of futures to a list of the promised values. + private fun List>.waitForAll(): List = map { it.getOrThrow() } + + // Starts multiple nodes simultaneously, then waits for them all to be ready. + private fun DriverDSL.startNodes(vararg identities: TestIdentity) = identities + .map { startNode(providedName = it.name) } + .waitForAll() +} \ No newline at end of file diff --git a/Basic/tutorial-jarsigning/workflows/src/main/kotlin/com/tutorial/flows/CreateAndIssueAppleStamp.kt b/Basic/tutorial-jarsigning/workflows/src/main/kotlin/com/tutorial/flows/CreateAndIssueAppleStamp.kt new file mode 100644 index 00000000..b3afb0d3 --- /dev/null +++ b/Basic/tutorial-jarsigning/workflows/src/main/kotlin/com/tutorial/flows/CreateAndIssueAppleStamp.kt @@ -0,0 +1,70 @@ +package com.tutorial.flows + +import co.paralleluniverse.fibers.Suspendable +import com.tutorial.contracts.AppleStampContract +import com.tutorial.states.AppleStamp +import net.corda.core.contracts.UniqueIdentifier +import net.corda.core.contracts.requireThat +import net.corda.core.flows.* +import net.corda.core.identity.Party +import net.corda.core.transactions.SignedTransaction +import net.corda.core.transactions.TransactionBuilder +import java.util.* + +@InitiatingFlow +@StartableByRPC +class CreateAndIssueAppleStampInitiator(private val stampDescription: String, private val holder: Party) : FlowLogic(){ + + @Suspendable + override fun call(): SignedTransaction { + + /* Obtain a reference to a notary we wish to use. + * METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* + * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred) + * * - For production you always want to1 use Method 2 as it guarantees the expected notary is returned. + */ + val notary = serviceHub.networkMapCache.notaryIdentities[0] // METHOD 1 + //final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + + //Building the output AppleStamp state + val uniqueID = UniqueIdentifier() + val newStamp = AppleStamp(stampDescription, ourIdentity, this.holder, uniqueID) + + //Compositing the transaction + val txBuilder = TransactionBuilder(notary) + .addOutputState(newStamp) + .addCommand(AppleStampContract.Commands.Issue(), + listOf(ourIdentity.owningKey, holder.owningKey)) + + // Verify that the transaction is valid. + txBuilder.verify(serviceHub) + + // Sign the transaction. + val partSignedTx = serviceHub.signInitialTransaction(txBuilder) + + // Send the state to the counterparty, and receive it back with their signature. + + // Send the state to the counterparty, and receive it back with their signature. + val otherPartySession = initiateFlow(holder) + val fullySignedTx = subFlow( + CollectSignaturesFlow(partSignedTx, Arrays.asList(otherPartySession))) + + // Notarise and record the transaction in both parties' vaults. + return subFlow(FinalityFlow(fullySignedTx, Arrays.asList(otherPartySession))) + } +} + +@InitiatedBy(CreateAndIssueAppleStampInitiator::class) +class CreateAndIssueAppleStampResponder(val counterpartySession: FlowSession) : FlowLogic() { + @Suspendable + override fun call(): SignedTransaction { + val signTransactionFlow = object : SignTransactionFlow(counterpartySession) { + override fun checkTransaction(stx: SignedTransaction) = requireThat { + //Addition checks + } + } + val txId = subFlow(signTransactionFlow).id + return subFlow(ReceiveFinalityFlow(counterpartySession, expectedTxId = txId)) + } +} + diff --git a/Basic/tutorial-jarsigning/workflows/src/main/kotlin/com/tutorial/flows/Flows.kt b/Basic/tutorial-jarsigning/workflows/src/main/kotlin/com/tutorial/flows/Flows.kt new file mode 100644 index 00000000..69e81a62 --- /dev/null +++ b/Basic/tutorial-jarsigning/workflows/src/main/kotlin/com/tutorial/flows/Flows.kt @@ -0,0 +1,83 @@ +package com.tutorial.flows + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.flows.* +import net.corda.core.utilities.ProgressTracker +import net.corda.core.flows.FinalityFlow + +import net.corda.core.flows.CollectSignaturesFlow + +import net.corda.core.transactions.SignedTransaction + +import java.util.stream.Collectors + +import net.corda.core.flows.FlowSession + +import net.corda.core.identity.Party + +import com.tutorial.contracts.TemplateContract + +import net.corda.core.transactions.TransactionBuilder + +import com.tutorial.states.TemplateState +import net.corda.core.contracts.requireThat +import net.corda.core.identity.AbstractParty + + +// ********* +// * Flows * +// ********* +@InitiatingFlow +@StartableByRPC +class Initiator(private val receiver: Party) : FlowLogic() { + override val progressTracker = ProgressTracker() + + @Suspendable + override fun call(): SignedTransaction { + //Hello World message + val msg = "Hello-World" + val sender = ourIdentity + + // Step 1. Get a reference to the notary service on our network and our key pair. + // Note: ongoing work to support multiple notary identities is still in progress. + val notary = serviceHub.networkMapCache.notaryIdentities[0] + + //Compose the State that carries the Hello World message + val output = TemplateState(msg, sender, receiver) + + // Step 3. Create a new TransactionBuilder object. + val builder = TransactionBuilder(notary) + .addCommand(TemplateContract.Commands.Create(), listOf(sender.owningKey, receiver.owningKey)) + .addOutputState(output) + + // Step 4. Verify and sign it with our KeyPair. + builder.verify(serviceHub) + val ptx = serviceHub.signInitialTransaction(builder) + + + // Step 6. Collect the other party's signature using the SignTransactionFlow. + val otherParties: MutableList = output.participants.stream().map { el: AbstractParty? -> el as Party? }.collect(Collectors.toList()) + otherParties.remove(ourIdentity) + val sessions = otherParties.stream().map { el: Party? -> initiateFlow(el!!) }.collect(Collectors.toList()) + + val stx = subFlow(CollectSignaturesFlow(ptx, sessions)) + + // Step 7. Assuming no exceptions, we can now finalise the transaction + return subFlow(FinalityFlow(stx, sessions)) + } +} + +@InitiatedBy(Initiator::class) +class Responder(val counterpartySession: FlowSession) : FlowLogic() { + @Suspendable + override fun call(): SignedTransaction { + val signTransactionFlow = object : SignTransactionFlow(counterpartySession) { + override fun checkTransaction(stx: SignedTransaction) = requireThat { + //Addition checks + } + } + val txId = subFlow(signTransactionFlow).id + return subFlow(ReceiveFinalityFlow(counterpartySession, expectedTxId = txId)) + } +} + diff --git a/Basic/tutorial-jarsigning/workflows/src/main/kotlin/com/tutorial/flows/PackageApples.kt b/Basic/tutorial-jarsigning/workflows/src/main/kotlin/com/tutorial/flows/PackageApples.kt new file mode 100644 index 00000000..bdf1e2a0 --- /dev/null +++ b/Basic/tutorial-jarsigning/workflows/src/main/kotlin/com/tutorial/flows/PackageApples.kt @@ -0,0 +1,43 @@ +package com.tutorial.flows + +import co.paralleluniverse.fibers.Suspendable +import com.tutorial.contracts.BasketOfApplesContract.Commands.packBasket +import com.tutorial.states.BasketOfApples +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 +class PackApplesInitiator(private val appleDescription: String, private val weight: Int) : FlowLogic(){ + + @Suspendable + override fun call(): SignedTransaction { + + /* Obtain a reference to a notary we wish to use. + * METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* + * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred) + * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. + */ + val notary = serviceHub.networkMapCache.notaryIdentities[0] // METHOD 1 + //final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + + //Create the output object + val basket = BasketOfApples(description = appleDescription, farm = ourIdentity, weight = weight) + + //Building transaction + val txBuilder = TransactionBuilder(notary) + .addOutputState(basket) + .addCommand(packBasket(), ourIdentity.owningKey) + + // Verify the transaction + txBuilder.verify(serviceHub) + + // Sign the transaction + val signedTransaction = serviceHub.signInitialTransaction(txBuilder) + + // Notarise the transaction and record the states in the ledger. + return subFlow(FinalityFlow(signedTransaction, emptyList())) + } +} diff --git a/Basic/tutorial-jarsigning/workflows/src/main/kotlin/com/tutorial/flows/RedeemApples.kt b/Basic/tutorial-jarsigning/workflows/src/main/kotlin/com/tutorial/flows/RedeemApples.kt new file mode 100644 index 00000000..9f006d78 --- /dev/null +++ b/Basic/tutorial-jarsigning/workflows/src/main/kotlin/com/tutorial/flows/RedeemApples.kt @@ -0,0 +1,89 @@ +package com.tutorial.flows + +import co.paralleluniverse.fibers.Suspendable +import com.tutorial.contracts.BasketOfApplesContract +import com.tutorial.states.AppleStamp +import com.tutorial.states.BasketOfApples +import net.corda.core.contracts.StateAndRef +import net.corda.core.contracts.UniqueIdentifier +import net.corda.core.flows.* +import net.corda.core.identity.Party +import net.corda.core.node.services.Vault.RelevancyStatus +import net.corda.core.node.services.Vault.StateStatus +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.* + +@InitiatingFlow +@StartableByRPC +class RedeemApplesInitiator(private val buyer: Party, private val stampId: UniqueIdentifier) : FlowLogic() { + + @Suspendable + override fun call(): SignedTransaction { + + /* Obtain a reference to a notary we wish to use. + * METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* + * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred) + * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. + */ + val notary = serviceHub.networkMapCache.notaryIdentities[0] // METHOD 1 + //final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + + //Query the AppleStamp + val inputCriteria = QueryCriteria.LinearStateQueryCriteria() + .withUuid(listOf(UUID.fromString(stampId.toString()))) + .withStatus(StateStatus.UNCONSUMED) + .withRelevancyStatus(RelevancyStatus.RELEVANT) + val appleStampStateAndRef: StateAndRef<*> = serviceHub.vaultService.queryBy(AppleStamp::class.java, inputCriteria).states.get(0) + + //Query output BasketOfApples + val outputCriteria: QueryCriteria = QueryCriteria.VaultQueryCriteria() + .withStatus(StateStatus.UNCONSUMED) + .withRelevancyStatus(RelevancyStatus.RELEVANT) + val BasketOfApplesStateAndRef: StateAndRef<*> = serviceHub.vaultService.queryBy(BasketOfApples::class.java, outputCriteria).states[0] + val originalBasketOfApples = BasketOfApplesStateAndRef.state.data as BasketOfApples + + //Modify output to address the owner change + val output = originalBasketOfApples.changeOwner(buyer) + + //Build Transaction + val txBuilder = TransactionBuilder(notary) + .addInputState(appleStampStateAndRef) + .addInputState(BasketOfApplesStateAndRef) + .addOutputState(output, BasketOfApplesContract.ID) + .addCommand(BasketOfApplesContract.Commands.Redeem(), + Arrays.asList(ourIdentity.owningKey, buyer.owningKey)) + + // Verify that the transaction is valid. + txBuilder.verify(serviceHub) + + // Sign the transaction. + val partSignedTx = serviceHub.signInitialTransaction(txBuilder) + + // Send the state to the counterparty, and receive it back with their signature. + val otherPartySession = initiateFlow(buyer) + val fullySignedTx = subFlow( + CollectSignaturesFlow(partSignedTx, Arrays.asList(otherPartySession))) + + // Notarise and record the transaction in both parties' vaults. + return subFlow(FinalityFlow(fullySignedTx, Arrays.asList(otherPartySession))) + } +} + +@InitiatedBy(RedeemApplesInitiator::class) +class RedeemApplesResponder(private val counterpartySession: FlowSession) : FlowLogic() { + @Suspendable + @Throws(FlowException::class) + override fun call(): SignedTransaction { + val signedTransaction = subFlow(object : SignTransactionFlow(counterpartySession) { + @Throws(FlowException::class) + override fun checkTransaction(stx: SignedTransaction) { + } + }) + + //Stored the transaction into data base. + return subFlow(ReceiveFinalityFlow(counterpartySession, signedTransaction.id)) + } +} diff --git a/Basic/tutorial-jarsigning/workflows/src/test/kotlin/com/tutorial/CreateAndIssueAppleStampTest.kt b/Basic/tutorial-jarsigning/workflows/src/test/kotlin/com/tutorial/CreateAndIssueAppleStampTest.kt new file mode 100644 index 00000000..911c04ea --- /dev/null +++ b/Basic/tutorial-jarsigning/workflows/src/test/kotlin/com/tutorial/CreateAndIssueAppleStampTest.kt @@ -0,0 +1,67 @@ +package com.tutorial + +import com.google.common.collect.ImmutableList +import com.tutorial.flows.CreateAndIssueAppleStampInitiator +import com.tutorial.flows.Initiator +import com.tutorial.states.AppleStamp +import com.tutorial.states.TemplateState +import net.corda.core.node.services.Vault.StateStatus +import net.corda.core.node.services.vault.QueryCriteria +import net.corda.core.transactions.SignedTransaction +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 +import java.util.concurrent.Future + +class CreateAndIssueAppleStampTest { + + private var network: MockNetwork? = null + private var a: StartedMockNode? = null + private var b: StartedMockNode? = null + + @Before + fun setup() { + network = MockNetwork(MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("com.tutorial.contracts"), + TestCordapp.findCordapp("com.tutorial.flows")))) + a = network!!.createPartyNode(null) + b = network!!.createPartyNode(null) + network!!.runNetwork() + } + + @After + fun tearDown() { + network!!.stopNodes() + } + + @Test + fun dummyTest() { + val flow = Initiator(b!!.info.legalIdentities[0]) + val future: Future = a!!.startFlow(flow) + network!!.runNetwork() + + //successful query means the state is stored at node b's vault. Flow went through. + val inputCriteria: QueryCriteria = QueryCriteria.VaultQueryCriteria().withStatus(StateStatus.UNCONSUMED) + val state = b!!.services.vaultService + .queryBy(TemplateState::class.java, inputCriteria).states[0].state.data + } + + @Test + fun CreateAndIssueAppleStampTest() { + val flow1 = CreateAndIssueAppleStampInitiator( + "HoneyCrispy 4072", b!!.info.legalIdentities[0]) + val future1: Future = a!!.startFlow(flow1) + network!!.runNetwork() + + //successful query means the state is stored at node b's vault. Flow went through. + val inputCriteria: QueryCriteria = QueryCriteria.VaultQueryCriteria() + .withStatus(StateStatus.UNCONSUMED) + val state = b!!.services.vaultService + .queryBy(AppleStamp::class.java, inputCriteria).states[0].state.data + assert(state.stampDesc == "HoneyCrispy 4072") + } +} \ No newline at end of file diff --git a/Basic/tutorial-jarsigning/workflows/src/test/kotlin/com/tutorial/FarmerSelfCreateBasketOfApplesTest.kt b/Basic/tutorial-jarsigning/workflows/src/test/kotlin/com/tutorial/FarmerSelfCreateBasketOfApplesTest.kt new file mode 100644 index 00000000..2191cc6a --- /dev/null +++ b/Basic/tutorial-jarsigning/workflows/src/test/kotlin/com/tutorial/FarmerSelfCreateBasketOfApplesTest.kt @@ -0,0 +1,53 @@ +package com.tutorial + +import com.google.common.collect.ImmutableList +import com.tutorial.flows.PackApplesInitiator +import com.tutorial.states.BasketOfApples +import net.corda.core.node.services.Vault.StateStatus +import net.corda.core.node.services.vault.QueryCriteria +import net.corda.core.transactions.SignedTransaction +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 +import java.util.concurrent.Future + +class FarmerSelfCreateBasketOfApplesTest { + private var network: MockNetwork? = null + private var a: StartedMockNode? = null + private var b: StartedMockNode? = null + @Before + fun setup() { + network = MockNetwork(MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("com.tutorial.contracts"), + TestCordapp.findCordapp("com.tutorial.flows")))) + a = network!!.createPartyNode(null) + b = network!!.createPartyNode(null) + network!!.runNetwork() + } + + @After + fun tearDown() { + network!!.stopNodes() + } + + @Test + fun createBasketOfApples() { + val flow1 = PackApplesInitiator("Fuji4072", 10) + val future: Future = a!!.startFlow(flow1) + network!!.runNetwork() + + //successful query means the state is stored at node b's vault. Flow went through. + val inputCriteria: QueryCriteria = QueryCriteria.VaultQueryCriteria().withStatus(StateStatus.UNCONSUMED) + val state = a!!.services.vaultService + .queryBy(BasketOfApples::class.java, inputCriteria).states[0].state.data + println("-------------------------") + println(state.owner) + println("-------------------------") + assert(state.description == "Fuji4072") + } +} + diff --git a/Basic/tutorial-jarsigning/workflows/src/test/kotlin/com/tutorial/FlowTests.kt b/Basic/tutorial-jarsigning/workflows/src/test/kotlin/com/tutorial/FlowTests.kt new file mode 100644 index 00000000..743ffa84 --- /dev/null +++ b/Basic/tutorial-jarsigning/workflows/src/test/kotlin/com/tutorial/FlowTests.kt @@ -0,0 +1,45 @@ +package com.tutorial + +import net.corda.testing.node.* +import org.junit.After +import org.junit.Before +import org.junit.Test +import com.tutorial.states.TemplateState +import java.util.concurrent.Future; +import net.corda.core.node.services.vault.QueryCriteria +import net.corda.core.transactions.SignedTransaction +import com.tutorial.flows.Initiator +import net.corda.core.node.services.Vault.StateStatus + + +class FlowTests { + private lateinit var network: MockNetwork + private lateinit var a: StartedMockNode + private lateinit var b: StartedMockNode + + @Before + fun setup() { + network = MockNetwork(MockNetworkParameters(cordappsForAllNodes = listOf( + TestCordapp.findCordapp("com.tutorial.contracts"), + TestCordapp.findCordapp("com.tutorial.flows") + ))) + a = network.createPartyNode() + b = network.createPartyNode() + network.runNetwork() + } + + @After + fun tearDown() { + network.stopNodes() + } + @Test + fun `DummyTest`() { + val flow = Initiator(b.info.legalIdentities[0]) + val future: Future = a.startFlow(flow) + network.runNetwork() + + //successful query means the state is stored at node b's vault. Flow went through. + val inputCriteria: QueryCriteria = QueryCriteria.VaultQueryCriteria().withStatus(StateStatus.UNCONSUMED) + val state = b.services.vaultService.queryBy(TemplateState::class.java, inputCriteria).states[0].state.data + } +} \ No newline at end of file diff --git a/Basic/tutorial-jarsigning/workflows/src/test/kotlin/com/tutorial/RedeemApplesWithStampTest.kt b/Basic/tutorial-jarsigning/workflows/src/test/kotlin/com/tutorial/RedeemApplesWithStampTest.kt new file mode 100644 index 00000000..b96454e6 --- /dev/null +++ b/Basic/tutorial-jarsigning/workflows/src/test/kotlin/com/tutorial/RedeemApplesWithStampTest.kt @@ -0,0 +1,69 @@ +package com.tutorial + +import com.google.common.collect.ImmutableList +import com.tutorial.flows.CreateAndIssueAppleStampInitiator +import com.tutorial.flows.PackApplesInitiator +import com.tutorial.flows.RedeemApplesInitiator +import com.tutorial.states.AppleStamp +import com.tutorial.states.BasketOfApples +import net.corda.core.contracts.UniqueIdentifier +import net.corda.core.node.services.Vault.StateStatus +import net.corda.core.node.services.vault.QueryCriteria +import net.corda.core.transactions.SignedTransaction +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 +import java.util.concurrent.ExecutionException +import java.util.concurrent.Future + +class RedeemApplesWithStampTest { + private var network: MockNetwork? = null + private var a: StartedMockNode? = null + private var b: StartedMockNode? = null + @Before + fun setup() { + network = MockNetwork(MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("com.tutorial.contracts"), + TestCordapp.findCordapp("com.tutorial.flows")))) + a = network!!.createPartyNode(null) + b = network!!.createPartyNode(null) + network!!.runNetwork() + } + + @After + fun tearDown() { + network!!.stopNodes() + } + + @Test + @Throws(ExecutionException::class, InterruptedException::class) + fun buyerRedeemBasketOfApples() { + //Create Basket of Apples + val createBasketOfApples = PackApplesInitiator("Fuji4072", 10) + val future: Future = a!!.startFlow(createBasketOfApples) + network!!.runNetwork() + + //Issue Apple Stamp + val issueAppleStamp = CreateAndIssueAppleStampInitiator( + "Fuji4072", b!!.info.legalIdentities[0]) + val future1: Future = a!!.startFlow(issueAppleStamp) + network!!.runNetwork() + val issuedStamp = future1.get().tx.outputStates[0] as AppleStamp + val id = issuedStamp.linearId + + //Redeem Basket of Apples with stamp + val redeemApples = RedeemApplesInitiator(b!!.info.legalIdentities[0], id) + val future2: Future = a!!.startFlow(redeemApples) + network!!.runNetwork() + + //successful query means the state is stored at node b's vault. Flow went through. + val outputCriteria: QueryCriteria = QueryCriteria.VaultQueryCriteria().withStatus(StateStatus.UNCONSUMED) + val state = b!!.services.vaultService + .queryBy(BasketOfApples::class.java, outputCriteria).states[0].state.data + assert(state.description == "Fuji4072") + } +} \ No newline at end of file diff --git a/Basic/tutorial-networkbootstrapper/Notary_node.conf b/Basic/tutorial-networkbootstrapper/Notary_node.conf new file mode 100644 index 00000000..e5fc437c --- /dev/null +++ b/Basic/tutorial-networkbootstrapper/Notary_node.conf @@ -0,0 +1,32 @@ +devMode=true +myLegalName="O=Notary,L=London,C=GB" +notary { + validating=false +} + +//remote Vm address +p2pAddress="20.222.71.244:10002" + +rpcSettings { + address="0.0.0.0:10003" + adminAddress="0.0.0.0:10103" + standAloneBroker=false + useSsl=false +} +security { + authService { + dataSource { + type=INMEMORY + users=[ + { + password=password + permissions=[ + ALL + ] + user=user1 + } + ] + } + } +} + diff --git a/Basic/tutorial-networkbootstrapper/PA_node.conf b/Basic/tutorial-networkbootstrapper/PA_node.conf new file mode 100644 index 00000000..2d9f7cc5 --- /dev/null +++ b/Basic/tutorial-networkbootstrapper/PA_node.conf @@ -0,0 +1,33 @@ +devMode=true +myLegalName="O=PA,L=London,C=GB" + +//remote VM address +p2pAddress="13.71.147.131:10007" + +rpcSettings { + address="0.0.0.0:10008" + adminAddress="0.0.0.0:10108" + standAloneBroker=false + useSsl=false +} +security { + authService { + dataSource { + type=INMEMORY + users=[ + { + password=password + permissions=[ + ALL + ] + user=user1 + } + ] + } + } +} +sshd { + port = 2222 +} + + diff --git a/Basic/tutorial-networkbootstrapper/PB_node.conf b/Basic/tutorial-networkbootstrapper/PB_node.conf new file mode 100644 index 00000000..8088047d --- /dev/null +++ b/Basic/tutorial-networkbootstrapper/PB_node.conf @@ -0,0 +1,29 @@ +devMode=true +myLegalName="O=PB,L=London,C=GB" + +//remote vm address +p2pAddress="20.222.71.244:10009" + +rpcSettings { + address="0.0.0.0:10010" + adminAddress="0.0.0.0:10110" + standAloneBroker=false + useSsl=false +} +security { + authService { + dataSource { + type=INMEMORY + users=[ + { + password=password + permissions=[ + ALL + ] + user=user1 + } + ] + } + } +} + diff --git a/Basic/tutorial-networkbootstrapper/README.md b/Basic/tutorial-networkbootstrapper/README.md new file mode 100644 index 00000000..6dabe46b --- /dev/null +++ b/Basic/tutorial-networkbootstrapper/README.md @@ -0,0 +1,56 @@ +# Network Bootstrapper Tutorial + +In this tutorial, we will walk through the steps of bootstrapping a Corda Network using the Corda Network Bootstrapper. This tutorial consists of 3 node Config files and 2 shell scripts to ease up the manual copying of the files and folders. + +Due to the size cap of the GitHub uploaded file, you will need to manually download the Corda Network Bootstrapper (to this directory). The download link is at [here](https://software.r3.com/ui/native/corda-releases/net/corda/corda-tools-network-bootstrapper) + +## Deploy a local Corda Network via the bootstrapper +With the Corda Network Bootstrapper downloaded to this directory, you can simply call: (with the version name of the bootstrapper that you downloaded) +``` +java -jar corda-tools-network-bootstrapper-4.9.jar +``` +This command will generate the node folders that corresponds with each node config file. You should expect some folder structure like this: +``` +. +├── network_Bootstrapper.jar +├── Notary_node.conf +├── PA_node.conf +├── PB_node.conf +├── cordapp-a.jar +├── cordapp-b.jar +├── checkpoints_xxxxxx.log //Logs of bootstrapping +├── diagnostic-xxxxxxx.log //Logs of bootstrapping +├── node-xxxxx.log //Logs of bootstrapping +├── Notary //Notary's folder +├── PA //PA's folder +└── PB //PB's folder +``` +Next, you will need to go into each node folder and start the node. +``` +cd PA +java -jar corda.jar +``` +Then, you can go into the PB folder and Notary folder to do the same steps, and you will have a Corda network running on your local machine. + +## Deploy a Corda Network onto VMs via the bootstrapper + +When deploying a Corda network to remote VMs, you would need to do an additional step before bootstrap the node folders. You would need to go into the node.conf file and add the VM address. For example: (this is not the full config file, you still need other fields.) +``` +//remote VM address +p2pAddress="13.71.147.131:10007" +rpcSettings { + address="0.0.0.0:10008" + adminAddress="0.0.0.0:10108" + standAloneBroker=false + useSsl=false +} +``` +After change the p2pAddress, you can now perform the bootstrapping with +``` +java -jar corda-tools-network-bootstrapper-4.9.jar +``` +Next, we need to drop the node folder to the remote VMs. I simply use the `scp` command. For example: +``` +scp -r ./PartyA user@13.71.147.131:./ +``` +The steps for starting the node would be the same with doing so on a local machine. diff --git a/Basic/tutorial-networkbootstrapper/bootstrapp.sh b/Basic/tutorial-networkbootstrapper/bootstrapp.sh new file mode 100644 index 00000000..8b85ff13 --- /dev/null +++ b/Basic/tutorial-networkbootstrapper/bootstrapp.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +echo "Bootstrapping the network" +java -jar corda-tools-network-bootstrapper-4.9.jar + + diff --git a/Basic/tutorial-networkbootstrapper/clean.sh b/Basic/tutorial-networkbootstrapper/clean.sh new file mode 100644 index 00000000..2dd973e0 --- /dev/null +++ b/Basic/tutorial-networkbootstrapper/clean.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +echo "Remove the generated node folders." +rm -rf ./PA +rm -rf ./PB +rm -rf ./Notary + +echo "Remove the bootstrapping log files." +rm checkpoints* +rm diagnostic* +rm node-admin* diff --git a/BusinessNetworks/constants.properties b/BusinessNetworks/constants.properties new file mode 100644 index 00000000..2e4426cc --- /dev/null +++ b/BusinessNetworks/constants.properties @@ -0,0 +1,13 @@ +cordaReleaseGroup=net.corda +cordaCoreReleaseGroup=net.corda +cordaVersion=4.10 +cordaCoreVersion=4.10 +gradlePluginsVersion=5.0.12 +kotlinVersion=1.2.71 +junitVersion=4.12 +quasarVersion=0.7.10 +log4jVersion =2.17.1 +platformVersion=12 +slf4jVersion=1.7.25 +nettyVersion=4.1.22.Final +corda_bn_extension_version=1.1-RC03 diff --git a/BusinessNetworks/insurancebusinessnetwork/LICENCE b/BusinessNetworks/insurancebusinessnetwork/LICENCE new file mode 100644 index 00000000..3ff572d1 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/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/BusinessNetworks/insurancebusinessnetwork/MockDiagram.jpeg b/BusinessNetworks/insurancebusinessnetwork/MockDiagram.jpeg new file mode 100644 index 00000000..dc8c0230 Binary files /dev/null and b/BusinessNetworks/insurancebusinessnetwork/MockDiagram.jpeg differ diff --git a/BusinessNetworks/insurancebusinessnetwork/README.md b/BusinessNetworks/insurancebusinessnetwork/README.md new file mode 100644 index 00000000..4890f098 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/README.md @@ -0,0 +1,105 @@ +# Insurance Business Network + + +This sample will show you how to deploy and manage a business network. Our example use case is a mock insurance consortium. + +

+ Corda +

+ +### Concept: +In this app, we will have a global insurance network, where participants are either insurance companies or different kind of health care providers. +With the help of business network extension, we can further breakdown the global network into smaller pieces as groups, such as APAC_Insurance_Alliance. + +In our sample, we will have three nodes, named as: +* NetworkOperator <- Business Network Operator +* Insurance <- Insurance Company that is in the network +* CarePro <- Care Provider of the network + +The NetworkOperator will create and primarily manage the network. As introduced in the SDK docs, NetworkOperator will be the default authorized user of this global network. And the other two nodes will fill the roles which can be easily tell by its name. + +#### The corDapp will run with the following steps: +1. Network creations by NetworkOperator +2. The rest of the nodes request join the network +3. The NetworkOperator will query all the request and active the membership status for the other nodes. +4. The NetworkOperator will then create a subgroup out of the global insurance network called APAC_Insurance_Alliance, and include the two other nodes in the network. +5. The NetworkOperator will then assign custom network identity to the nodes. The insurer node will get an insurance identity, the carePro node will get a health care provider identity. +6. Custom network identity comes with custom roles. We will give the insurer node a policy. + As of now, the network setup is done. The very last step is to run a transaction between the insurer and the carePro node + +### Usage + +#### Running the CorDapp + +Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) +``` +./gradlew clean build deployNodes +``` +Then type: (to run the nodes) +``` +./build/nodes/runnodes +``` +#### Interacting with the CorDapp + +**Step 1:** Create the network in NetworkOperator's terminal +``` +flow start CreateNetwork +``` +Sample output: +``` +Mon Apr 12 10:35:47 EDT 2021>>> flow start CreateNetwork + + ✅ Starting +➡️ Done +Flow completed with result: +A network was created with NetworkID: 121577cf-30bf-4e20-9c7d-97f0b4628b06 <- This is what you need in Step 2 +``` +**Step 2:** 2 non-member makes the request to join the network. Fill in the networkId with what was return from Step1 +``` +flow start RequestMembership authorisedParty: NetworkOperator, networkId: +``` +**Step 3:** go back to the admin node, and query all the membership requests. +``` +flow start QueryAllMembers +``` +**Step 4:** In this step, Network Operator will activate the pending memberships +Insurance: fill in the Insurance node MembershipId that is display in the previous query +``` +flow start ActiveMembers membershipId: +``` +CarePro: fill in the CarePro node MembershipId that is display in the previous query +``` +flow start ActiveMembers membershipId: +``` + +**Step 5:** Admin create subgroup and add group members. +``` +flow start CreateNetworkSubGroup networkId: , groupName: APAC_Insurance_Alliance, groupParticipants: [, , ] +``` +**Step 6:** Admin assign business identity to a member. +``` +flow start AssignBNIdentity firmType: InsuranceFirm, membershipId: , bnIdentity: APACIN76CZX +``` +**Step 7:** Admin assign business identity to the second member +``` +flow start AssignBNIdentity firmType: CareProvider, membershipId: , bnIdentity: APACCP44OJS +``` +**Step 8:** Admin assign business identity related ROLE to the member. +``` +flow start AssignPolicyIssuerRole membershipId: , networkId: +``` +Now to see our membership states, we can run these vault queries. +``` +run vaultQuery contractStateType: net.corda.core.contracts.ContractState +run vaultQuery contractStateType: net.corda.bn.states.MembershipState +``` +-------------------Network setup is done, and business flow begins-------------------------- + +**Step 9:** The insurance Company will issue a policy to insuree. The flow initiator (the insurance company) has to be a member of the Business network, has to have a insuranceIdentity, and has to have issuer Role, and has to have issuance permission. +``` +flow start IssuePolicyInitiator networkId: , careProvider: CarePro, insuree: PeterLi +``` +**Step 10:** Query the state from the CarePro node. +``` +run vaultQuery contractStateType: net.corda.samples.businessmembership.states.InsuranceState +``` diff --git a/BusinessNetworks/insurancebusinessnetwork/TRADEMARK b/BusinessNetworks/insurancebusinessnetwork/TRADEMARK new file mode 100644 index 00000000..d2e056b5 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/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/BusinessNetworks/insurancebusinessnetwork/build.gradle b/BusinessNetworks/insurancebusinessnetwork/build.gradle new file mode 100644 index 00000000..77d083c8 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/build.gradle @@ -0,0 +1,153 @@ +buildscript {//properties that you need to build the project + Properties constants = new Properties() + file("$projectDir/../constants.properties").withInputStream { constants.load(it) } + + ext { + corda_release_group = constants.getProperty("cordaReleaseGroup") + corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") + corda_release_version = constants.getProperty("cordaVersion") + corda_core_release_version = constants.getProperty("cordaCoreVersion") + 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' + //Membership Attestation + corda_bn_extension_version=constants.getProperty("corda_bn_extension_version") + } + + repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://download.corda.net/maven/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 {//Properties that you need to compile your project (The application) + apply from: "${rootProject.projectDir}/repositories.gradle" + apply plugin: 'java' + + repositories { + mavenLocal() + + mavenCentral() + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://download.corda.net/maven/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 + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} + +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") + } + } +} +//Module dependencis +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_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") + cordapp("net.corda.bn:business-networks-contracts:$corda_bn_extension_version") + cordapp("net.corda.bn:business-networks-workflows:$corda_bn_extension_version") + + 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" + cordaDriver "net.corda:corda-shell:4.10" + +} + + +//Task to deploy the nodes in order to bootstrap a network +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + + /* This property will load the CorDapps to each of the node by default, including the Notary. You can find them + * in the cordapps folder of the node at build/nodes/Notary/cordapps. However, the notary doesn't really understand + * the notion of cordapps. In production, Notary does not need cordapps as well. This is just a short cut to load + * the Corda network bootstrapper. + */ + nodeDefaults { + projectCordapp { + deploy = false + } + cordapp project(':contracts') + cordapp project(':workflows') + cordapp("net.corda.bn:business-networks-contracts:$corda_bn_extension_version") + cordapp("net.corda.bn:business-networks-workflows:$corda_bn_extension_version") + runSchemaMigration = true //This configuration is for any CorDapps with custom schema, We will leave this as true to avoid + //problems for developers who are not familiar with Corda. If you are not using custom schemas, you can change + //it to false for quicker project compiling time. + } + node { + name "O=Notary,L=London,C=GB" + notary = [validating : false] + p2pPort 10002 + rpcSettings { + address("localhost:10003") + adminAddress("localhost:10043") + } + } + node { + name "O=NetworkOperator,L=London,C=GB" + p2pPort 10005 + rpcSettings { + address("localhost:10006") + adminAddress("localhost:10046") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=Insurance,L=New York,C=US" + p2pPort 10008 + rpcSettings { + address("localhost:10009") + adminAddress("localhost:10049") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=CarePro,L=New York,C=US" + p2pPort 10011 + rpcSettings { + address("localhost:10012") + adminAddress("localhost:10052") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } +} diff --git a/BusinessNetworks/insurancebusinessnetwork/config/dev/log4j2.xml b/BusinessNetworks/insurancebusinessnetwork/config/dev/log4j2.xml new file mode 100644 index 00000000..34ba4d45 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/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/BusinessNetworks/insurancebusinessnetwork/config/test/log4j2.xml b/BusinessNetworks/insurancebusinessnetwork/config/test/log4j2.xml new file mode 100644 index 00000000..cd9926ca --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/config/test/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n + > + + + + + + + + + + + + diff --git a/BusinessNetworks/insurancebusinessnetwork/contracts/build.gradle b/BusinessNetworks/insurancebusinessnetwork/contracts/build.gradle new file mode 100644 index 00000000..6e09fc22 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/contracts/build.gradle @@ -0,0 +1,36 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + contract { + name "Membership Attestation Contracts" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main{ + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + } + test{ + java{ + srcDir 'src/test/java' + java.outputDir = file('bin/test') + } + } +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + cordapp("net.corda.bn:business-networks-contracts:$corda_bn_extension_version") +} \ No newline at end of file diff --git a/BusinessNetworks/insurancebusinessnetwork/contracts/src/main/java/net/corda/samples/businessmembership/contracts/InsuranceStateContract.java b/BusinessNetworks/insurancebusinessnetwork/contracts/src/main/java/net/corda/samples/businessmembership/contracts/InsuranceStateContract.java new file mode 100644 index 00000000..42dbfaaf --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/contracts/src/main/java/net/corda/samples/businessmembership/contracts/InsuranceStateContract.java @@ -0,0 +1,81 @@ +package net.corda.samples.businessmembership.contracts; + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import net.corda.samples.businessmembership.states.CareProviderIdentity; +import net.corda.samples.businessmembership.states.InsuranceState; +import net.corda.samples.businessmembership.states.InsurerIdentity; +import org.jetbrains.annotations.NotNull; +import net.corda.bn.states.MembershipState; +import net.corda.core.identity.Party; + +import java.lang.IllegalArgumentException; +import java.util.List; +import java.util.stream.Collectors; + +import static net.corda.core.contracts.ContractsDSL.requireThat; + +public class InsuranceStateContract implements Contract { + + public static final String InsuranceStateContract_ID = "net.corda.samples.businessmembership.contracts.InsuranceStateContract"; + + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + CommandData command = tx.getCommands().get(0).getValue(); + InsuranceState output = (InsuranceState) tx.getOutputs().get(0).getData(); + if (command instanceof Commands.Issue){ + verifyIssue(tx,output.getNetworkId(), output.getInsurer(), output.getCareProvider()); + }else{ + throw new IllegalArgumentException("Unsupported command "+command); + } + } + + public void verifyIssue(LedgerTransaction tx, String networkId, Party insurance, Party careProvider){ + verifyMembershipsForMedInsuranceTransaction(tx, networkId, insurance, careProvider, "Issue"); + + } + public void verifyMembershipsForMedInsuranceTransaction(LedgerTransaction tx, String networkId, + Party insurance, Party careProvider,String commandName){ + requireThat(require -> { + //Verify number of memberships + require.using("Insurance "+ commandName+" transaction should have 2 reference states", tx.getReferences().size() == 2); + require.using("Insurance "+ commandName+" transaction should contain only reference MembershipStates", + tx.getReferenceStates().stream().allMatch(it -> it.getClass() == MembershipState.class)); + + //Extract memberships + List membershipReferenceStates = tx.getReferenceStates().stream().map( it -> (MembershipState) it).collect(Collectors.toList()); + require.using("Insurance "+ commandName+ + " transaction should contain only reference membership states from Business Network with "+networkId+" ID", + membershipReferenceStates.stream().allMatch(it -> it.getNetworkId().equals(networkId))); + + //Extract Membership and verify not null + MembershipState insuranceMembership = membershipReferenceStates.stream() + .filter(it -> (it.getNetworkId().equals(networkId) && it.getIdentity().getCordaIdentity().equals(insurance))) + .collect(Collectors.toList()).get(0); + require.using("\nInsurance "+ commandName+" transaction should have insurance's reference membership state", insuranceMembership!= null); + + MembershipState careProviderMembership = membershipReferenceStates.stream() + .filter(it -> (it.getNetworkId().equals(networkId) && it.getIdentity().getCordaIdentity().equals(careProvider))) + .collect(Collectors.toList()).get(0); + require.using("\nInsurance "+ commandName+" transaction should have careProvider's reference membership state", careProviderMembership!= null); + + //Exam the customized Identity + require.using("Insurance should be active member of Business Network with "+networkId, insuranceMembership.isActive()); + require.using("insurance should have business identity of FirmIdentity type", + insuranceMembership.getIdentity().getBusinessIdentity().getClass().equals(InsurerIdentity.class)); + + require.using("careProvider should be active member of Business Network with "+networkId, careProviderMembership.isActive()); + require.using("insurance should have business identity of FirmIdentity type", + careProviderMembership.getIdentity().getBusinessIdentity().getClass().equals(CareProviderIdentity.class)); + return null; + }); + } + + + public interface Commands extends CommandData{ + class Issue implements Commands {} + class Claim implements Commands {} + + } +} diff --git a/BusinessNetworks/insurancebusinessnetwork/contracts/src/main/java/net/corda/samples/businessmembership/states/CareProviderIdentity.java b/BusinessNetworks/insurancebusinessnetwork/contracts/src/main/java/net/corda/samples/businessmembership/states/CareProviderIdentity.java new file mode 100644 index 00000000..e141ae05 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/contracts/src/main/java/net/corda/samples/businessmembership/states/CareProviderIdentity.java @@ -0,0 +1,64 @@ +package net.corda.samples.businessmembership.states; + +import net.corda.bn.states.BNIdentity; +import net.corda.bn.states.BNPermission; +import net.corda.bn.states.BNRole; +import net.corda.core.serialization.CordaSerializable; + +import java.util.Collections; +import java.util.HashSet; +/** + * Custom Identity #2 + * Business identity specific for CareProvider Companies. Uses mimicking Swift Business Identifier Code (BIC). + * + * @property bic Business Identifier Code of the bank. + */ +@CordaSerializable +public class CareProviderIdentity implements BNIdentity { + + private String CareProviderIdentityCode; + private String cicRegex = "^[a-zA-Z]{6}[0-9a-zA-Z]{2}([0-9a-zA-Z]{3})?$"; + + public CareProviderIdentity(String careProviderIdentityCode) { + CareProviderIdentityCode = careProviderIdentityCode; + } + + public String getCareProviderIdentityCode() { + return CareProviderIdentityCode; + } + + public String getCicRegex() { + return cicRegex; + } + + public boolean isValid(){ + return this.CareProviderIdentityCode.matches(cicRegex); + } + + @CordaSerializable + public static class PsychiatryRole extends BNRole { + public PsychiatryRole() { + super("Psychiatrists", new HashSet( + Collections.singleton(PsychiatryPermissions.CAN_TREAT_MENTAL_HEALTH_ISSUE))); + } + } + @CordaSerializable + public enum PsychiatryPermissions implements BNPermission { + /** Enables Business Network member to issue [InsuranceClaim]s. **/ + CAN_TREAT_MENTAL_HEALTH_ISSUE + } + + @CordaSerializable + public static class OrthodonticsRole extends BNRole { + public OrthodonticsRole() { + super("Orthodontics", new HashSet( + Collections.singleton(OrthodonticsPermissions.CAN_TREAT_ORAL_ISSUE))); + } + } + @CordaSerializable + public enum OrthodonticsPermissions implements BNPermission { + /** Enables Business Network member to issue [InsuranceClaim]s. **/ + CAN_TREAT_ORAL_ISSUE + } + +} diff --git a/BusinessNetworks/insurancebusinessnetwork/contracts/src/main/java/net/corda/samples/businessmembership/states/InsuranceState.java b/BusinessNetworks/insurancebusinessnetwork/contracts/src/main/java/net/corda/samples/businessmembership/states/InsuranceState.java new file mode 100644 index 00000000..35c84271 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/contracts/src/main/java/net/corda/samples/businessmembership/states/InsuranceState.java @@ -0,0 +1,81 @@ +package net.corda.samples.businessmembership.states; + +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.ContractState; +import net.corda.core.contracts.LinearState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.core.serialization.ConstructorForDeserialization; +import net.corda.samples.businessmembership.contracts.InsuranceStateContract; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +@BelongsToContract(InsuranceStateContract.class) +public class InsuranceState implements LinearState { + private Party insurer; + private String insuree; + private Party careProvider; + private String networkId; + private String policyStatus; + private UniqueIdentifier linearId; + private List participants; + + @ConstructorForDeserialization + public InsuranceState(Party insurer, String insuree, Party careProvider, String networkId, String policyStatus, UniqueIdentifier linearId, List participants) { + this.insurer = insurer; + this.insuree = insuree; + this.careProvider = careProvider; + this.networkId = networkId; + this.policyStatus = policyStatus; + this.linearId = linearId; + this.participants = participants; + } + + public InsuranceState(Party insurer, String insuree, Party careProvider, String networkId, String policyStatus) { + this.insurer = insurer; + this.insuree = insuree; + this.careProvider = careProvider; + this.networkId = networkId; + this.policyStatus = policyStatus; + + this.linearId = new UniqueIdentifier(); + this.participants = new ArrayList(); + this.participants.add(insurer); + this.participants.add(careProvider); + } + + public Party getInsurer() { + return insurer; + } + + public String getInsuree() { + return insuree; + } + + public Party getCareProvider() { + return careProvider; + } + + public String getNetworkId() { + return networkId; + } + + public String getPolicyStatus() { + return policyStatus; + } + + @NotNull + @Override + public List getParticipants() { + return this.participants; + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return this.linearId; + } +} diff --git a/BusinessNetworks/insurancebusinessnetwork/contracts/src/main/java/net/corda/samples/businessmembership/states/InsurerIdentity.java b/BusinessNetworks/insurancebusinessnetwork/contracts/src/main/java/net/corda/samples/businessmembership/states/InsurerIdentity.java new file mode 100644 index 00000000..7ca26381 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/contracts/src/main/java/net/corda/samples/businessmembership/states/InsurerIdentity.java @@ -0,0 +1,64 @@ +package net.corda.samples.businessmembership.states; + +import net.corda.bn.states.BNIdentity; +import net.corda.bn.states.BNPermission; +import net.corda.bn.states.BNRole; +import net.corda.core.serialization.CordaSerializable; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; + +/** + * Custom Identity #1 + * Business identity specific for Insurer Companies. Uses mimicking Swift Business Identifier Code (BIC). + * + * @property bic Business Identifier Code of the bank. + */ +@CordaSerializable +public class InsurerIdentity implements BNIdentity { + + private String insuranceIdentityCode; + private String iicRegex = "^[a-zA-Z]{6}[0-9a-zA-Z]{2}([0-9a-zA-Z]{3})?$"; + + + public InsurerIdentity(String insuranceIdentityCode) { + this.insuranceIdentityCode = insuranceIdentityCode; + } + + public String getInsuranceIdentityCode() { + return insuranceIdentityCode; + } + + public String getIicRegex() { + return iicRegex; + } + + public boolean isValid(){ + return this.insuranceIdentityCode.matches(iicRegex); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof InsurerIdentity)) return false; + InsurerIdentity that = (InsurerIdentity) o; + return Objects.equals(getInsuranceIdentityCode(), that.getInsuranceIdentityCode()) && Objects.equals(getIicRegex(), that.getIicRegex()); + } + + @Override + public int hashCode() { + return Objects.hash(getInsuranceIdentityCode(), getIicRegex()); + } + + @CordaSerializable + public static class PolicyIssuerRole extends BNRole { + public PolicyIssuerRole() { + super("PolicyIssuer", new HashSet(Collections.singleton(IssuePermissions.CAN_ISSUE_POLICY))); + } + } + @CordaSerializable + public enum IssuePermissions implements BNPermission { + /** Enables Business Network member to issue [InsuranceClaim]s. **/ + CAN_ISSUE_POLICY + } +} diff --git a/BusinessNetworks/insurancebusinessnetwork/contracts/src/test/java/net/corda/samples/businessmembership/contracts/ContractTests.java b/BusinessNetworks/insurancebusinessnetwork/contracts/src/test/java/net/corda/samples/businessmembership/contracts/ContractTests.java new file mode 100644 index 00000000..9b9a9a27 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/contracts/src/test/java/net/corda/samples/businessmembership/contracts/ContractTests.java @@ -0,0 +1,32 @@ +package net.corda.samples.businessmembership.contracts; + +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; +import net.corda.samples.businessmembership.states.InsuranceState; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.node.MockServices; +import org.junit.Test; + +import java.util.Arrays; + +import static net.corda.testing.node.NodeTestUtils.ledger; + +public class ContractTests { + private final MockServices ledgerServices = new MockServices( + Arrays.asList("net.corda.samples.businessmembership.contracts")); + TestIdentity alice = new TestIdentity(new CordaX500Name("Alice", "TestLand", "US")); + TestIdentity bob = new TestIdentity(new CordaX500Name("Bob", "TestLand", "US")); + + @Test + public void failsDueToParticipantsAreNotNetworkMembers() { + InsuranceState insurancestate = new InsuranceState(alice.getParty(), "TEST", bob.getParty(), new UniqueIdentifier().toString(), "Initiating Policy"); + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.output(InsuranceStateContract.InsuranceStateContract_ID, insurancestate); + tx.command(alice.getPublicKey(), new InsuranceStateContract.Commands.Issue()); + return tx.fails(); + }); + return null; + }); + } +} diff --git a/BusinessNetworks/insurancebusinessnetwork/contracts/src/test/java/net/corda/samples/businessmembership/contracts/StateTests.java b/BusinessNetworks/insurancebusinessnetwork/contracts/src/test/java/net/corda/samples/businessmembership/contracts/StateTests.java new file mode 100644 index 00000000..83ebd9f0 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/contracts/src/test/java/net/corda/samples/businessmembership/contracts/StateTests.java @@ -0,0 +1,16 @@ +package net.corda.samples.businessmembership.contracts; + +import net.corda.core.identity.Party; +import net.corda.samples.businessmembership.states.InsuranceState; +import org.junit.Test; + +public class StateTests { + + @Test + public void hasFieldOfCorrectType() throws NoSuchFieldException { + // Does the message field exist? + InsuranceState.class.getDeclaredField("insurer"); + // Is the message field of the correct type? + assert(InsuranceState.class.getDeclaredField("insurer").getType().equals(Party.class)); + } +} \ No newline at end of file diff --git a/BusinessNetworks/insurancebusinessnetwork/gradle.properties b/BusinessNetworks/insurancebusinessnetwork/gradle.properties new file mode 100644 index 00000000..b4861f86 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/gradle.properties @@ -0,0 +1,3 @@ +name=Membership Attestation Cordapp +group=net.corda.samples +version=0.1 \ No newline at end of file diff --git a/BusinessNetworks/insurancebusinessnetwork/gradle/wrapper/gradle-wrapper.jar b/BusinessNetworks/insurancebusinessnetwork/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..99340b4a Binary files /dev/null and b/BusinessNetworks/insurancebusinessnetwork/gradle/wrapper/gradle-wrapper.jar differ diff --git a/BusinessNetworks/insurancebusinessnetwork/gradle/wrapper/gradle-wrapper.properties b/BusinessNetworks/insurancebusinessnetwork/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..0188225a --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +#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-5.6.4-all.zip + diff --git a/BusinessNetworks/insurancebusinessnetwork/gradlew b/BusinessNetworks/insurancebusinessnetwork/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/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/BusinessNetworks/insurancebusinessnetwork/gradlew.bat b/BusinessNetworks/insurancebusinessnetwork/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/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/BusinessNetworks/insurancebusinessnetwork/repositories.gradle b/BusinessNetworks/insurancebusinessnetwork/repositories.gradle new file mode 100644 index 00000000..8be7b630 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/repositories.gradle @@ -0,0 +1,8 @@ +repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://jitpack.io' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } +} diff --git a/BusinessNetworks/insurancebusinessnetwork/settings.gradle b/BusinessNetworks/insurancebusinessnetwork/settings.gradle new file mode 100644 index 00000000..0473ad43 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/settings.gradle @@ -0,0 +1,2 @@ +include 'workflows' +include 'contracts' diff --git a/BusinessNetworks/insurancebusinessnetwork/workflows/build.gradle b/BusinessNetworks/insurancebusinessnetwork/workflows/build.gradle new file mode 100644 index 00000000..83d8c1ed --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/workflows/build.gradle @@ -0,0 +1,66 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + workflow { + name "Membership Attestation Flows" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/dev") + } + } + test { + java { + srcDir 'src/test/java' + java.outputDir = file('bin/main') + } + 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_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":contracts") + cordapp("net.corda.bn:business-networks-contracts:$corda_bn_extension_version") + cordapp("net.corda.bn:business-networks-workflows:$corda_bn_extension_version") +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} \ No newline at end of file diff --git a/BusinessNetworks/insurancebusinessnetwork/workflows/src/integrationTest/java/net/corda/samples/businessmembership/DriverBasedTest.java b/BusinessNetworks/insurancebusinessnetwork/workflows/src/integrationTest/java/net/corda/samples/businessmembership/DriverBasedTest.java new file mode 100644 index 00000000..30f0c3fc --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/workflows/src/integrationTest/java/net/corda/samples/businessmembership/DriverBasedTest.java @@ -0,0 +1,48 @@ +package net.corda.samples.businessmembership; + +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/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/IssuePolicy.java b/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/IssuePolicy.java new file mode 100644 index 00000000..528af131 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/IssuePolicy.java @@ -0,0 +1,191 @@ +package net.corda.samples.businessmembership.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.bn.flows.BNService; +import net.corda.bn.flows.IllegalMembershipStatusException; +import net.corda.bn.flows.MembershipAuthorisationException; +import net.corda.bn.flows.MembershipNotFoundException; +import net.corda.bn.states.BNRole; +import net.corda.bn.states.MembershipState; +import net.corda.core.contracts.*; +import net.corda.core.crypto.SecureHash; +import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.core.utilities.ProgressTracker; +import net.corda.samples.businessmembership.contracts.InsuranceStateContract; +import net.corda.samples.businessmembership.states.CareProviderIdentity; +import net.corda.samples.businessmembership.states.InsuranceState; +import net.corda.samples.businessmembership.states.InsurerIdentity; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Objects; +import java.util.Set; + +import static net.corda.core.contracts.ContractsDSL.requireThat; + +public class IssuePolicy { + + @InitiatingFlow + @StartableByRPC + public static class IssuePolicyInitiator extends FlowLogic { + + private String networkId; + private Party careProvider; + private String insuree; + + public IssuePolicyInitiator(String networkId, Party careProvider, String insuree) { + this.networkId = networkId; + this.careProvider = careProvider; + this.insuree = insuree; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + businessNetworkFullVerification(this.networkId, getOurIdentity(), this.careProvider); + InsuranceState outputState = new InsuranceState(getOurIdentity(), this.insuree, this.careProvider, networkId, "Initiating Policy"); + BNService bnService = getServiceHub().cordaService(BNService.class); + TransactionBuilder txBuilder = new TransactionBuilder(notary) + .addOutputState(outputState) + .addCommand(new InsuranceStateContract.Commands.Issue(), Arrays.asList(getOurIdentity().getOwningKey(), careProvider.getOwningKey())) + .addReferenceState(new ReferencedStateAndRef<>(Objects.requireNonNull(bnService.getMembership(networkId, getOurIdentity())))) + .addReferenceState(new ReferencedStateAndRef<>(Objects.requireNonNull(bnService.getMembership(networkId, careProvider)) + )); + txBuilder.verify(getServiceHub()); + + SignedTransaction ptx = getServiceHub().signInitialTransaction(txBuilder); + FlowSession session = initiateFlow(careProvider); + SignedTransaction ftx = subFlow(new CollectSignaturesFlow(ptx, Arrays.asList(session))); + return subFlow(new FinalityFlow(ftx, Arrays.asList(session))); + } + + + /** + * Verifies that [lender] and [borrower] are members of Business Network with [networkId] ID, their memberships are active, contain + * business identity of [BankIdentity] type and that lender is authorised to issue the loan. + */ + @Suspendable + protected void businessNetworkFullVerification(String networkId, Party policyIssuer, Party careProvider) throws MembershipNotFoundException { + BNService bnService = getServiceHub().cordaService(BNService.class); + try{ + MembershipState policyIssuerMembership = bnService.getMembership(networkId,policyIssuer).getState().getData(); + if (!policyIssuerMembership.isActive()){ + throw new IllegalMembershipStatusException("$policyIssuer is not active member of Business Network with $networkId ID"); + } + if(policyIssuerMembership.getIdentity().getBusinessIdentity().getClass()!= InsurerIdentity.class){ + throw new IllegalMembershipBusinessIdentityException("$policyIssuer business identity should be InsurerIdentity"); + } + Set setRoles = policyIssuerMembership.getRoles(); + for (BNRole role : setRoles){ + if(!role.getPermissions().contains(InsurerIdentity.IssuePermissions.CAN_ISSUE_POLICY)){ + throw new MembershipAuthorisationException("$policyIssuer is not authorised to issue insurance Polict in Business Network with $networkId ID"); + } + } + } catch (Exception e){ + throw new MembershipNotFoundException("$policyIssuer is not member of Business Network with $networkId ID"); + } + try{ + MembershipState careProviderMembership = bnService.getMembership(networkId,careProvider).getState().getData(); + if (!careProviderMembership.isActive()){ + throw new IllegalMembershipStatusException("$policyIssuer is not active member of Business Network with $networkId ID"); + } + if(careProviderMembership.getIdentity().getBusinessIdentity().getClass()!= CareProviderIdentity.class){ + throw new IllegalMembershipBusinessIdentityException("$policyIssuer business identity should be InsurerIdentity"); + } + } catch (Exception e){ + throw new MembershipNotFoundException("$policyIssuer is not member of Business Network with $networkId ID"); + } + } + + @Suspendable + private Memberships businessNetworkPartialVerification(String networkId, Party insurer, Party careProvider) throws MembershipNotFoundException { + BNService bnService = getServiceHub().cordaService(BNService.class); + StateAndRef insurerMembership = null; + try { + insurerMembership = bnService.getMembership(networkId,insurer); + }catch(Exception e){ + throw new MembershipNotFoundException("insurer is not part of Business Network with $networkId ID"); + } + StateAndRef careProMembership = null; + try { + careProMembership = bnService.getMembership(networkId,careProvider); + }catch(Exception e){ + throw new MembershipNotFoundException("careProvider is not part of Business Network with $networkId ID"); + } + + return new Memberships(insurerMembership,careProMembership); + } + + } + + @InitiatedBy(IssuePolicyInitiator.class) + public static class Acceptor extends FlowLogic { + + private final FlowSession otherPartySession; + + public Acceptor(FlowSession otherPartySession) { + this.otherPartySession = otherPartySession; + } + + @Suspendable + @Override + public SignedTransaction call() throws FlowException { + class SignTxFlow extends SignTransactionFlow { + private SignTxFlow(FlowSession otherPartyFlow, ProgressTracker progressTracker) { + super(otherPartyFlow, progressTracker); + } + + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + Command command = stx.getTx().getCommands().get(0); + if (!(command.getValue() instanceof InsuranceStateContract.Commands.Issue)){ + throw new FlowException("Only LoanContract.Commands.Issue command is allowed"); + } + + InsuranceState insuranceState = (InsuranceState) stx.getTx().getOutputStates().get(0); + if (!(insuranceState.getInsurer().equals(otherPartySession.getCounterparty()))){ + throw new FlowException("insurer doesn't match sender's identity"); + } + if(!(insuranceState.getCareProvider().equals(getOurIdentity()))){ + throw new FlowException("careProvider doesn't match receiver's identity"); + } + } + } + final SignTxFlow signTxFlow = new SignTxFlow(otherPartySession, SignTransactionFlow.Companion.tracker()); + final SecureHash txId = subFlow(signTxFlow).getId(); + return subFlow(new ReceiveFinalityFlow(otherPartySession, txId)); + } + } + + static class Memberships{ + private StateAndRef MembershipA; + private StateAndRef MembershipB; + + public Memberships(StateAndRef membershipA, StateAndRef membershipB) { + MembershipA = membershipA; + MembershipB = membershipB; + } + + public StateAndRef getMembershipA() { + return MembershipA; + } + + public StateAndRef getMembershipB() { + return MembershipB; + } + } + + static class IllegalMembershipBusinessIdentityException extends FlowException{ + public IllegalMembershipBusinessIdentityException(@Nullable String message) { + super(message); + } + } + +} diff --git a/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/ActiveMembers.java b/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/ActiveMembers.java new file mode 100644 index 00000000..ad0384a5 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/ActiveMembers.java @@ -0,0 +1,30 @@ +package net.corda.samples.businessmembership.flows.membershipFlows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.bn.flows.ActivateMembershipFlow; +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.CordaX500Name; +import net.corda.core.identity.Party; + +@StartableByRPC +public class ActiveMembers extends FlowLogic { + + private UniqueIdentifier membershipId; + + public ActiveMembers(UniqueIdentifier membershipId) { + this.membershipId = membershipId; + } + + @Override + @Suspendable + public String call() throws FlowException { + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + subFlow(new ActivateMembershipFlow(this.membershipId,notary)); + return "\nMember("+ this.membershipId.toString()+")'s network membership has been activated."; + } +} diff --git a/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/AssignBNIdentity.java b/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/AssignBNIdentity.java new file mode 100644 index 00000000..7f1767a6 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/AssignBNIdentity.java @@ -0,0 +1,48 @@ +package net.corda.samples.businessmembership.flows.membershipFlows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.bn.flows.ModifyBusinessIdentityFlow; +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.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.samples.businessmembership.states.CareProviderIdentity; +import net.corda.samples.businessmembership.states.InsurerIdentity; + +@StartableByRPC +public class AssignBNIdentity extends FlowLogic { + + private String firmType; + private UniqueIdentifier membershipId; + private String bnIdentity; + + public AssignBNIdentity(String firmType, UniqueIdentifier membershipId, String bnIdentity) { + this.firmType = firmType; + this.membershipId = membershipId; + this.bnIdentity = bnIdentity; + } + + @Override + @Suspendable + public String call() throws FlowException { + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + if(this.firmType.equals("InsuranceFirm")){ + InsurerIdentity insuranceIdty = new InsurerIdentity(bnIdentity); + if(!insuranceIdty.isValid()){ + throw new IllegalArgumentException(""+bnIdentity+" in not a valid Insurance Identity"); + } + subFlow(new ModifyBusinessIdentityFlow(membershipId, insuranceIdty, notary)); + }else{ + CareProviderIdentity careProviderIdty = new CareProviderIdentity(bnIdentity); + if(!careProviderIdty.isValid()){ + throw new IllegalArgumentException(""+bnIdentity+" in not a valid Insurance Identity"); + } + subFlow(new ModifyBusinessIdentityFlow(membershipId, careProviderIdty, notary)); + } + return "Issue a "+ this.firmType+" BN Identity to member("+ this.membershipId+")"; + } +} diff --git a/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/AssignOrthodonticsRole.java b/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/AssignOrthodonticsRole.java new file mode 100644 index 00000000..f00d5d55 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/AssignOrthodonticsRole.java @@ -0,0 +1,47 @@ +package net.corda.samples.businessmembership.flows.membershipFlows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.bn.flows.BNService; +import net.corda.bn.flows.ModifyRolesFlow; +import net.corda.bn.states.BNRole; +import net.corda.bn.states.MembershipState; +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.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.samples.businessmembership.states.CareProviderIdentity; + +import java.util.HashSet; +import java.util.Set; + +@StartableByRPC +public class AssignOrthodonticsRole extends FlowLogic { + + private UniqueIdentifier membershipId; + private String networkId; + + public AssignOrthodonticsRole(UniqueIdentifier membershipId, String networkId) { + this.membershipId = membershipId; + this.networkId = networkId; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + BNService bnService = getServiceHub().cordaService(BNService.class); + MembershipState membershipState = bnService.getMembership(membershipId).getState().getData(); + Set roles = new HashSet<>(); + for(BNRole br : membershipState.getRoles()){ + roles.add(br); + } + roles.add(new CareProviderIdentity.OrthodonticsRole()); + return subFlow(new ModifyRolesFlow(this.membershipId,roles,notary)); + } +} diff --git a/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/AssignPolicyIssuerRole.java b/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/AssignPolicyIssuerRole.java new file mode 100644 index 00000000..602178e0 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/AssignPolicyIssuerRole.java @@ -0,0 +1,46 @@ +package net.corda.samples.businessmembership.flows.membershipFlows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.bn.flows.BNService; +import net.corda.bn.flows.ModifyRolesFlow; +import net.corda.bn.states.BNRole; +import net.corda.bn.states.MembershipState; +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.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.samples.businessmembership.states.InsurerIdentity; + +import java.util.HashSet; +import java.util.Set; + +@StartableByRPC +public class AssignPolicyIssuerRole extends FlowLogic { + + private UniqueIdentifier membershipId; + private String networkId; + + public AssignPolicyIssuerRole(UniqueIdentifier membershipId, String networkId) { + this.membershipId = membershipId; + this.networkId = networkId; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + BNService bnService = getServiceHub().cordaService(BNService.class); + MembershipState membershipState = bnService.getMembership(this.membershipId).getState().getData(); + Set roles = new HashSet<>(); + for(BNRole br : membershipState.getRoles()){ + roles.add(br); + } + roles.add(new InsurerIdentity.PolicyIssuerRole()); + return subFlow(new ModifyRolesFlow(membershipId, roles, notary)); + } +} diff --git a/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/CreateNetwork.java b/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/CreateNetwork.java new file mode 100644 index 00000000..83dde76f --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/CreateNetwork.java @@ -0,0 +1,22 @@ +package net.corda.samples.businessmembership.flows.membershipFlows; + +import co.paralleluniverse.fibers.Suspendable; +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.transactions.SignedTransaction; +import net.corda.bn.flows.CreateBusinessNetworkFlow; + + +@StartableByRPC +public class CreateNetwork extends FlowLogic { + + @Override + @Suspendable + public String call() throws FlowException { + UniqueIdentifier networkId = new UniqueIdentifier(); + subFlow(new CreateBusinessNetworkFlow(networkId,null, new UniqueIdentifier(),null,null)); + return "\nA network was created with NetworkID: "+ networkId.toString(); + } +} diff --git a/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/CreateNetworkSubGroup.java b/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/CreateNetworkSubGroup.java new file mode 100644 index 00000000..72bebce8 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/CreateNetworkSubGroup.java @@ -0,0 +1,42 @@ +package net.corda.samples.businessmembership.flows.membershipFlows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.bn.flows.CreateGroupFlow; +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.CordaX500Name; +import net.corda.core.identity.Party; + +import java.util.Set; + +@StartableByRPC +public class CreateNetworkSubGroup extends FlowLogic { + + private String networkId; + private String groupName; + private Set groupParticipants; + + public CreateNetworkSubGroup(String networkId, String groupName, Set groupParticipants) { + this.networkId = networkId; + this.groupName = groupName; + this.groupParticipants = groupParticipants; + } + + @Override + @Suspendable + public String call() throws FlowException { + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + UniqueIdentifier groupId = new UniqueIdentifier(); + subFlow(new CreateGroupFlow(this.networkId,groupId,this.groupName,this.groupParticipants,notary)); + String result = "\n "+ this.groupName+ " has created under BN network ("+this.networkId+")"+ + "GroupId: "+groupId.toString(); + for (UniqueIdentifier id: groupParticipants){ + result = result + "\nAdded participants(shown by membershipId): "+ id.toString(); + } + return result; + } +} diff --git a/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/QueryAllMembers.java b/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/QueryAllMembers.java new file mode 100644 index 00000000..551b107c --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/QueryAllMembers.java @@ -0,0 +1,29 @@ +package net.corda.samples.businessmembership.flows.membershipFlows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.bn.states.MembershipState; +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 java.util.List; + +@StartableByRPC +public class QueryAllMembers extends FlowLogic { + + @Override + @Suspendable + public String call() throws FlowException { + + List> membershipRequests = getServiceHub().getVaultService().queryBy(MembershipState.class).getStates(); + String result = "\nQuery Found the following memberships:"; + for (StateAndRef request : membershipRequests){ + result = result + "\n- [" + + request.getState().getData().getIdentity().getCordaIdentity().getName().getOrganisation()+ + "] with membershipId: " + request.getState().getData().getLinearId() + + " | Membership Status: " + request.getState().getData().getStatus(); + } + return result; + } +} diff --git a/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/RequestMembership.java b/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/RequestMembership.java new file mode 100644 index 00000000..9ee86b7c --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/workflows/src/main/java/net/corda/samples/businessmembership/flows/membershipFlows/RequestMembership.java @@ -0,0 +1,27 @@ +package net.corda.samples.businessmembership.flows.membershipFlows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.bn.flows.RequestMembershipFlow; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.identity.Party; + +@StartableByRPC +public class RequestMembership extends FlowLogic{ + + private Party authorisedParty; + private String networkId; + + public RequestMembership(Party authorisedParty, String networkId) { + this.authorisedParty = authorisedParty; + this.networkId = networkId; + } + + @Override + @Suspendable + public String call() throws FlowException { + subFlow(new RequestMembershipFlow(this.authorisedParty,this.networkId,null,null)); + return "\nRequest membership sent from "+ this.getOurIdentity().getName().getOrganisation()+" nodes (ourself) to an authorized network member "+this.authorisedParty.getName().getOrganisation(); + } +} diff --git a/BusinessNetworks/insurancebusinessnetwork/workflows/src/test/java/net/corda/samples/businessmembership/FlowTests.java b/BusinessNetworks/insurancebusinessnetwork/workflows/src/test/java/net/corda/samples/businessmembership/FlowTests.java new file mode 100644 index 00000000..5d404f93 --- /dev/null +++ b/BusinessNetworks/insurancebusinessnetwork/workflows/src/test/java/net/corda/samples/businessmembership/FlowTests.java @@ -0,0 +1,63 @@ +package net.corda.samples.businessmembership; + +import com.google.common.collect.ImmutableList; +import net.corda.bn.states.MembershipState; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.samples.businessmembership.flows.membershipFlows.CreateNetwork; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +public class FlowTests { + private MockNetwork network; + private StartedMockNode a; + private StartedMockNode b; + + @Before + public void setup() { + network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.businessmembership.contracts"), + TestCordapp.findCordapp("net.corda.samples.businessmembership.flows"), + TestCordapp.findCordapp("net.corda.bn.flows"), + TestCordapp.findCordapp("net.corda.bn.states"))) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); + a = network.createPartyNode(null); + b = network.createPartyNode(null); + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void createNetworkTest() throws ExecutionException, InterruptedException { + CreateNetwork flow = new CreateNetwork(); + Future future = a.startFlow(flow); + network.runNetwork(); + String resString = future.get(); + System.out.println(resString); + + int subString = resString.indexOf("NetworkID: "); + String networkId = resString.substring(subString+11); + System.out.println("-"+ networkId+"-"); + + QueryCriteria inputCriteria = new QueryCriteria.LinearStateQueryCriteria().withStatus(Vault.StateStatus.UNCONSUMED); + MembershipState storedMembershipState = a.getServices().getVaultService() + .queryBy(MembershipState.class,inputCriteria).getStates().get(0).getState().getData(); + System.out.println(storedMembershipState.getNetworkId()); + + assert (storedMembershipState.getNetworkId().equals(networkId)); + + } +} diff --git a/BusinessNetworks/readme.md b/BusinessNetworks/readme.md new file mode 100644 index 00000000..0b0ac0dd --- /dev/null +++ b/BusinessNetworks/readme.md @@ -0,0 +1,6 @@ +## Business Network Extension Cordapp Sample + +This folder features sample projects that utilize bn-extension. + +### [Insurance Business network](./insurancebusinessnetwork): +This CorDapp provides a simple example of how a mock business network is set up with bn-extension. It consists of one sub group and two custom network identities, and multiple roles associated with each identity. diff --git a/Features/README.md b/Features/README.md index f1d94608..b23fc696 100644 --- a/Features/README.md +++ b/Features/README.md @@ -1,51 +1,51 @@ -## Feature Specific Cordapps +## Feature Specific CorDapps This folder features several sample projects, each of them demonstrates different specific features of corda. ### [Blacklist -- Attachment](./attachment-blacklist): -This CorDapp allows nodes to reach agreement over arbitrary strings of text, but only with parties that are not included in the blacklist uploaded to the nodes as an [attachment](https://training.corda.net/corda-details/attachments/). +This CorDapp allows nodes to reach agreement over arbitrary strings of text, but only with parties that are not included in the blacklist uploaded to the nodes as an [attachment](https://docs.r3.com/en/platform/corda/4.9/community/cordapp-build-systems.html#cordapp-contract-attachments). ### [Sendfile -- Attachment](./attachment-sendfile): -This Cordapp shows how to upload and download an [attachment](https://training.corda.net/corda-details/attachments/) via a flow. +This CorDapp shows how to upload and download an [attachment](https://docs.r3.com/en/platform/corda/4.9/community/cordapp-build-systems.html#cordapp-contract-attachments) via a flow.

Corda

### [Whistleblower -- Confidential Identity ](./confidentialidentity-whistleblower): -This CorDapp is a simple showcase of [confidential identities](https://docs.corda.net/docs/corda-os/api-identity.html#confidential-identities) (i.e. anonymous public keys). +This CorDapp is a simple showcase of [confidential identities](https://docs.r3.com/en/platform/corda/4.9/community/api-identity.html#confidential-identities) (i.e. anonymous public keys). ### [Autopayroll -- CordaService](./cordaservice-autopayroll): -This Cordapp shows how to trigger a flow with vault update(completion of prior flows) using [CordaService](https://training.corda.net/corda-details/automation/#services) & [trackby](https://training.corda.net/corda-details/automation-solution/#track-and-notify). +This CorDapp shows how to trigger a flow with vault update(completion of prior flows) using [CordaService](https://training.corda.net/corda-details/automation/#services).

- Corda + Corda

### [Trade Reporting -- ObservableStates](./observablestates-tradereporting): -This CorDapp shows how Corda's [observable states](https://docs.corda.net/docs/corda-os/4.4/tutorial-observer-nodes.html#observer-nodes) feature works. Observable states is the ability for nodes who are not participants in a transaction to still store them if the transactions are sent to them. +This CorDapp shows how Corda's observable states feature works. Observable states is the ability for nodes who are not participants in a transaction to still store them if the transactions are sent to them. ### [Prime Number -- Oracle](./oracle-primenumber): -This CorDapp implements an [oracle service](https://training.corda.net/corda-details/oracles) that allows nodes to: +This CorDapp implements an [oracle service](https://docs.r3.com/en/platform/corda/4.9/community/key-concepts-oracles.html) that allows nodes to: * Request the Nth prime number * Request the oracle's signature to prove that the number included in their transaction is actually the Nth prime number ### [Car Insurance -- QueryableState](./queryablestate-carinsurance): -This CorDapp demonstrates [QueryableState](https://docs.corda.net/docs/corda-os/api-persistence.html) works in Corda. Corda allows developers to have the ability to expose some or all parts of their states to a custom database table using an ORM tools. To support this feature the state must implement `QueryableState`. +This CorDapp demonstrates [QueryableState](https://docs.r3.com/en/platform/corda/4.9/enterprise/cordapps/api-states.html#the-queryablestate-and-schedulablestate-interfaces) works in Corda. Corda allows developers to have the ability to expose some or all parts of their states to a custom database table using an ORM tools. To support this feature the state must implement `QueryableState`. ### [Sanctionsbody -- ReferenceStates](./referencestates-sanctionsbody): -This CorDapp demonstrates the use of [reference states](https://training.corda.net/corda-details/reference-states/) in a transaction and in the verification method of a contract. +This CorDapp demonstrates the use of [reference states](https://docs.r3.com/en/platform/corda/4.9/enterprise/cordapps/api-states.html#reference-states) in a transaction and in the verification method of a contract. This CorDapp allows two nodes to enter into an IOU agreement, but enforces that both parties belong to a list of sanctioned entities. This list of sanctioned entities is taken from a referenced SanctionedEntities state. ### [Heartbeat -- SchedulableState](./schedulablestate-heartbeat): -This CorDapp is a simple showcase of [scheduled activities](https://docs.corda.net/docs/corda-os/event-scheduling.html#how-to-implement-scheduled-events) (i.e. activities started by a node at a specific time without direct input from the node owner). +This CorDapp is a simple showcase of [scheduled activities](https://docs.r3.com/en/platform/corda/4.9/enterprise/cordapps/api-states.html#the-queryablestate-and-schedulablestate-interfaces) (i.e. activities started by a node at a specific time without direct input from the node owner).

Corda

-### [CustomLogging -- YoCordapp](./customlogging-yocordapp): -This cordapp has some examples on how to setup custom logging with corda for either json logging and other tooling. +### [CustomLogging -- YoCorDapp](./customlogging-yocordapp): +This CorDapp has some examples on how to set up custom logging with corda for either json logging and other tooling. diff --git a/Features/attachment-blacklist/README.md b/Features/attachment-blacklist/README.md index 44bae370..027f0899 100644 --- a/Features/attachment-blacklist/README.md +++ b/Features/attachment-blacklist/README.md @@ -23,14 +23,14 @@ check that the parties to the `AgreementState` are not blacklisted. There aren't ## Pre-Requisites -For development environment setup, please refer to: [Setup Guide](https://docs.corda.net/getting-set-up.html). +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.10/community/getting-set-up.html). ### Running the CorDapp Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) ``` -./gradlew clean deployNodes +./gradlew clean build deployNodes ``` Then type: (to run the nodes) ``` @@ -43,7 +43,7 @@ Then type: (to run the nodes) Note: The nodes must be running before attempting this step Before attempting to reach any agreements, you must upload the blacklist as an attachment to each node that you want to -be able to *initiate* an agreement. The blacklist can be uploaded via [RPC](https://docs.corda.net/docs/corda-os/api-rpc.html#api-rpc-operations) by running the following command from the +be able to *initiate* an agreement. The blacklist can be uploaded via [RPC](https://docs.r3.com/en/platform/corda/4.9/community/api-rpc.html) by running the following command from the project's root folder: Java version diff --git a/Features/attachment-blacklist/build.gradle b/Features/attachment-blacklist/build.gradle index e22c3ff6..10b2b6f3 100644 --- a/Features/attachment-blacklist/build.gradle +++ b/Features/attachment-blacklist/build.gradle @@ -21,8 +21,8 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -39,10 +39,10 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } @@ -92,6 +92,8 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" + } task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { diff --git a/Features/attachment-blacklist/repositories.gradle b/Features/attachment-blacklist/repositories.gradle index 206b9c68..8be7b630 100644 --- a/Features/attachment-blacklist/repositories.gradle +++ b/Features/attachment-blacklist/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Features/attachment-blacklist/workflows/src/main/java/net/corda/samples/blacklist/ProposeFlow.java b/Features/attachment-blacklist/workflows/src/main/java/net/corda/samples/blacklist/ProposeFlow.java index 84192173..0f9ded89 100644 --- a/Features/attachment-blacklist/workflows/src/main/java/net/corda/samples/blacklist/ProposeFlow.java +++ b/Features/attachment-blacklist/workflows/src/main/java/net/corda/samples/blacklist/ProposeFlow.java @@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableSet; import net.corda.core.crypto.SecureHash; import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; @@ -42,13 +43,8 @@ Party getFirstNotary() throws FlowException { public SignedTransaction call() throws FlowException { // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - Party notary = getFirstNotary(); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); AgreementState agreementState = new AgreementState(getOurIdentity(), counterparty, agreementTxt); AgreementContract.Commands.Agree agreeCmd = new AgreementContract.Commands.Agree(); diff --git a/Features/attachment-blacklist/workflows/src/test/java/net/corda/samples/blacklist/FlowTests.java b/Features/attachment-blacklist/workflows/src/test/java/net/corda/samples/blacklist/FlowTests.java index 74ecb8fa..66ee8654 100644 --- a/Features/attachment-blacklist/workflows/src/test/java/net/corda/samples/blacklist/FlowTests.java +++ b/Features/attachment-blacklist/workflows/src/test/java/net/corda/samples/blacklist/FlowTests.java @@ -6,13 +6,11 @@ import net.corda.core.contracts.StateAndRef; import net.corda.core.contracts.TransactionVerificationException; import net.corda.core.crypto.SecureHash; +import net.corda.core.identity.CordaX500Name; import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; import net.corda.samples.blacklist.states.AgreementState; -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 net.corda.testing.node.*; import org.hamcrest.core.IsInstanceOf; import org.junit.After; import org.junit.Before; @@ -43,7 +41,9 @@ public class FlowTests { @Before public void setup() throws FileNotFoundException { this.network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( - TestCordapp.findCordapp("net.corda.samples.blacklist.contracts")))); + TestCordapp.findCordapp("net.corda.samples.blacklist.contracts"))) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); this.a = this.network.createNode(); this.b = this.network.createNode(); this.aIdentity = this.a.getInfo().getLegalIdentities().get(0); diff --git a/Features/attachment-sendfile/README.md b/Features/attachment-sendfile/README.md index 78d727eb..78db4ca4 100644 --- a/Features/attachment-sendfile/README.md +++ b/Features/attachment-sendfile/README.md @@ -1,12 +1,12 @@ # Sendfile -- Attachment -This Cordapp shows how to upload and download an [attachment](https://training.corda.net/corda-details/attachments/) via flow. +This CorDapp shows how to upload and download an [attachment](https://docs.r3.com/en/platform/corda/4.10/community/cordapp-build-systems.html#cordapp-contract-attachments) via flow. ## Concepts -In this Cordapp, there are two parties: +In this CorDapp, there are two parties: * Seller: sends an invoice (with attachment) to Buyer -* Buyer: receive the the invoice and be able to download the attached zip file to their local machine +* Buyer: receive the invoice and be able to download the attached zip file to their local machine ### States @@ -28,10 +28,10 @@ The flow logic is the following: * `sendAttachment`: send and sync the attachment between parties 1. Uploading attachment from local - 2. Attaching the accachmentID to the transaction - 3. Storing the attached file into attachment service at the counterparty's node (Automatically check if it already exists or not. If it does, do nothing; if not, download the attached file from the conterparty.) + 2. Attaching the attachmentID to the transaction + 3. Storing the attached file into attachment service at the counterparty's node (Automatically check if it already exists or not. If it does, do nothing; if not, download the attached file from the counterparty.) -* `downloadAttchment`: save the attachment file from node's serviceHub to local +* `downloadAttachment`: save the attachment file from node's serviceHub to local 1. signing the attachment service in the node to download the file via attachmentID ![alt text](./graph.png) @@ -43,7 +43,7 @@ The flow logic is the following: Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) ``` -./gradlew clean deployNodes +./gradlew clean build deployNodes ``` Then type: (to run the nodes) ``` diff --git a/Features/attachment-sendfile/build.gradle b/Features/attachment-sendfile/build.gradle index 297761b9..0997dcb1 100644 --- a/Features/attachment-sendfile/build.gradle +++ b/Features/attachment-sendfile/build.gradle @@ -4,28 +4,32 @@ buildscript { ext { corda_release_group = constants.getProperty("cordaReleaseGroup") - corda_release_version = constants.getProperty("cordaVersion") corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") + corda_release_version = constants.getProperty("cordaVersion") corda_core_release_version = constants.getProperty("cordaCoreVersion") 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() + spring_boot_version = '2.0.2.RELEASE' + ext.spring_boot_gradle_plugin_version = '2.0.2.RELEASE' } repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda' } + + maven { url 'https://download.corda.net/maven/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" } } @@ -35,10 +39,19 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } maven { url 'https://jitpack.io' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } + } + + configurations { + compile { + // We want to use SLF4J's version of these bindings: jcl-over-slf4j + // Remove any transitive dependency on Apache's version. + exclude group: 'commons-logging', module: 'commons-logging' + } } tasks.withType(JavaCompile) { @@ -49,11 +62,13 @@ allprojects { // This makes the JAR's SHA-256 hash repeatable. preserveFileTimestamps = false reproducibleFileOrder = true + duplicatesStrategy = DuplicatesStrategy.EXCLUDE } } apply plugin: 'net.corda.plugins.cordapp' apply plugin: 'net.corda.plugins.quasar-utils' +apply plugin: 'net.corda.plugins.cordformation' sourceSets { main { @@ -63,42 +78,24 @@ sourceSets { } } +//Module dependencis dependencies { // Corda dependencies. cordaCompile "$corda_core_release_group:corda-core:$corda_core_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") + 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" + cordaDriver "net.corda:corda-shell:4.10" - cordapp project('contracts') - cordapp project('workflows') -} - -task installQuasar(type: Copy) { - destinationDir rootProject.file("lib") - from(configurations.quasar) { - rename 'quasar-core(.*).jar', 'quasar.jar' - } -} - -cordapp { - info { - name "Attachment Sendfile" - vendor "Corda Open Source" - targetPlatformVersion corda_platform_version - minimumPlatformVersion corda_platform_version - } } -apply plugin: 'net.corda.plugins.cordapp' -apply plugin: 'net.corda.plugins.quasar-utils' -apply plugin: 'net.corda.plugins.cordformation' -apply plugin: 'java' - - task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { nodeDefaults { diff --git a/Features/attachment-sendfile/contracts/src/test/java/net.corda.samples.sendfile/contracts/InvoiceContractTests.java b/Features/attachment-sendfile/contracts/src/test/java/net.corda.samples.sendfile/contracts/InvoiceContractTests.java index bc461317..c7f05118 100644 --- a/Features/attachment-sendfile/contracts/src/test/java/net.corda.samples.sendfile/contracts/InvoiceContractTests.java +++ b/Features/attachment-sendfile/contracts/src/test/java/net.corda.samples.sendfile/contracts/InvoiceContractTests.java @@ -13,7 +13,7 @@ import java.util.Arrays; import static net.corda.testing.node.NodeTestUtils.transaction; -import static org.jgroups.util.Util.assertEquals; +import static org.junit.Assert.assertEquals; public class InvoiceContractTests { diff --git a/Features/attachment-sendfile/repositories.gradle b/Features/attachment-sendfile/repositories.gradle index 206b9c68..8be7b630 100644 --- a/Features/attachment-sendfile/repositories.gradle +++ b/Features/attachment-sendfile/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Features/attachment-sendfile/workflows/file.zip b/Features/attachment-sendfile/workflows/file.zip new file mode 100644 index 00000000..789ee5b4 Binary files /dev/null and b/Features/attachment-sendfile/workflows/file.zip differ diff --git a/Features/attachment-sendfile/workflows/src/main/java/net/corda/samples/sendfile/flows/SendAttachment.java b/Features/attachment-sendfile/workflows/src/main/java/net/corda/samples/sendfile/flows/SendAttachment.java index e03a3dde..ff2325b3 100644 --- a/Features/attachment-sendfile/workflows/src/main/java/net/corda/samples/sendfile/flows/SendAttachment.java +++ b/Features/attachment-sendfile/workflows/src/main/java/net/corda/samples/sendfile/flows/SendAttachment.java @@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList; import net.corda.core.crypto.SecureHash; import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; import net.corda.core.identity.Party; import net.corda.core.node.ServiceHub; import net.corda.core.transactions.SignedTransaction; @@ -53,14 +54,8 @@ public ProgressTracker getProgressTracker() { public SignedTransaction call() throws FlowException { // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 - + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // Initiate transaction Builder TransactionBuilder transactionBuilder = new TransactionBuilder(notary); diff --git a/Features/attachment-sendfile/workflows/src/test/java/net/corda/samples/sendfile/flows/FlowTests.java b/Features/attachment-sendfile/workflows/src/test/java/net/corda/samples/sendfile/flows/FlowTests.java index c526a824..9a05e859 100644 --- a/Features/attachment-sendfile/workflows/src/test/java/net/corda/samples/sendfile/flows/FlowTests.java +++ b/Features/attachment-sendfile/workflows/src/test/java/net/corda/samples/sendfile/flows/FlowTests.java @@ -2,11 +2,9 @@ import com.google.common.collect.ImmutableList; import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; import net.corda.core.transactions.SignedTransaction; -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 net.corda.testing.node.*; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -17,7 +15,8 @@ public class FlowTests { private final MockNetwork network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( TestCordapp.findCordapp("net.corda.samples.sendfile.contracts"), TestCordapp.findCordapp("net.corda.samples.sendfile.flows") - ))); + )) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB"))))); private final StartedMockNode a = network.createNode(); private final StartedMockNode b = network.createNode(); diff --git a/Features/confidentialidentity-whistleblower/README.md b/Features/confidentialidentity-whistleblower/README.md index a8993342..2e8a4e1f 100644 --- a/Features/confidentialidentity-whistleblower/README.md +++ b/Features/confidentialidentity-whistleblower/README.md @@ -1,6 +1,6 @@ # Whistleblower -- Confidential Identity -This CorDapp is a simple showcase of [confidential identities](https://docs.corda.net/docs/corda-os/api-identity.html#confidential-identities) (i.e. anonymous public keys). +This CorDapp is a simple showcase of [confidential identities](https://docs.r3.com/en/platform/corda/4.10/community/api-identity.html#confidential-identities) (i.e. anonymous public keys). ## Concepts @@ -8,7 +8,7 @@ This CorDapp is a simple showcase of [confidential identities](https://docs.cord A node (the *whistle-blower*) can whistle-blow on a company to another node (the *investigator*). Both the whistle-blower and the investigator generate anonymous public keys for this transaction, meaning that any third-parties -who manage to get ahold of the state cannot identity the whistle-blower or investigator. This process is handled +who manage to get a hold of the state cannot identity the whistle-blower or investigator. This process is handled automatically by the `SwapIdentitiesFlow`. @@ -17,14 +17,14 @@ automatically by the `SwapIdentitiesFlow`. ## Pre-Requisites -For development environment setup, please refer to: [Setup Guide](https://docs.corda.net/getting-set-up.html). +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.10/community/getting-set-up.html). ### Running the CorDapp Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) ``` -./gradlew clean deployNodes +./gradlew clean build deployNodes ``` Then type: (to run the nodes) ``` @@ -33,15 +33,17 @@ Then type: (to run the nodes) ### Interacting with the nodes: -We will interact with this CorDapp via the nodes' [CRaSH](https://docs.corda.net/docs/corda-os/shell.html) shells. +We will interact with this CorDapp via the nodes' interactive shells. -First, go the the shell of BraveEmployee, and report BadCompany to the TradeBody by running: +First, go the shell of BraveEmployee, and report BadCompany to the TradeBody by running: flow start BlowWhistleFlow badCompany: BadCompany, investigator: TradeBody -To see the whistle-blowing case stored on the whistle-blowing node, run: +To see the whistleblower case stored on the whistleblower node, run: - run vaultQuery contractStateType: BlowWhistleState + run vaultQuery contractStateType: net.corda.samples.whistleblower.states.BlowWhistleState + +You should see something similar to the following output: [ { "badCompany" : "C=KE,L=Eldoret,O=BadCompany", @@ -54,5 +56,5 @@ To see the whistle-blowing case stored on the whistle-blowing node, run: "participants" : [ "8Kqd4oWdx4KQGHGKubAvzAFiUG2JjhHxM2chUs4BTHHNHnUCgf6ngCAjmCu", "8Kqd4oWdx4KQGHGGdcHPVdafymUrBvXo6KimREJhttHNhY3JVBKgTCKod1X" ] } ] -We can also see the whistle-blowing case stored on the investigator node. +We can also see the whistleblower case stored on the investigator node. diff --git a/Features/confidentialidentity-whistleblower/build.gradle b/Features/confidentialidentity-whistleblower/build.gradle index 05e0b7a5..5c385bb7 100644 --- a/Features/confidentialidentity-whistleblower/build.gradle +++ b/Features/confidentialidentity-whistleblower/build.gradle @@ -14,8 +14,6 @@ buildscript { 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' } @@ -23,8 +21,8 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -42,9 +40,10 @@ allprojects {//Properties that you need to compile your project (The application repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } } @@ -86,6 +85,9 @@ dependencies { 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" + + cordaDriver "net.corda:corda-shell:4.10" + } task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { diff --git a/Features/confidentialidentity-whistleblower/contracts/build.gradle b/Features/confidentialidentity-whistleblower/contracts/build.gradle index 99e76271..2f8f2418 100644 --- a/Features/confidentialidentity-whistleblower/contracts/build.gradle +++ b/Features/confidentialidentity-whistleblower/contracts/build.gradle @@ -30,6 +30,6 @@ sourceSets { dependencies { // Corda dependencies. cordaCompile "$corda_release_group:corda-core:$corda_release_version" - compileOnly "$corda_release_group:corda-testserver-impl:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" } diff --git a/Features/confidentialidentity-whistleblower/contracts/src/test/java/net/corda/samples/whistleblower/states/BlowWhistleStateTests.java b/Features/confidentialidentity-whistleblower/contracts/src/test/java/net/corda/samples/whistleblower/states/BlowWhistleStateTests.java index 7458db23..c08acd65 100644 --- a/Features/confidentialidentity-whistleblower/contracts/src/test/java/net/corda/samples/whistleblower/states/BlowWhistleStateTests.java +++ b/Features/confidentialidentity-whistleblower/contracts/src/test/java/net/corda/samples/whistleblower/states/BlowWhistleStateTests.java @@ -6,9 +6,8 @@ import net.corda.core.identity.CordaX500Name; import net.corda.testing.core.TestIdentity; import org.junit.Test; +import org.junit.Assert; -import static org.jgroups.util.Util.assertEquals; -import static org.jgroups.util.Util.assertTrue; public class BlowWhistleStateTests { @@ -22,19 +21,19 @@ public void constructorTest() { // here, c is the bad corporation, a is the Whistleblower, and b is the investigator BlowWhistleState st = new BlowWhistleState(c.getParty(), a.getParty().anonymise(), b.getParty().anonymise()); - assertEquals(a.getParty(), st.getWhistleBlower()); - assertEquals(c.getParty(), st.getBadCompany()); - assertEquals(b.getParty(), st.getInvestigator()); + Assert.assertEquals(a.getParty(), st.getWhistleBlower()); + Assert.assertEquals(c.getParty(), st.getBadCompany()); + Assert.assertEquals(b.getParty(), st.getInvestigator()); - assertTrue(st.getParticipants().contains(a.getParty())); - assertTrue(st.getParticipants().contains(b.getParty())); + Assert.assertTrue(st.getParticipants().contains(a.getParty())); + Assert.assertTrue(st.getParticipants().contains(b.getParty())); } @Test public void stateImplementTests() { BlowWhistleState st = new BlowWhistleState(c.getParty(), a.getParty().anonymise(), b.getParty().anonymise()); - assertTrue(st instanceof ContractState); - assertTrue(st instanceof LinearState); - assertTrue(st.getLinearId() instanceof UniqueIdentifier); + Assert.assertTrue(st instanceof ContractState); + Assert.assertTrue(st instanceof LinearState); + Assert.assertTrue(st.getLinearId() instanceof UniqueIdentifier); } } diff --git a/Features/confidentialidentity-whistleblower/repositories.gradle b/Features/confidentialidentity-whistleblower/repositories.gradle index 206b9c68..8be7b630 100644 --- a/Features/confidentialidentity-whistleblower/repositories.gradle +++ b/Features/confidentialidentity-whistleblower/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Features/confidentialidentity-whistleblower/workflows/build.gradle b/Features/confidentialidentity-whistleblower/workflows/build.gradle index 10d368ed..284fa5de 100644 --- a/Features/confidentialidentity-whistleblower/workflows/build.gradle +++ b/Features/confidentialidentity-whistleblower/workflows/build.gradle @@ -53,7 +53,7 @@ dependencies { testCompile "$corda_release_group:corda-node-driver:$corda_release_version" cordaRuntime "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" - compileOnly "$corda_release_group:corda-testserver-impl:$corda_release_version" + cordapp "$corda_release_group:corda-confidential-identities:$corda_release_version" // CorDapp dependencies. diff --git a/Features/confidentialidentity-whistleblower/workflows/src/main/java/net/corda/samples/whistleblower/flows/BlowWhistleFlow.java b/Features/confidentialidentity-whistleblower/workflows/src/main/java/net/corda/samples/whistleblower/flows/BlowWhistleFlow.java index efca101c..7d3a1880 100644 --- a/Features/confidentialidentity-whistleblower/workflows/src/main/java/net/corda/samples/whistleblower/flows/BlowWhistleFlow.java +++ b/Features/confidentialidentity-whistleblower/workflows/src/main/java/net/corda/samples/whistleblower/flows/BlowWhistleFlow.java @@ -6,6 +6,7 @@ import net.corda.core.contracts.CommandData; import net.corda.core.flows.*; import net.corda.core.identity.AnonymousParty; +import net.corda.core.identity.CordaX500Name; import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; @@ -95,15 +96,10 @@ public SignedTransaction call() throws FlowException { BlowWhistleState output = new BlowWhistleState(badCompany, anonymousMe, anonymousInvestigator); CommandData command = new BlowWhistleContract.Commands.BlowWhistleCmd(); - // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=Nakuru,C=KE")); TransactionBuilder txBuilder = new TransactionBuilder(notary) .addOutputState(output, BlowWhistleContract.ID) .addCommand(command, ImmutableList.of(anonymousMe.getOwningKey(), anonymousInvestigator.getOwningKey())); diff --git a/Features/confidentialidentity-whistleblower/workflows/src/test/java/net/corda/samples/whistleblower/flows/FlowTests.java b/Features/confidentialidentity-whistleblower/workflows/src/test/java/net/corda/samples/whistleblower/flows/FlowTests.java index 4a1a3ca7..50771235 100644 --- a/Features/confidentialidentity-whistleblower/workflows/src/test/java/net/corda/samples/whistleblower/flows/FlowTests.java +++ b/Features/confidentialidentity-whistleblower/workflows/src/test/java/net/corda/samples/whistleblower/flows/FlowTests.java @@ -2,14 +2,13 @@ import com.google.common.collect.ImmutableList; import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; import net.corda.core.transactions.SignedTransaction; -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 net.corda.testing.node.*; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.Assert; import java.util.concurrent.ExecutionException; @@ -17,7 +16,8 @@ public class FlowTests { private final MockNetwork network = new MockNetwork(new MockNetworkParameters() .withCordappsForAllNodes(ImmutableList.of( TestCordapp.findCordapp("net.corda.samples.whistleblower.contracts"), - TestCordapp.findCordapp("net.corda.samples.whistleblower.flows")))); + TestCordapp.findCordapp("net.corda.samples.whistleblower.flows"))) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=Nakuru,C=KE"))))); private final StartedMockNode a = network.createNode(); private final StartedMockNode b = network.createNode(); private final StartedMockNode c = network.createNode(); @@ -43,7 +43,7 @@ public void dummyTest() throws ExecutionException, InterruptedException { CordaFuture future = a.startFlow(new BlowWhistleFlow(b.getInfo().getLegalIdentities().get(0), c.getInfo().getLegalIdentities().get(0))); network.runNetwork(); SignedTransaction ptx = future.get(); - assert (!ptx.getTx().getRequiredSigningKeys().contains(a.getInfo().getLegalIdentities().get(0).getOwningKey())); - assert (!ptx.getTx().getRequiredSigningKeys().contains(b.getInfo().getLegalIdentities().get(0).getOwningKey())); + Assert.assertTrue(!ptx.getTx().getRequiredSigningKeys().contains(a.getInfo().getLegalIdentities().get(0).getOwningKey())); + Assert.assertTrue(!ptx.getTx().getRequiredSigningKeys().contains(b.getInfo().getLegalIdentities().get(0).getOwningKey())); } } diff --git a/Features/constants.properties b/Features/constants.properties index 2cd5a28c..16db3235 100644 --- a/Features/constants.properties +++ b/Features/constants.properties @@ -1,12 +1,12 @@ cordaReleaseGroup=net.corda cordaCoreReleaseGroup=net.corda -cordaVersion=4.6 -cordaCoreVersion=4.6 +cordaVersion=4.10 +cordaCoreVersion=4.10 gradlePluginsVersion=5.0.12 kotlinVersion=1.2.71 junitVersion=4.12 quasarVersion=0.7.10 -log4jVersion =2.11.2 -platformVersion=8 +log4jVersion =2.17.1 +platformVersion=12 slf4jVersion=1.7.25 -nettyVersion=4.1.22.Final +nettyVersion=4.1.22.Final \ No newline at end of file diff --git a/Features/contractsdk-recordplayers/.ci/Jenkinsfile b/Features/contractsdk-recordplayers/.ci/Jenkinsfile new file mode 100644 index 00000000..7421cafe --- /dev/null +++ b/Features/contractsdk-recordplayers/.ci/Jenkinsfile @@ -0,0 +1,37 @@ +#!groovy +/** + * Jenkins pipeline to build the java CorDapp template + */ + +/** + * Kill already started job. + * Assume new commit takes precedence and results from previousunfinished builds are not required. + * This feature doesn't play well with disableConcurrentBuilds() option + */ +@Library('corda-shared-build-pipeline-steps') +import static com.r3.build.BuildControl.killAllExistingBuildsForJob +killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) + +pipeline { + agent { + label 'eight-cores' + } + options { + ansiColor('xterm') + timestamps() + timeout(3*60) // 3 hours + buildDiscarder(logRotator(daysToKeepStr: '7', artifactDaysToKeepStr: '7')) + } + stages { + stage('Build') { + steps { + sh './gradlew --no-daemon -s clean build test deployNodes' + } + } + } + post { + cleanup { + deleteDir() + } + } +} diff --git a/Features/contractsdk-recordplayers/.gitignore b/Features/contractsdk-recordplayers/.gitignore new file mode 100644 index 00000000..0d75547d --- /dev/null +++ b/Features/contractsdk-recordplayers/.gitignore @@ -0,0 +1,78 @@ +# Eclipse, ctags, Mac metadata, log files +.classpath +.project +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/ +*/bin/* + +# 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 diff --git a/Features/contractsdk-recordplayers/.settings/org.eclipse.jdt.core.prefs b/Features/contractsdk-recordplayers/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..ac86f90c --- /dev/null +++ b/Features/contractsdk-recordplayers/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1 @@ +org.eclipse.jdt.core.compiler.codegen.methodParameters=generate \ No newline at end of file diff --git a/Features/contractsdk-recordplayers/LICENCE b/Features/contractsdk-recordplayers/LICENCE new file mode 100644 index 00000000..3ff572d1 --- /dev/null +++ b/Features/contractsdk-recordplayers/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/Features/contractsdk-recordplayers/README.md b/Features/contractsdk-recordplayers/README.md new file mode 100644 index 00000000..33ee5a5c --- /dev/null +++ b/Features/contractsdk-recordplayers/README.md @@ -0,0 +1,69 @@ +# Contract SDK - Record Player Maintenance + + +If you're familiar with record players you probably know how difficult it is to make sure that they run in pristine condition, especially if it's a collectible. + +![](./cordaphone.png) + + +This CorDapp simulates how you could model the process of a limited edition record player (the cordagraf) that is manufactured and issued to specific dealers, and those dealers are the only entities that can service those record players after the fact and report stats back to the manufacturer about how the players are being used. + +Record Players are issued as `LinearState`s and are updated by dealers, that act functionally as the only entity that can update the `RecordPlayer` state. + + +### Using the Contract SDK + +The [Contract SDK](https://github.com/corda/contract-sdk) is a series of annotations that you can use in your corda contracts. It's great for putting together cleaner contract code and writing it faster. + +This repository demonstrates how you can configure and use it in your own projects. + +Configuration is essentially three small steps: + +- add `maven { url 'https://download.corda.net/maven/corda-lib-dev' }` to `repositories.gradle` +- add `compile "com.r3.corda.lib.contracts:contract-sdk:0.9-SNAPSHOT"` to the `build.gradle` file of your contract module in your CorDapp +- add the annotations to your apps! + + +To show how convenient this can be, here's an example demonstrating how to configure a simple issuance command with one output and no inputs. + +```java +// note the annotations here from the Contracts SDK +@RequireNumberOfStatesOnInput(value = 0) +@RequireNumberOfStatesOnOutput(value = 1) +class Issue implements Commands {} +``` + +If you've written contracts before you might be used to outlining the verify method, applying a conditional for issuance commands and configuring a lot of the same verification for contract inputs. This removes the need for those tools. + +Take a look at the small RecordPlayerContract sample in this repository to see how this works in practice. + +## Pre-Requisites +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.10/community/getting-set-up.html). + + +## Usage + +To run the CorDapp, just use the gradle wrapper script the same way you normally would. + + + ./gradlew clean build deployNodes + ./build/nodes/runnodes + +In the Manufacturer's interactive shell, type: + + flow start net.corda.samples.contractsdk.flows.IssueRecordPlayerFlow dealer: "O=Alice Audio,L=New York,C=US", needle: spherical + + # you can get your state id with a quick vault query + run vaultQuery contractStateType: net.corda.samples.contractsdk.states.RecordPlayerState + + flow start net.corda.samples.contractsdk.flows.UpdateRecordPlayerFlow stateId: < Place State ID here >, needleId: spherical, magneticStrength: 100, coilTurns: 100, amplifierSNR: 10000, songsPlayed: 100 + +## Additional Information + +If you're looking to find more information on record players specifically, I included some sources for how we modeled record players in the state class. + +- [You can find much more information on the contract sdk on github here](https://github.com/corda/contract-sdk) +- [The notes on the number of turns in a record player cartridge coil was sourced from here](https://www.vinylengine.com/turntable_forum/viewtopic.php?t=35449) +- [The info on other aspects of amplifiers was sourced from here](https://www.cambridgeaudio.com/usa/en/blog/amplifier-specifications) + + diff --git a/Features/contractsdk-recordplayers/TRADEMARK b/Features/contractsdk-recordplayers/TRADEMARK new file mode 100644 index 00000000..d2e056b5 --- /dev/null +++ b/Features/contractsdk-recordplayers/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/Features/contractsdk-recordplayers/build.gradle b/Features/contractsdk-recordplayers/build.gradle new file mode 100644 index 00000000..ef3f1f9e --- /dev/null +++ b/Features/contractsdk-recordplayers/build.gradle @@ -0,0 +1,150 @@ +buildscript { //properties that you need to build the project + Properties constants = new Properties() + file("$projectDir/../constants.properties").withInputStream { constants.load(it) } + + ext { + corda_release_group = constants.getProperty("cordaReleaseGroup") + corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") + corda_release_version = constants.getProperty("cordaVersion") + corda_core_release_version = constants.getProperty("cordaCoreVersion") + 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' + } + + repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://download.corda.net/maven/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 {//Properties that you need to compile your project (The application) + apply from: "${rootProject.projectDir}/repositories.gradle" + apply plugin: 'java' + + repositories { + mavenLocal() + + mavenCentral() + maven { url 'https://download.corda.net/maven/corda-dependencies' } + 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 + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} + +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") + } + } +} +//Module dependencis +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaCompile "$corda_release_group:corda-node-api:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":contracts") + cordapp project(":workflows") + + + 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" + cordaDriver "net.corda:corda-shell:4.10" + +} + + +//Task to deploy the nodes in order to bootstrap a network +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + + /* This property will load the CorDapps to each of the node by default, including the Notary. You can find them + * in the cordapps folder of the node at build/nodes/Notary/cordapps. However, the notary doesn't really understand + * the notion of cordapps. In production, Notary does not need cordapps as well. This is just a short cut to load + * the Corda network bootstrapper. + */ + nodeDefaults { + projectCordapp { + deploy = false + } + cordapp project(':contracts') + cordapp project(':workflows') + runSchemaMigration = true //This configuration is for any CorDapps with custom schema, We will leave this as true to avoid + //problems for developers who are not familiar with Corda. If you are not using custom schemas, you can change + //it to false for quicker project compiling time. + } + node { + name "O=Notary,L=London,C=GB" + notary = [validating : false] + p2pPort 10001 + rpcSettings { + address("localhost:10011") + adminAddress("localhost:10021") + } + } + node { + name "O=Manufacturer,L=London,C=GB" + p2pPort 10002 + rpcSettings { + address("localhost:10012") + adminAddress("localhost:10022") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + + node { + name "O=Alice Audio,L=New York,C=US" + p2pPort 10003 + rpcSettings { + address("localhost:10013") + adminAddress("localhost:10023") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + + node { + name "O=Bob Hustle Records,L=Egypt,C=US" + p2pPort 10004 + rpcSettings { + address("localhost:10014") + adminAddress("localhost:10024") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + +} diff --git a/Features/contractsdk-recordplayers/clients/build.gradle b/Features/contractsdk-recordplayers/clients/build.gradle new file mode 100644 index 00000000..db0e549a --- /dev/null +++ b/Features/contractsdk-recordplayers/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" +} + +springBoot { + mainClassName = "com.template.webserver.Server" +} + +/* The Client is the communication channel between the external and the node. This task will help you immediately + * execute your rpc methods in the main method of the client.kt. You can somewhat see this as a quick test of making + * RPC calls to your nodes. + */ +task runTemplateClient(type: JavaExec, dependsOn: assemble) { + classpath = sourceSets.main.runtimeClasspath + main = 'com.template.Client' + args 'localhost:10006', 'user1', 'test' +} + +/* This task will start the springboot server that connects to your node (via RPC connection). All of the http requests + * are in the Controller file. You can leave the Server.kt and NodeRPCConnection.kt file untouched for your use. + */ +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/Features/contractsdk-recordplayers/clients/src/main/java/net/corda/samples/contractsdk/Client.java b/Features/contractsdk-recordplayers/clients/src/main/java/net/corda/samples/contractsdk/Client.java new file mode 100644 index 00000000..b6b84208 --- /dev/null +++ b/Features/contractsdk-recordplayers/clients/src/main/java/net/corda/samples/contractsdk/Client.java @@ -0,0 +1,49 @@ +package net.corda.samples.contractsdk; + +import net.corda.client.rpc.CordaRPCClient; +import net.corda.client.rpc.CordaRPCConnection; +import net.corda.core.identity.CordaX500Name; +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 CordaRPCConnection clientConnection = client.start(rpcUsername, rpcPassword); + final CordaRPCOps proxy = clientConnection.getProxy(); + + // Interact with the node. + // Example #1, here we print the nodes on the network. + final List nodes = proxy.networkMapSnapshot(); + System.out.println("\n-- Here is the networkMap snapshot --"); + logger.info("{}", nodes); + + // Example #2, here we print the PartyA's node info + CordaX500Name name = proxy.nodeInfo().getLegalIdentities().get(0).getName();//nodeInfo().legalIdentities.first().name + System.out.println("\n-- Here is the node info of the node that the client connected to --"); + logger.info("{}", name); + + //Close the client connection + clientConnection.close(); + + } +} diff --git a/Features/contractsdk-recordplayers/clients/src/main/java/net/corda/samples/contractsdk/webserver/Controller.java b/Features/contractsdk-recordplayers/clients/src/main/java/net/corda/samples/contractsdk/webserver/Controller.java new file mode 100644 index 00000000..b0decff6 --- /dev/null +++ b/Features/contractsdk-recordplayers/clients/src/main/java/net/corda/samples/contractsdk/webserver/Controller.java @@ -0,0 +1,27 @@ +package net.corda.samples.contractsdk.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."; + } +} diff --git a/Features/contractsdk-recordplayers/clients/src/main/java/net/corda/samples/contractsdk/webserver/NodeRPCConnection.java b/Features/contractsdk-recordplayers/clients/src/main/java/net/corda/samples/contractsdk/webserver/NodeRPCConnection.java new file mode 100644 index 00000000..45e9492d --- /dev/null +++ b/Features/contractsdk-recordplayers/clients/src/main/java/net/corda/samples/contractsdk/webserver/NodeRPCConnection.java @@ -0,0 +1,48 @@ +package net.corda.samples.contractsdk.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(); + } +} diff --git a/Features/contractsdk-recordplayers/clients/src/main/java/net/corda/samples/contractsdk/webserver/Starter.java b/Features/contractsdk-recordplayers/clients/src/main/java/net/corda/samples/contractsdk/webserver/Starter.java new file mode 100644 index 00000000..a0f4847c --- /dev/null +++ b/Features/contractsdk-recordplayers/clients/src/main/java/net/corda/samples/contractsdk/webserver/Starter.java @@ -0,0 +1,23 @@ +package net.corda.samples.contractsdk.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); + } +} diff --git a/Features/contractsdk-recordplayers/clients/src/main/resources/static/app.js b/Features/contractsdk-recordplayers/clients/src/main/resources/static/app.js new file mode 100644 index 00000000..c58d2de8 --- /dev/null +++ b/Features/contractsdk-recordplayers/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/Features/contractsdk-recordplayers/clients/src/main/resources/static/index.html b/Features/contractsdk-recordplayers/clients/src/main/resources/static/index.html new file mode 100644 index 00000000..3ab26ca0 --- /dev/null +++ b/Features/contractsdk-recordplayers/clients/src/main/resources/static/index.html @@ -0,0 +1,15 @@ + + + + + + Example front-end. + + +
+ Corda +

CorDapp Template (Java Version)

+

Learn more about how to build CorDapps at sample-java

+
+ + \ No newline at end of file diff --git a/Features/contractsdk-recordplayers/config/dev/log4j2.xml b/Features/contractsdk-recordplayers/config/dev/log4j2.xml new file mode 100644 index 00000000..34ba4d45 --- /dev/null +++ b/Features/contractsdk-recordplayers/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/Features/contractsdk-recordplayers/config/test/log4j2.xml b/Features/contractsdk-recordplayers/config/test/log4j2.xml new file mode 100644 index 00000000..cd9926ca --- /dev/null +++ b/Features/contractsdk-recordplayers/config/test/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n + > + + + + + + + + + + + + diff --git a/Features/contractsdk-recordplayers/contracts/build.gradle b/Features/contractsdk-recordplayers/contracts/build.gradle new file mode 100644 index 00000000..565e817e --- /dev/null +++ b/Features/contractsdk-recordplayers/contracts/build.gradle @@ -0,0 +1,53 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + contract { + name "Contracts SDK - Record Player" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/dev") + } + } + test { + java { + srcDir 'src/test/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/test") + } + } + integrationTest { + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integrationTest/java') + } + } +} + + +dependencies { + // include contracts + // corda_contracts_sdk_version="0.9-SNAPSHOT" + compile "com.r3.corda.lib.contracts:contract-sdk:0.9-SNAPSHOT" + + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" +} diff --git a/Features/contractsdk-recordplayers/contracts/src/main/java/net/corda/samples/contractsdk/contracts/RecordPlayerContract.java b/Features/contractsdk-recordplayers/contracts/src/main/java/net/corda/samples/contractsdk/contracts/RecordPlayerContract.java new file mode 100644 index 00000000..c759afed --- /dev/null +++ b/Features/contractsdk-recordplayers/contracts/src/main/java/net/corda/samples/contractsdk/contracts/RecordPlayerContract.java @@ -0,0 +1,85 @@ +package net.corda.samples.contractsdk.contracts; + +import com.r3.corda.lib.contracts.contractsdk.StandardContract; +import com.r3.corda.lib.contracts.contractsdk.annotations.RequireNumberOfStatesOnInput; +import com.r3.corda.lib.contracts.contractsdk.annotations.RequireNumberOfStatesOnOutput; +import com.r3.corda.lib.contracts.contractsdk.annotations.RequireSignersFromEachInputState; +import com.r3.corda.lib.contracts.contractsdk.verifiers.StandardCommand; +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.CommandWithParties; +import net.corda.core.contracts.Contract; +import net.corda.core.contracts.ContractState; +import net.corda.core.transactions.LedgerTransaction; +import net.corda.samples.contractsdk.states.RecordPlayerState; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +import static net.corda.core.contracts.ContractsDSL.requireSingleCommand; +import static net.corda.core.contracts.ContractsDSL.requireThat; + + +/* +You'll notice some nice things about this contract code, +the first is that you can have really small command definitions with a lot of flexibility +the second is that you now have logical grouping of your contract verification and your command definition. + +This makes writing your contract code cleaner and more intuitive. +*/ +public class RecordPlayerContract extends StandardContract implements Contract { + // This id must be used to identify our contract when building a transaction. + public static final String ID = "net.corda.samples.contractsdk.contracts.RecordPlayerContract"; + + + // Used to indicate the transaction's intent. + public interface Commands extends CommandData { + + // note the annotations here from the Contracts SDK + @RequireNumberOfStatesOnInput(value = 0) + @RequireNumberOfStatesOnOutput(value = 1) + @RequireSignersFromEachInputState(roles = "manufacturer") + class Issue implements Commands { + } + + // with these annotations we can ignore worrying about many + // aspects of the transaction we're not as interested in. + @RequireNumberOfStatesOnInput(value = 1) + @RequireNumberOfStatesOnOutput(value = 1) + class Update implements Commands, StandardCommand { + + // We can add additional logic to the update command without adding extra boilerplate + @Override + public void verifyFurther(@NotNull LedgerTransaction tx) { + + final CommandWithParties command = requireSingleCommand(tx.getCommands(), RecordPlayerContract.Commands.class); + + List inputs = tx.getInputStates(); + List outputs = tx.getOutputStates(); + + RecordPlayerState oldRp = (RecordPlayerState) inputs.get(0); + RecordPlayerState newRp = (RecordPlayerState) outputs.get(0); + + + // We can still use Corda DSL function requireThat to replicate conditions-checks + requireThat(require -> { + + require.using("Magenetic Strength must be above 0", newRp.getMagneticStrength() > 0); + require.using("Magenetic Strength is too high", newRp.getMagneticStrength() < 100000); + + require.using("Coil turns can't be negative", oldRp.getCoilTurns() > 0); + require.using("Coil turns can't be negative", newRp.getCoilTurns() > 0); + + require.using("Coil turns too high", newRp.getCoilTurns() < 100000); + + require.using("songsPlayed should never decrease", oldRp.getSongsPlayed() <= newRp.getSongsPlayed()); + + return null; + }); + + } + + } + + } +} + diff --git a/Features/contractsdk-recordplayers/contracts/src/main/java/net/corda/samples/contractsdk/states/Needle.java b/Features/contractsdk-recordplayers/contracts/src/main/java/net/corda/samples/contractsdk/states/Needle.java new file mode 100644 index 00000000..49d0971a --- /dev/null +++ b/Features/contractsdk-recordplayers/contracts/src/main/java/net/corda/samples/contractsdk/states/Needle.java @@ -0,0 +1,11 @@ +package net.corda.samples.contractsdk.states; + +import net.corda.core.serialization.CordaSerializable; + +// define some types of record player needles +@CordaSerializable +public enum Needle { + SPHERICAL, + ELLIPTICAL, + DAMAGED +} diff --git a/Features/contractsdk-recordplayers/contracts/src/main/java/net/corda/samples/contractsdk/states/RecordPlayerState.java b/Features/contractsdk-recordplayers/contracts/src/main/java/net/corda/samples/contractsdk/states/RecordPlayerState.java new file mode 100644 index 00000000..cbbce3b5 --- /dev/null +++ b/Features/contractsdk-recordplayers/contracts/src/main/java/net/corda/samples/contractsdk/states/RecordPlayerState.java @@ -0,0 +1,167 @@ +package net.corda.samples.contractsdk.states; + +import com.r3.corda.lib.contracts.contractsdk.verifiers.StateWithRoles; +import net.corda.core.contracts.LinearState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.serialization.ConstructorForDeserialization; +import net.corda.samples.contractsdk.contracts.RecordPlayerContract; +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.Arrays; +import java.util.List; + + +// ********* +// * State * +// ********* +@BelongsToContract(RecordPlayerContract.class) +public class RecordPlayerState implements ContractState, LinearState, StateWithRoles { + + // we'll assume some basic stats about this record player + private Needle needle; // enum describing the needle type or damage + private int magneticStrength; // assume 100 gauss about the strength of a refrigerator magnet + private int coilTurns; // typical number of turns in the coils of a record player cartridge + private int amplifierSNR; // signal to noise ratio on the amplifier, 10,000 is pretty good. + private final UniqueIdentifier uid; // unique id for this rare record player. + + private int songsPlayed; // assume a new player has not played any tracks and it's in mint condition. + private String manufacturerNotes; + + private Party manufacturer; + private Party dealer; + + /* Constructor */ + @ConstructorForDeserialization + public RecordPlayerState(Party manufacturer, Party dealer, Needle needle, int magneticStrength, int coilTurns, int amplifierSNR, int songsPlayed, UniqueIdentifier uid) { + + if (songsPlayed < 0) { + throw new IllegalArgumentException("Invalid songs played"); + } + + if (magneticStrength < 0) { + throw new IllegalArgumentException("Invalid magnetic strength."); + } + + this.manufacturer = manufacturer; + this.dealer = dealer; + + this.needle = needle; + this.magneticStrength = magneticStrength; + this.coilTurns = coilTurns; + this.amplifierSNR = amplifierSNR; + this.songsPlayed = songsPlayed; + + this.uid = uid; + } + + /* Constructor for a CordaGraf with default */ + public RecordPlayerState(Party manufacturer, Party dealer, Needle needle) { + this(manufacturer, dealer, needle, 100, 700, 10000, 0, new UniqueIdentifier()); + } + + public Needle getNeedle() { + return needle; + } + + public void setNeedle(Needle needle) { + this.needle = needle; + } + + public int getMagneticStrength() { + return magneticStrength; + } + + public void setMagneticStrength(int magneticStrength) { + this.magneticStrength = magneticStrength; + } + + public int getCoilTurns() { + return coilTurns; + } + + public void setCoilTurns(int coilTurns) { + this.coilTurns = coilTurns; + } + + public int getAmplifierSNR() { + return amplifierSNR; + } + + public void setAmplifierSNR(int amplifierSNR) { + this.amplifierSNR = amplifierSNR; + } + + public UniqueIdentifier getUid() { + return uid; + } + + public int getSongsPlayed() { + return songsPlayed; + } + + public void setSongsPlayed(int songsPlayed) { + this.songsPlayed = songsPlayed; + } + + public Party getManufacturer() { + return manufacturer; + } + + public void setManufacturer(Party manufacturer) { + this.manufacturer = manufacturer; + } + + public Party getDealer() { + return dealer; + } + + public void setOwner(Party dealer) { + this.dealer = dealer; + } + + /* This method will indicate who are the participants and required signers when + * this state is used in a transaction. */ + @Override + public List getParticipants() { + return Arrays.asList(manufacturer, dealer); + } + + + public RecordPlayerState update(Needle needle, int magneticStrength, int coilTurns, int amplifierSNR, int songsPlayed) { + // take our params and apply them to the state object + return new RecordPlayerState(manufacturer, dealer, needle, magneticStrength, coilTurns, amplifierSNR, songsPlayed, uid); + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return this.getUid(); + } + + @NotNull + @Override + public Party getParty(@NotNull String role) { + Party ret; + + role = role.toLowerCase(); + + switch(role) { + case "manufacturer" : + ret = getManufacturer(); + break; + + case "dealer" : + ret = getDealer(); + break; // optional + + default : + ret = getManufacturer(); + break; + } + return ret; + } +} diff --git a/Features/contractsdk-recordplayers/contracts/src/test/java/net/corda/samples/contractsdk/contracts/ContractTests.java b/Features/contractsdk-recordplayers/contracts/src/test/java/net/corda/samples/contractsdk/contracts/ContractTests.java new file mode 100644 index 00000000..3182a48f --- /dev/null +++ b/Features/contractsdk-recordplayers/contracts/src/test/java/net/corda/samples/contractsdk/contracts/ContractTests.java @@ -0,0 +1,241 @@ +package net.corda.samples.contractsdk.contracts; + +import com.r3.corda.lib.contracts.contractsdk.StandardContract; +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.contracts.TypeOnlyCommandData; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.samples.contractsdk.states.Needle; +import net.corda.samples.contractsdk.states.RecordPlayerState; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.node.MockServices; +import org.junit.Test; + +import java.util.Arrays; + +import static net.corda.testing.node.NodeTestUtils.ledger; + +public class ContractTests { + private final MockServices ledgerServices = new MockServices(); + + // A pre-defined dummy command. + public interface Commands extends CommandData { + class DummyCommand extends TypeOnlyCommandData implements Commands { + } + } + + private final Party alice = new TestIdentity(new CordaX500Name("Alice Audio", "", "GB")).getParty(); + private final Party bob = new TestIdentity(new CordaX500Name("Bob's Hustle Records", "", "GB")).getParty(); + + @Test + public void implementsContractTest() { + assert (new RecordPlayerContract() instanceof Contract); + assert (new RecordPlayerContract() instanceof StandardContract); + } + + @Test + public void mustIncludeValidCommand() { + + RecordPlayerState st = new RecordPlayerState(alice, bob, Needle.SPHERICAL, 100, 700, 10000, 0, new UniqueIdentifier()); + + ledger(ledgerServices, l -> { + + // invalid command! + l.transaction(tx -> { + tx.output(RecordPlayerContract.ID, st); + tx.command(Arrays.asList(alice.getOwningKey(), bob.getOwningKey()), new Commands.DummyCommand()); + return tx.fails(); + }); + + // valid create command + l.transaction(tx -> { + tx.output(RecordPlayerContract.ID, st); + tx.command(Arrays.asList(alice.getOwningKey(), bob.getOwningKey()), new RecordPlayerContract.Commands.Issue()); + return tx.verifies(); + }); + + // valid update command + l.transaction(tx -> { + tx.input(RecordPlayerContract.ID, st); + tx.output(RecordPlayerContract.ID, st); + tx.command(Arrays.asList(alice.getOwningKey(), bob.getOwningKey()), new RecordPlayerContract.Commands.Update()); + return tx.verifies(); + }); + + return null; + }); + + } + + + @Test + public void requiresCorrectNumberOfCommands() { + + RecordPlayerState st = new RecordPlayerState(alice, bob, Needle.SPHERICAL, 100, 700, 10000, 0, new UniqueIdentifier()); + + ledger(ledgerServices, l -> { + + // 2+ issue commands + l.transaction(tx -> { + tx.input(RecordPlayerContract.ID, st); + tx.command(Arrays.asList(alice.getOwningKey(), bob.getOwningKey()), new RecordPlayerContract.Commands.Issue()); + tx.command(Arrays.asList(alice.getOwningKey(), bob.getOwningKey()), new RecordPlayerContract.Commands.Issue()); + return tx.fails(); + }); + + // 2+ update commands + l.transaction(tx -> { + tx.input(RecordPlayerContract.ID, st); + tx.output(RecordPlayerContract.ID, st); + tx.command(Arrays.asList(alice.getOwningKey(), bob.getOwningKey()), new RecordPlayerContract.Commands.Issue()); + tx.command(Arrays.asList(alice.getOwningKey(), bob.getOwningKey()), new RecordPlayerContract.Commands.Issue()); + return tx.fails(); + }); + + return null; + }); + + } + + + @Test + public void requiresCorrectNumberOfInputsAndOutputs() { + + RecordPlayerState st = new RecordPlayerState(alice, bob, Needle.SPHERICAL, 100, 700, 10000, 0, new UniqueIdentifier()); + + ledger(ledgerServices, l -> { + + // 2 inputs to issue, no outputs + l.transaction(tx -> { + tx.input(RecordPlayerContract.ID, st); + tx.input(RecordPlayerContract.ID, st); + tx.command(Arrays.asList(alice.getOwningKey(), bob.getOwningKey()), new RecordPlayerContract.Commands.Issue()); + return tx.fails(); + }); + + // 1 output to issue, no input + l.transaction(tx -> { + tx.output(RecordPlayerContract.ID, st); + tx.command(Arrays.asList(alice.getOwningKey(), bob.getOwningKey()), new RecordPlayerContract.Commands.Issue()); + return tx.verifies(); + }); + + // 2 outputs to issue + l.transaction(tx -> { + tx.input(RecordPlayerContract.ID, st); + tx.output(RecordPlayerContract.ID, st); + tx.output(RecordPlayerContract.ID, st); + tx.command(Arrays.asList(alice.getOwningKey(), bob.getOwningKey()), new RecordPlayerContract.Commands.Issue()); + return tx.fails(); + }); + + // 0 inputs to update + l.transaction(tx -> { + tx.output(RecordPlayerContract.ID, st); + tx.command(Arrays.asList(alice.getOwningKey(), bob.getOwningKey()), new RecordPlayerContract.Commands.Update()); + return tx.fails(); + }); + + // 2 inputs to update + l.transaction(tx -> { + tx.input(RecordPlayerContract.ID, st); + tx.input(RecordPlayerContract.ID, st); + tx.output(RecordPlayerContract.ID, st); + tx.command(Arrays.asList(alice.getOwningKey(), bob.getOwningKey()), new RecordPlayerContract.Commands.Update()); + return tx.fails(); + }); + + return null; + }); + + } + + + @Test + public void requireSpecificRoles() { + + RecordPlayerState st = new RecordPlayerState(alice, bob, Needle.SPHERICAL, 100, 700, 10000, 0, new UniqueIdentifier()); + + ledger(ledgerServices, l -> { + // must have manufacturer + l.transaction(tx -> { + tx.output(RecordPlayerContract.ID, st); + tx.command(Arrays.asList(alice.getOwningKey(), bob.getOwningKey()), new RecordPlayerContract.Commands.Issue()); + return tx.verifies(); + }); + + return null; + }); + + } + + + @Test + public void additionalVerificationTests() { + + ledger(ledgerServices, l -> { + + // songs played decreases, should error + l.transaction(tx -> { + RecordPlayerState oldSt = new RecordPlayerState(alice, bob, Needle.SPHERICAL, 100, 700, 10000, 20, new UniqueIdentifier()); + RecordPlayerState newSt = new RecordPlayerState(alice, bob, Needle.SPHERICAL, 100, 700, 10000, 15, new UniqueIdentifier()); + + tx.input(RecordPlayerContract.ID, oldSt); + tx.output(RecordPlayerContract.ID, newSt); + tx.command(Arrays.asList(alice.getOwningKey(), bob.getOwningKey()), new RecordPlayerContract.Commands.Update()); + return tx.fails(); + }); + + // songs played increases, valid + l.transaction(tx -> { + RecordPlayerState oldSt = new RecordPlayerState(alice, bob, Needle.SPHERICAL, 100, 700, 10000, 0, new UniqueIdentifier()); + RecordPlayerState newSt = new RecordPlayerState(alice, bob, Needle.SPHERICAL, 100, 800, 10000, 15, new UniqueIdentifier()); + + tx.input(RecordPlayerContract.ID, oldSt); + tx.output(RecordPlayerContract.ID, newSt); + tx.command(Arrays.asList(alice.getOwningKey(), bob.getOwningKey()), new RecordPlayerContract.Commands.Update()); + return tx.verifies(); + }); + + // songs played the same, valid + l.transaction(tx -> { + RecordPlayerState oldSt = new RecordPlayerState(alice, bob, Needle.SPHERICAL, 100, 700, 10000, 0, new UniqueIdentifier()); + RecordPlayerState newSt = new RecordPlayerState(alice, bob, Needle.SPHERICAL, 100, 800, 10000, 0, new UniqueIdentifier()); + + tx.input(RecordPlayerContract.ID, oldSt); + tx.output(RecordPlayerContract.ID, newSt); + tx.command(Arrays.asList(alice.getOwningKey(), bob.getOwningKey()), new RecordPlayerContract.Commands.Update()); + return tx.verifies(); + }); + + // negative coil turns on update, invalid + l.transaction(tx -> { + RecordPlayerState oldSt = new RecordPlayerState(alice, bob, Needle.SPHERICAL, 100, 700, 10000, 0, new UniqueIdentifier()); + RecordPlayerState newSt = new RecordPlayerState(alice, bob, Needle.SPHERICAL, 100, -30, 10000, 15, new UniqueIdentifier()); + + tx.input(RecordPlayerContract.ID, oldSt); + tx.output(RecordPlayerContract.ID, newSt); + tx.command(Arrays.asList(alice.getOwningKey(), bob.getOwningKey()), new RecordPlayerContract.Commands.Update()); + return tx.fails(); + }); + + // new magnet too strong, unsafe & invalid + l.transaction(tx -> { + RecordPlayerState oldSt = new RecordPlayerState(alice, bob, Needle.SPHERICAL, 100, 700, 10000, 0, new UniqueIdentifier()); + RecordPlayerState newSt = new RecordPlayerState(alice, bob, Needle.SPHERICAL, 9999999, 700, 10000, 15, new UniqueIdentifier()); + + tx.input(RecordPlayerContract.ID, oldSt); + tx.output(RecordPlayerContract.ID, newSt); + tx.command(Arrays.asList(alice.getOwningKey(), bob.getOwningKey()), new RecordPlayerContract.Commands.Update()); + return tx.fails(); + }); + + return null; + }); + + } + + +} diff --git a/Features/contractsdk-recordplayers/contracts/src/test/java/net/corda/samples/contractsdk/states/StateTests.java b/Features/contractsdk-recordplayers/contracts/src/test/java/net/corda/samples/contractsdk/states/StateTests.java new file mode 100644 index 00000000..6ad3049b --- /dev/null +++ b/Features/contractsdk-recordplayers/contracts/src/test/java/net/corda/samples/contractsdk/states/StateTests.java @@ -0,0 +1,81 @@ +package net.corda.samples.contractsdk.states; + +import net.corda.core.contracts.ContractState; +import net.corda.core.contracts.LinearState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.node.MockServices; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static junit.framework.TestCase.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class StateTests { + private final MockServices ledgerServices = new MockServices(); + + // in this example, bob is intended to be the dealer and record maintenance service. + private final Party alice = new TestIdentity(new CordaX500Name("Alice Audio", "", "GB")).getParty(); + private final Party bob = new TestIdentity(new CordaX500Name("Bob's Hustle Records", "", "GB")).getParty(); + + @Before + public void setup() { + } + + @After + public void tearDown() { + // pass + } + + // unmatching names + @Test(expected = IllegalArgumentException.class) + public void invalidMagneticStrength() { + RecordPlayerState st = new RecordPlayerState(alice, bob, Needle.SPHERICAL, -5, 700, 10000, 0, new UniqueIdentifier()); + } + + @Test(expected = IllegalArgumentException.class) + public void invalidSongsPlayed() { + RecordPlayerState st = new RecordPlayerState(alice, bob, Needle.SPHERICAL, 100, 700, 10000, -200, new UniqueIdentifier()); + } + + @Test + public void stateImplementsContractState() { + RecordPlayerState st = new RecordPlayerState(alice, bob, Needle.SPHERICAL); + assertTrue(st instanceof ContractState); + assertTrue(st instanceof LinearState); + assertTrue(st.getUid() instanceof UniqueIdentifier); + } + + @Test + public void constructorTests() { + RecordPlayerState st = new RecordPlayerState(alice, bob, Needle.SPHERICAL, 100, 700, 10000, 0, new UniqueIdentifier()); + + assertEquals(alice, st.getManufacturer()); + assertEquals(bob, st.getDealer()); + assertEquals(100, st.getMagneticStrength()); + assertEquals(700, st.getCoilTurns()); + assertEquals(10000, st.getAmplifierSNR()); + assertEquals(0, st.getSongsPlayed()); + } + + @Test + public void updateMethodTests() { + + RecordPlayerState st = new RecordPlayerState(alice, bob, Needle.SPHERICAL, 100, 700, 10000, 0, new UniqueIdentifier()); + + assertEquals(100, st.getMagneticStrength()); + assertEquals(700, st.getCoilTurns()); + assertEquals(10000, st.getAmplifierSNR()); + + // change params + assertEquals(50, st.update(st.getNeedle(), 50, 650, 8000, st.getSongsPlayed()).getMagneticStrength()); + assertEquals(650, st.update(st.getNeedle(), 50, 650, 8000, st.getSongsPlayed()).getCoilTurns()); + assertEquals(8000, st.update(st.getNeedle(), 50, 650, 8000, st.getSongsPlayed()).getAmplifierSNR()); + + } + + +} diff --git a/Features/contractsdk-recordplayers/cordaphone.png b/Features/contractsdk-recordplayers/cordaphone.png new file mode 100644 index 00000000..9e5c18ef Binary files /dev/null and b/Features/contractsdk-recordplayers/cordaphone.png differ diff --git a/Features/contractsdk-recordplayers/gradle.properties b/Features/contractsdk-recordplayers/gradle.properties new file mode 100644 index 00000000..1c49e963 --- /dev/null +++ b/Features/contractsdk-recordplayers/gradle.properties @@ -0,0 +1,3 @@ +name=Contracts SDK - Record Player +group=com.contractsdk +version=0.1 diff --git a/Features/contractsdk-recordplayers/gradle/wrapper/gradle-wrapper.jar b/Features/contractsdk-recordplayers/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..99340b4a Binary files /dev/null and b/Features/contractsdk-recordplayers/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Features/contractsdk-recordplayers/gradle/wrapper/gradle-wrapper.properties b/Features/contractsdk-recordplayers/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..0188225a --- /dev/null +++ b/Features/contractsdk-recordplayers/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +#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-5.6.4-all.zip + diff --git a/Features/contractsdk-recordplayers/gradlew b/Features/contractsdk-recordplayers/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/Features/contractsdk-recordplayers/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/Features/contractsdk-recordplayers/gradlew.bat b/Features/contractsdk-recordplayers/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/Features/contractsdk-recordplayers/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/Features/contractsdk-recordplayers/repositories.gradle b/Features/contractsdk-recordplayers/repositories.gradle new file mode 100644 index 00000000..6211873b --- /dev/null +++ b/Features/contractsdk-recordplayers/repositories.gradle @@ -0,0 +1,9 @@ +repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://jitpack.io' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } + maven { url 'https://download.corda.net/maven/corda-lib-dev' } +} diff --git a/Features/contractsdk-recordplayers/settings.gradle b/Features/contractsdk-recordplayers/settings.gradle new file mode 100644 index 00000000..2b08bb58 --- /dev/null +++ b/Features/contractsdk-recordplayers/settings.gradle @@ -0,0 +1,3 @@ +include 'workflows' +include 'contracts' +include 'clients' diff --git a/Features/contractsdk-recordplayers/workflows/build.gradle b/Features/contractsdk-recordplayers/workflows/build.gradle new file mode 100644 index 00000000..be29f597 --- /dev/null +++ b/Features/contractsdk-recordplayers/workflows/build.gradle @@ -0,0 +1,64 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + workflow { + name "Contracts SDK - Record Player" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/dev") + } + } + test { + java { + srcDir 'src/test/java' + java.outputDir = file('bin/main') + } + 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_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":contracts") +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} diff --git a/Features/contractsdk-recordplayers/workflows/src/integrationTest/java/net/corda/samples/contractsdk/DriverBasedTest.java b/Features/contractsdk-recordplayers/workflows/src/integrationTest/java/net/corda/samples/contractsdk/DriverBasedTest.java new file mode 100644 index 00000000..4f37237f --- /dev/null +++ b/Features/contractsdk-recordplayers/workflows/src/integrationTest/java/net/corda/samples/contractsdk/DriverBasedTest.java @@ -0,0 +1,48 @@ +package net.corda.samples.contractsdk; + +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 contractsdk, 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; + }); + } +} diff --git a/Features/contractsdk-recordplayers/workflows/src/main/java/net/corda/samples/contractsdk/flows/IssueRecordPlayerFlow.java b/Features/contractsdk-recordplayers/workflows/src/main/java/net/corda/samples/contractsdk/flows/IssueRecordPlayerFlow.java new file mode 100644 index 00000000..bde957ea --- /dev/null +++ b/Features/contractsdk-recordplayers/workflows/src/main/java/net/corda/samples/contractsdk/flows/IssueRecordPlayerFlow.java @@ -0,0 +1,98 @@ +package net.corda.samples.contractsdk.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.core.utilities.ProgressTracker; +import net.corda.samples.contractsdk.contracts.RecordPlayerContract; +import net.corda.samples.contractsdk.states.Needle; +import net.corda.samples.contractsdk.states.RecordPlayerState; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + + +// ****************** +// * Initiator flow * +// ****************** +@InitiatingFlow +@StartableByRPC +public class IssueRecordPlayerFlow extends FlowLogic { + + // We will not use these ProgressTracker for this Hello-World sample + private final ProgressTracker progressTracker = new ProgressTracker(); + + @Override + public ProgressTracker getProgressTracker() { + return progressTracker; + } + + private Party dealer; + private Party manufacturer; + private String needle; + + /* + * A new record player is issued only from the manufacturer to an exclusive dealer. + * Most of the settings are default + */ + public IssueRecordPlayerFlow(Party dealer, String needle) { + this.dealer = dealer; + this.needle = needle; + } + + @Suspendable + @Override + public SignedTransaction call() throws FlowException { + + // ideally this is only run by the manufacturer + this.manufacturer = getOurIdentity(); + + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + Needle n = Needle.SPHERICAL; + + if (needle.equals("elliptical")) { + n = Needle.ELLIPTICAL; + } + + RecordPlayerState output = new RecordPlayerState(this.manufacturer, this.dealer, n, 100, 700, 10000, 0, new UniqueIdentifier()); + + // Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // Add the iou as an output state, as well as a command to the transaction builder. + builder.addOutputState(output); + builder.addCommand(new RecordPlayerContract.Commands.Issue(), Arrays.asList(this.manufacturer.getOwningKey(), this.dealer.getOwningKey())); + + // verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + // Collect the other party's signature using the SignTransactionFlow. + + List otherParties = output.getParticipants().stream().map(el -> (Party) el).collect(Collectors.toList()); + otherParties.remove(getOurIdentity()); + +// FlowSession targetSession = initiateFlow(this.dealer); +// return subFlow(new FinalityFlow(ptx, Collections.singletonList(targetSession))); + + + List sessions = otherParties.stream().map(el -> initiateFlow(el)).collect(Collectors.toList()); + + SignedTransaction stx = subFlow(new CollectSignaturesFlow(ptx, sessions)); + + // Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(stx, sessions)); + } +} + + + diff --git a/Features/contractsdk-recordplayers/workflows/src/main/java/net/corda/samples/contractsdk/flows/IssueRecordPlayerFlowResponder.java b/Features/contractsdk-recordplayers/workflows/src/main/java/net/corda/samples/contractsdk/flows/IssueRecordPlayerFlowResponder.java new file mode 100644 index 00000000..33067872 --- /dev/null +++ b/Features/contractsdk-recordplayers/workflows/src/main/java/net/corda/samples/contractsdk/flows/IssueRecordPlayerFlowResponder.java @@ -0,0 +1,36 @@ +package net.corda.samples.contractsdk.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.flows.*; +import net.corda.core.transactions.SignedTransaction; + +// ****************** +// * Responder flow * +// ****************** +@InitiatedBy(IssueRecordPlayerFlow.class) +public class IssueRecordPlayerFlowResponder extends FlowLogic { + + //private variable + private FlowSession counterpartySession; + + //Constructor + public IssueRecordPlayerFlowResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + // we could include additional checks here but it's not necessary for the purposes of the contract sdk + } + }); + + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } +} diff --git a/Features/contractsdk-recordplayers/workflows/src/main/java/net/corda/samples/contractsdk/flows/UpdateRecordPlayerFlow.java b/Features/contractsdk-recordplayers/workflows/src/main/java/net/corda/samples/contractsdk/flows/UpdateRecordPlayerFlow.java new file mode 100644 index 00000000..6a891388 --- /dev/null +++ b/Features/contractsdk-recordplayers/workflows/src/main/java/net/corda/samples/contractsdk/flows/UpdateRecordPlayerFlow.java @@ -0,0 +1,118 @@ +package net.corda.samples.contractsdk.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.Command; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +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 net.corda.core.utilities.ProgressTracker; +import net.corda.samples.contractsdk.contracts.RecordPlayerContract; +import net.corda.samples.contractsdk.states.Needle; +import net.corda.samples.contractsdk.states.RecordPlayerState; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + + +// ****************** +// * Initiator flow * +// ****************** +@InitiatingFlow +@StartableByRPC +public class UpdateRecordPlayerFlow extends FlowLogic { + + private final ProgressTracker progressTracker = new ProgressTracker(); + + @Override + public ProgressTracker getProgressTracker() { + return progressTracker; + } + + String needleId; + + Needle needle; + int magneticStrength; + int coilTurns; + int amplifierSNR; + int songsPlayed; + UniqueIdentifier stateId; + + /* + * A new record player is issued only from the manufacturer to an exclusive dealer. + * Most of the settings are default + */ + public UpdateRecordPlayerFlow(UniqueIdentifier stateId, String needleId, int magneticStrength, int coilTurns, int amplifierSNR, int songsPlayed) { + + if (needleId.equalsIgnoreCase("elliptical")) { + needle = Needle.ELLIPTICAL; + } else if (needleId.equalsIgnoreCase("damaged")) { + needle = Needle.DAMAGED; + } else if (needleId.equalsIgnoreCase("spherical")){ + needle = Needle.SPHERICAL; + } else { + throw new IllegalArgumentException("Invalid needle state given."); + } + + this.stateId = stateId; + this.magneticStrength = magneticStrength; + this.coilTurns = coilTurns; + this.amplifierSNR = amplifierSNR; + this.songsPlayed = songsPlayed; + } + + @Suspendable + @Override + public SignedTransaction call() throws FlowException { + + Vault.Page results = getServiceHub().getVaultService().queryBy(RecordPlayerState.class); + StateAndRef inputStateAndRef = (StateAndRef) results.getStates().get(0); + RecordPlayerState input = (RecordPlayerState) ((StateAndRef) results.getStates().get(0)).getState().getData(); + + Party manufacturer = input.getManufacturer(); + Party dealer = input.getDealer(); + + if (getOurIdentity() == input.getDealer()) { + throw new IllegalArgumentException("Only the dealer that sold this record player can service it!"); + } + + final Party notary = inputStateAndRef.getState().getNotary(); + + Command command = new Command<>( + new RecordPlayerContract.Commands.Update(), + Arrays.asList(manufacturer.getOwningKey(), dealer.getOwningKey()) + ); + + // Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // add an output state + builder.addInputState(inputStateAndRef); + builder.addOutputState(input.update(needle, magneticStrength, coilTurns, amplifierSNR, songsPlayed), RecordPlayerContract.ID); + builder.addCommand(command); + + // Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + // Collect the other party's signature using the SignTransactionFlow. + List otherParties = input.getParticipants().stream().map(el -> (Party) el).collect(Collectors.toList()); + otherParties.remove(getOurIdentity()); + + List sessions = otherParties.stream().map(el -> initiateFlow(el)).collect(Collectors.toList()); + + SignedTransaction stx = subFlow(new CollectSignaturesFlow(ptx, sessions)); + + // Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(stx, sessions)); + } +} + + + diff --git a/Features/contractsdk-recordplayers/workflows/src/main/java/net/corda/samples/contractsdk/flows/UpdateRecordPlayerFlowResponder.java b/Features/contractsdk-recordplayers/workflows/src/main/java/net/corda/samples/contractsdk/flows/UpdateRecordPlayerFlowResponder.java new file mode 100644 index 00000000..9b142f45 --- /dev/null +++ b/Features/contractsdk-recordplayers/workflows/src/main/java/net/corda/samples/contractsdk/flows/UpdateRecordPlayerFlowResponder.java @@ -0,0 +1,40 @@ +package net.corda.samples.contractsdk.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.flows.*; +import net.corda.core.transactions.SignedTransaction; + + +// ****************** +// * Responder flow * +// ****************** +@InitiatedBy(UpdateRecordPlayerFlow.class) +public class UpdateRecordPlayerFlowResponder extends FlowLogic { + + // private variable + private FlowSession counterpartySession; + + // Constructor + public UpdateRecordPlayerFlowResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + // we could include additional checks here but it's not necessary for the purposes of the contract sdk + } + }); + + // Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + + return null; + } +} + + diff --git a/Features/contractsdk-recordplayers/workflows/src/test/java/net/corda/samples/contractsdk/flows/IssueRecordPlayerFlowTests.java b/Features/contractsdk-recordplayers/workflows/src/test/java/net/corda/samples/contractsdk/flows/IssueRecordPlayerFlowTests.java new file mode 100644 index 00000000..ca40f393 --- /dev/null +++ b/Features/contractsdk-recordplayers/workflows/src/test/java/net/corda/samples/contractsdk/flows/IssueRecordPlayerFlowTests.java @@ -0,0 +1,122 @@ +package net.corda.samples.contractsdk.flows; + + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.contracts.TransactionState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.node.NetworkParameters; +import net.corda.core.transactions.SignedTransaction; +import net.corda.samples.contractsdk.states.Needle; +import net.corda.samples.contractsdk.states.RecordPlayerState; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.time.Instant; +import java.util.Arrays; +import java.util.LinkedHashMap; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + + +/** + * Practical exercise instructions Flows part 1. + * Uncomment the unit tests and use the hints + unit test body to complete the FLows such that the unit tests pass. + */ +public class IssueRecordPlayerFlowTests { + + private MockNetwork network; + private StartedMockNode manufacturerNode; + private StartedMockNode dealerBNode; + private StartedMockNode dealerCNode; + + private Party manufacturer; + private Party dealerB; + private Party dealerC; + + private NetworkParameters testNetworkParameters = new NetworkParameters(4, Arrays.asList(), 10485760, (10485760 * 5), Instant.now(), 1, new LinkedHashMap<>()); + + @Before + public void setup() { + + network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.contractsdk.contracts"), + TestCordapp.findCordapp("net.corda.samples.contractsdk.flows"))).withNetworkParameters(testNetworkParameters) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); + + manufacturerNode = network.createPartyNode(null); + dealerBNode = network.createPartyNode(null); + dealerCNode = network.createPartyNode(null); + + manufacturer = manufacturerNode.getInfo().getLegalIdentities().get(0); + dealerB = dealerBNode.getInfo().getLegalIdentities().get(0); + dealerC = dealerCNode.getInfo().getLegalIdentities().get(0); + + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Rule + public final ExpectedException exception = ExpectedException.none(); + + @Test + public void flowUsesCorrectNotary() throws Exception { + // RecordPlayerState st = new RecordPlayerState(manufacturer, dealerB, Needle.SPHERICAL, 100, 700, 10000, 0, new UniqueIdentifier()); + + IssueRecordPlayerFlow issueFlow = new IssueRecordPlayerFlow(dealerB, "SPHERICAL"); + CordaFuture future = manufacturerNode.startFlow(issueFlow); + network.runNetwork(); + + SignedTransaction signedTransaction = future.get(); + + assertEquals(1, signedTransaction.getTx().getOutputStates().size()); + assertEquals(network.getNotaryNodes().get(0).getInfo().getLegalIdentities().get(0), signedTransaction.getNotary()); + } + + @Test + public void contractCorrectness() throws Exception { + RecordPlayerState st = new RecordPlayerState(manufacturer, dealerB, Needle.SPHERICAL, 100, 700, 10000, 0, new UniqueIdentifier()); + + IssueRecordPlayerFlow issueFlow = new IssueRecordPlayerFlow(dealerB, "SPHERICAL"); + CordaFuture future = manufacturerNode.startFlow(issueFlow); + network.runNetwork(); + + SignedTransaction signedTransaction = future.get(); + + TransactionState output = signedTransaction.getTx().getOutputs().get(0); + + assertEquals("net.corda.samples.contractsdk.contracts.RecordPlayerContract", output.getContract()); + } + + @Test + public void canCreateState() throws Exception { + RecordPlayerState st = new RecordPlayerState(manufacturer, dealerB, Needle.SPHERICAL, 100, 700, 10000, 0, new UniqueIdentifier()); + + IssueRecordPlayerFlow issueFlow = new IssueRecordPlayerFlow(dealerB, "SPHERICAL"); + CordaFuture future = manufacturerNode.startFlow(issueFlow); + network.runNetwork(); + + SignedTransaction signedTransaction = future.get(); + + RecordPlayerState output = signedTransaction.getTx().outputsOfType(RecordPlayerState.class).get(0); + + // get some random data from the output to verify + assertEquals(st.getManufacturer(), output.getManufacturer()); + assertEquals(st.getDealer(), output.getDealer()); + assertNotEquals(st.getDealer(), output.getManufacturer()); + assertEquals(st.getNeedle(), output.getNeedle()); + } + +} diff --git a/Features/contractsdk-recordplayers/workflows/src/test/java/net/corda/samples/contractsdk/flows/UpdateRecordPlayerFlowTests.java b/Features/contractsdk-recordplayers/workflows/src/test/java/net/corda/samples/contractsdk/flows/UpdateRecordPlayerFlowTests.java new file mode 100644 index 00000000..44c908d9 --- /dev/null +++ b/Features/contractsdk-recordplayers/workflows/src/test/java/net/corda/samples/contractsdk/flows/UpdateRecordPlayerFlowTests.java @@ -0,0 +1,120 @@ +package net.corda.samples.contractsdk.flows; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.node.NetworkParameters; +import net.corda.core.transactions.SignedTransaction; +import net.corda.samples.contractsdk.states.Needle; +import net.corda.samples.contractsdk.states.RecordPlayerState; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.time.Instant; +import java.util.Arrays; +import java.util.LinkedHashMap; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + + +public class UpdateRecordPlayerFlowTests { + + private MockNetwork network; + private StartedMockNode manufacturerNode; + private StartedMockNode dealerBNode; + private StartedMockNode dealerCNode; + + private Party manufacturer; + private Party dealerB; + private Party dealerC; + + private NetworkParameters testNetworkParameters = new NetworkParameters(4, Arrays.asList(), 10485760, (10485760 * 5), Instant.now(), 1, new LinkedHashMap<>()); + + @Before + public void setup() { + + network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.contractsdk.contracts"), + TestCordapp.findCordapp("net.corda.samples.contractsdk.flows"))).withNetworkParameters(testNetworkParameters) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); + + manufacturerNode = network.createPartyNode(null); + dealerBNode = network.createPartyNode(null); + dealerCNode = network.createPartyNode(null); + + manufacturer = manufacturerNode.getInfo().getLegalIdentities().get(0); + dealerB = dealerBNode.getInfo().getLegalIdentities().get(0); + dealerC = dealerCNode.getInfo().getLegalIdentities().get(0); + + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Rule + public final ExpectedException exception = ExpectedException.none(); + +// @Test +// public void flowUsesCorrectNotary() throws Exception { +// +// IssueRecordPlayerFlow f1 = new IssueRecordPlayerFlow(dealerB, "SPHERICAL"); +// CordaFuture future = manufacturerNode.startFlow(f1); +// network.runNetwork(); +// +// RecordPlayerState f1Output = future.get().getTx().outputsOfType(RecordPlayerState.class).get(0); +// +// UpdateRecordPlayerFlow f2 = new UpdateRecordPlayerFlow(f1Output.getLinearId(), "damaged", f1Output.getMagneticStrength(), f1Output.getCoilTurns(), f1Output.getAmplifierSNR(), f1Output.getSongsPlayed()); +// CordaFuture future2 = dealerBNode.startFlow(f2); +// network.runNetwork(); +// +// RecordPlayerState f2Output = future2.get().getTx().outputsOfType(RecordPlayerState.class).get(0); +// +// SignedTransaction signedTransaction = future.get(); +// +// // assert our contract SDK conditions +// assertEquals(1, signedTransaction.getTx().getOutputStates().size()); +// assertEquals(network.getNotaryNodes().get(0).getInfo().getLegalIdentities().get(0), signedTransaction.getNotary()); +// } +// +// // ensure that our linear state updates work correctly +// @Test +// public void flowUpdateTest() throws Exception { +// IssueRecordPlayerFlow f1 = new IssueRecordPlayerFlow(dealerB, "SPHERICAL"); +// CordaFuture future = manufacturerNode.startFlow(f1); +// network.runNetwork(); +// +// RecordPlayerState f1Output = future.get().getTx().outputsOfType(RecordPlayerState.class).get(0); +// +// UpdateRecordPlayerFlow f2 = new UpdateRecordPlayerFlow( +// f1Output.getLinearId(), +// "damaged", +// f1Output.getMagneticStrength(), +// f1Output.getCoilTurns(), +// f1Output.getAmplifierSNR(), +// f1Output.getSongsPlayed() + 5); +// +// CordaFuture future2 = dealerBNode.startFlow(f2); +// network.runNetwork(); +// +// RecordPlayerState f2Output = future2.get().getTx().outputsOfType(RecordPlayerState.class).get(0); +// +// assertEquals(Needle.SPHERICAL, f1Output.getNeedle()); +// assertEquals(Needle.DAMAGED, f2Output.getNeedle()); +// assertEquals(f1Output.getMagneticStrength(), f2Output.getMagneticStrength()); +// assertEquals(f1Output.getSongsPlayed() + 5, f2Output.getSongsPlayed()); +// assertNotEquals(f1Output.getSongsPlayed(), f2Output.getSongsPlayed()); +// } + + +} + diff --git a/Features/cordaservice-autopayroll/README.md b/Features/cordaservice-autopayroll/README.md index 1c5c4eb0..2d24a68f 100644 --- a/Features/cordaservice-autopayroll/README.md +++ b/Features/cordaservice-autopayroll/README.md @@ -1,17 +1,17 @@ # Auto Payroll -- CordaService -This Cordapp shows how to trigger a flow with vault update(completion of prior flows) using [CordaService](https://training.corda.net/corda-details/automation/#services) & [trackby](https://training.corda.net/corda-details/automation-solution/#track-and-notify). +This CorDapp shows how to trigger a flow with vault update(completion of prior flows) using [CordaService](https://training.corda.net/corda-details/automation/#services). ## Concepts -In this Cordapp, there are four parties: +In this CorDapp, there are four parties: - Finance Team: gives payroll order - - Bank Operater: take the order and automatically initiate the money transfer - - PetersonThomas: worker #1 will accept money - - GeorgeJefferson: worker #2 will accept money + - Bank Operator: takes the order and automatically initiate the money transfer + - PetersonThomas: worker #1 accepts money + - GeorgeJefferson: worker #2 accepts money There are two states `PaymentRequestState` & `MoneyState`, and two flows `RequestFlow` & `PaymentFlow`. The business logic looks like the following: -![alt text](./webpic/Business%20Logic.png) +![alt text](./webpic/Business_Logic.png) 1. Finance team put in payroll request to the bank operators 2. Bank operator receives the requests and process them without stopping @@ -22,21 +22,20 @@ There are two states `PaymentRequestState` & `MoneyState`, and two flows `Reques ## Pre-Requisites -For development environment setup, please refer to: [Setup Guide](https://docs.corda.net/getting-set-up.html). +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.10/community/getting-set-up.html). ### Running the CorDapp Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) ``` -./gradlew clean deployNodes +./gradlew clean build deployNodes ``` Then type: (to run the nodes) ``` ./build/nodes/runnodes ``` -if you have any questions during setup, please go to https://docs.corda.net/getting-set-up.html for detailed setup instructions. -Once all four nodes are started up, in Financeteam's node shell, run: +Once all four nodes are started up, in FinanceTeam's node shell, run: ``` flow start RequestFlowInitiator amount: 500, towhom: GeorgeJefferson ``` @@ -50,7 +49,7 @@ Behind the scenes, upon the completion of `RequestFlow`, a request state is stor ### Flow triggering using CordaService -The CordaService that triggers the flow is defined in AutoPaymentService.kt. The `CordaService` annotation is used by Corda to find any services that should be created on startup. In order for a flow to be startable by a service, the flow must be annotated with @StartableByService. An example is given in PaymentFlow.kt. +The CordaService that triggers the flow is defined in [AutoPaymentService.java](./workflows/src/main/java/net/corda/samples/autopayroll/flows/AutoPaymentService.java). The `CordaService` annotation is used by Corda to find any services that should be created on startup. In order for a flow to be startable by a service, the flow must be annotated with @StartableByService. An example is given in PaymentFlow.kt. You probably have noticed that `paymentFlow` is not tagged with `@StartableByRPC` like flows normally are. That is, it will not show up in the node shell's flow list. The reason is that `paymentflow` is a completely automated process that does not need any external interactions, so it is ok to be "not-been-seen" from the RPC. -That said, CordaService broadly opens up the probabilities of writing automated flows and fast responding Cordapps! +That said, CordaService broadly opens up the probabilities of writing automated flows and fast responding CorDapps! diff --git a/Features/cordaservice-autopayroll/build.gradle b/Features/cordaservice-autopayroll/build.gradle index ce3ffa47..54eca33e 100644 --- a/Features/cordaservice-autopayroll/build.gradle +++ b/Features/cordaservice-autopayroll/build.gradle @@ -22,8 +22,8 @@ buildscript {//properties that you need to build the project repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -40,9 +40,9 @@ allprojects {//Properties that you need to compile your project (The application repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } } @@ -84,6 +84,8 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" + } task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { diff --git a/Features/cordaservice-autopayroll/repositories.gradle b/Features/cordaservice-autopayroll/repositories.gradle index 206b9c68..8be7b630 100644 --- a/Features/cordaservice-autopayroll/repositories.gradle +++ b/Features/cordaservice-autopayroll/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Features/cordaservice-autopayroll/webpic/Business Logic.png b/Features/cordaservice-autopayroll/webpic/Business_Logic.png similarity index 100% rename from Features/cordaservice-autopayroll/webpic/Business Logic.png rename to Features/cordaservice-autopayroll/webpic/Business_Logic.png diff --git a/Features/cordaservice-autopayroll/workflows/src/main/java/net/corda/samples/autopayroll/flows/PaymentFlow.java b/Features/cordaservice-autopayroll/workflows/src/main/java/net/corda/samples/autopayroll/flows/PaymentFlow.java index 0651d0c9..65f44df0 100644 --- a/Features/cordaservice-autopayroll/workflows/src/main/java/net/corda/samples/autopayroll/flows/PaymentFlow.java +++ b/Features/cordaservice-autopayroll/workflows/src/main/java/net/corda/samples/autopayroll/flows/PaymentFlow.java @@ -36,14 +36,9 @@ public SignedTransaction call() throws FlowException { // Initiator flow logic goes here - // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + //Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); List> wBStateList = getServiceHub().getVaultService().queryBy(PaymentRequestState.class).getStates(); PaymentRequestState vaultState = wBStateList.get(wBStateList.size() - 1).getState().getData(); diff --git a/Features/cordaservice-autopayroll/workflows/src/main/java/net/corda/samples/autopayroll/flows/RequestFlow.java b/Features/cordaservice-autopayroll/workflows/src/main/java/net/corda/samples/autopayroll/flows/RequestFlow.java index 696e46a0..c94d01e6 100644 --- a/Features/cordaservice-autopayroll/workflows/src/main/java/net/corda/samples/autopayroll/flows/RequestFlow.java +++ b/Features/cordaservice-autopayroll/workflows/src/main/java/net/corda/samples/autopayroll/flows/RequestFlow.java @@ -57,14 +57,9 @@ public RequestFlowInitiator(String amount, Party towhom) { public SignedTransaction call() throws FlowException { progressTracker.setCurrentStep(GENERATING_TRANSACTION); - // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + //Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); Party bank = getServiceHub().getNetworkMapCache().getPeerByLegalName(new CordaX500Name("BankOperator", "Toronto", "CA")); PaymentRequestState output = new PaymentRequestState(amount, towhom, Arrays.asList(getOurIdentity(), bank)); diff --git a/Features/cordaservice-autopayroll/workflows/src/test/java/net/corda/samples/autopayroll/FlowTests.java b/Features/cordaservice-autopayroll/workflows/src/test/java/net/corda/samples/autopayroll/FlowTests.java index 125f8b24..85fd5758 100644 --- a/Features/cordaservice-autopayroll/workflows/src/test/java/net/corda/samples/autopayroll/FlowTests.java +++ b/Features/cordaservice-autopayroll/workflows/src/test/java/net/corda/samples/autopayroll/FlowTests.java @@ -6,10 +6,7 @@ import net.corda.core.identity.CordaX500Name; import net.corda.core.transactions.SignedTransaction; import net.corda.samples.autopayroll.flows.RequestFlow; -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 net.corda.testing.node.*; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -27,6 +24,7 @@ public class FlowTests { TestCordapp.findCordapp("net.corda.samples.autopayroll.flows") ) ) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) ); private final StartedMockNode a = network.createNode(); diff --git a/Features/customlogging-yocordapp/README.md b/Features/customlogging-yocordapp/README.md index 1d552d5b..6215da2c 100644 --- a/Features/customlogging-yocordapp/README.md +++ b/Features/customlogging-yocordapp/README.md @@ -2,14 +2,14 @@ ## Custom Logging -This is a modified version of the original yo cordapp with some additions to use custom log4j2 configurations. +This is a modified version of the original yo CorDapp with some additions to use custom log4j2 configurations. The primary example we've implemented here is json logging which is configured in `config/dev/log4j2.xml`. This gives us the ability to use Log4j thread contexts to log arbitrary objects or data points in json format. -In this example not only do the node logs output in json but we can add arbitrary key value pairs as well. +In this example not only do the node logs output in json, but we can add arbitrary key value pairs as well. ```java // here we have our first opportunity to log out the contents of the flow arguments. @@ -70,14 +70,14 @@ You can end up getting log feeds in json that look something like this: ### Pre-Requisites -See https://docs.corda.net/getting-set-up.html. +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.10/community/getting-set-up.html). ### Running the CorDapp Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) ``` -./gradlew clean deployNodes +./gradlew clean build deployNodes ``` Then type: (to run the nodes) @@ -85,7 +85,7 @@ Then type: (to run the nodes) ./build/nodes/runnodes ``` -When the nodes run you'll be able to see the node's json log files in their respesctive `logs` folders. +When the nodes run you'll be able to see the node's json log files in their respective `logs` folders. This logging configuration will add a new file that you can view. ```shell @@ -108,12 +108,12 @@ Yo to another node: ``` Where `NODE_NAME` is 'PartyA' or 'PartyB'. The space after the `:` is required. You are not required to use the full -X500 name in the node shell. Note you can't sent a Yo! to yourself because that's not cool! +X500 name in the node shell. Note: you can't send a Yo! to yourself because that's not cool! To see all the Yo's! other nodes have sent you in your vault (you do not store the Yo's! you send yourself), run: ``` - run vaultQuery contractStateType: YoState + run vaultQuery contractStateType: net.corda.samples.logging.states.YoState ``` ### Other ways to use this log configuration diff --git a/Features/customlogging-yocordapp/build.gradle b/Features/customlogging-yocordapp/build.gradle index dd5a089b..3363c62b 100644 --- a/Features/customlogging-yocordapp/build.gradle +++ b/Features/customlogging-yocordapp/build.gradle @@ -16,8 +16,8 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -33,9 +33,9 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } } @@ -76,6 +76,8 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" + } cordapp { diff --git a/Features/customlogging-yocordapp/gradle.properties b/Features/customlogging-yocordapp/gradle.properties index 6f39554d..a06a567b 100644 --- a/Features/customlogging-yocordapp/gradle.properties +++ b/Features/customlogging-yocordapp/gradle.properties @@ -1,3 +1,3 @@ name=Logging CorDapp -group=net.corda.examples.logging +group=net.corda.samples.logging version=0.1 diff --git a/Features/customlogging-yocordapp/repositories.gradle b/Features/customlogging-yocordapp/repositories.gradle index 206b9c68..8be7b630 100644 --- a/Features/customlogging-yocordapp/repositories.gradle +++ b/Features/customlogging-yocordapp/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Features/customlogging-yocordapp/workflows/src/main/java/net/corda/samples/logging/flows/YoFlow.java b/Features/customlogging-yocordapp/workflows/src/main/java/net/corda/samples/logging/flows/YoFlow.java index a1ab0842..ba173e1b 100644 --- a/Features/customlogging-yocordapp/workflows/src/main/java/net/corda/samples/logging/flows/YoFlow.java +++ b/Features/customlogging-yocordapp/workflows/src/main/java/net/corda/samples/logging/flows/YoFlow.java @@ -5,6 +5,7 @@ import net.corda.core.contracts.Command; import net.corda.core.contracts.StateAndContract; import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; @@ -73,8 +74,9 @@ public SignedTransaction call() throws FlowException { // flush the threadContext ThreadContext.removeAll(Arrays.asList("initiator", "target")); - // Obtain a reference to a notary. - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); Command command = new Command(new YoContract.Commands.Send(), Arrays.asList(me.getOwningKey())); YoState state = new YoState(me, target); diff --git a/Features/customlogging-yocordapp/workflows/src/test/java/net/corda/samples/logging/flows/FlowTests.java b/Features/customlogging-yocordapp/workflows/src/test/java/net/corda/samples/logging/flows/FlowTests.java index 5b3fa007..d1b09b4c 100644 --- a/Features/customlogging-yocordapp/workflows/src/test/java/net/corda/samples/logging/flows/FlowTests.java +++ b/Features/customlogging-yocordapp/workflows/src/test/java/net/corda/samples/logging/flows/FlowTests.java @@ -2,11 +2,9 @@ import com.google.common.collect.ImmutableList; import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; import net.corda.core.transactions.SignedTransaction; -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 net.corda.testing.node.*; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -17,7 +15,8 @@ public class FlowTests { private final MockNetwork network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( TestCordapp.findCordapp("net.corda.samples.logging.contracts"), TestCordapp.findCordapp("net.corda.samples.logging.flows") - ))); + )) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB"))))); private final StartedMockNode a = network.createNode(); private final StartedMockNode b = network.createNode(); diff --git a/Features/customquery-carinsurance/.settings/org.eclipse.jdt.core.prefs b/Features/customquery-carinsurance/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..ac86f90c --- /dev/null +++ b/Features/customquery-carinsurance/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1 @@ +org.eclipse.jdt.core.compiler.codegen.methodParameters=generate \ No newline at end of file diff --git a/Features/customquery-carinsurance/LICENCE b/Features/customquery-carinsurance/LICENCE new file mode 100644 index 00000000..3ff572d1 --- /dev/null +++ b/Features/customquery-carinsurance/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/Features/customquery-carinsurance/QueryableState.postman_collection.json b/Features/customquery-carinsurance/QueryableState.postman_collection.json new file mode 100644 index 00000000..593eaaf6 --- /dev/null +++ b/Features/customquery-carinsurance/QueryableState.postman_collection.json @@ -0,0 +1,93 @@ +{ + "info": { + "_postman_id": "381497f6-9903-47b2-9d74-7f1578532895", + "name": "Queryable State", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Vehicle Insurance", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"vehicleInfo\": {\n\t\t\"registrationNumber\": \"MH014343\",\n\t\t\"chasisNumber\": \"C232ND832\",\n\t\t\"make\": \"Hyundai\",\n\t\t\"model\": \"Elantra\",\n\t\t\"variant\": \"SX\",\n\t\t\"color\": \"Black\",\n\t\t\"fuelType\": \"Petrol\"\n\t},\n\t\"policyNumber\": \"8190\",\n\t\"insuredValue\": \"20000\",\n\t\"duration\": 1,\n\t\"premium\": \"3000\"\n}" + }, + "url": { + "raw": "http://localhost:8080/vehicleInsurance/Insuree", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "vehicleInsurance", + "Insuree" + ] + } + }, + "response": [] + }, + { + "name": "Claim", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"claimNumber\":\"C001\",\n\t\"claimDescription\": \"Minor Accident, Bumper Damaged\",\n\t\"claimAmount\": 1000\n}" + }, + "url": { + "raw": "http://localhost:8080//vehicleInsurance/claim/8190", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "", + "vehicleInsurance", + "claim", + "8190" + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] +} \ No newline at end of file diff --git a/Features/customquery-carinsurance/README.md b/Features/customquery-carinsurance/README.md new file mode 100644 index 00000000..e4bae8d2 --- /dev/null +++ b/Features/customquery-carinsurance/README.md @@ -0,0 +1,73 @@ +# Car Insurance -- QueryableState -- Custom Query + +This CorDapp demonstrates how [Custom Query](https://docs.r3.com/en/platform/corda/4.10/community/api-vault-query.html) +works in Corda. Corda allows developers to have the ability to query the vault using multiple mechanisms such as the +Vault Query API, using a JDBC session, etc. This sample demonstrates how to store your state data to a custom database +using an ORM tool and how to query this vault via Vault Query using some custom field defined in your state (for example +a string property of your state). To use Vault Query to query by a certain property the state must implement +[QueryableState](https://docs.r3.com/en/platform/corda/4.10/community/api-states.html#the-queryablestate-and-schedulablestate-interfaces) with a custom mapped schema. This way the DB (and Corda) know what you will be querying By. Please +refer to the flow [InsuranceClaimFlow](./workflows/src/main/java/net/corda/samples/carinsurance/flows/InsuranceClaimFlow.java) for details. + +In this CorDapp we would use an `Insurance` state and persist its properties in a custom table in the database. +The `Insurance` state among other fields also contains an `VehicleDetail` object, which is the asset being insured. We +have used this `VehicleDetail` to demonstrate _One-to-One_ relationship. Similarly, we also have a list of `Claim` +objects in the `Insurance` state which represents claims made against the insurance. We use them to demonstrate _ +One-to-Many_ relationship. + +## Concepts + +A spring boot client is provided with the CorDapp, which exposes two REST endpoints +(see [Controller](./clients/src/main/java/net/corda/samples/carinsurance/webserver/Controller.java) in the clients' module) to trigger the flows. Use the command `./gradlew bootRun` in the project root +folder to run the [Spring Boot Server](https://spring.io/projects/spring-boot#overview). + +### Flows + +There are two flows in this CorDapp: + +1. `IssueInsurance`: It creates the insurance state with the associated vehicle information. + +2. `InsuranceClaim`: It creates the claims against the insurance.It uses the Vault Query to perform a custom vault query. + +## Usage + +## Pre-Requisites + +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.10/community/getting-set-up.html). + +### Running the CorDapp + +Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) + +``` +./gradlew clean build deployNodes +``` + +Then type: (to run the nodes) + +``` +./build/nodes/runnodes +``` + +### Interacting with the nodes + +The Postman collection containing API's calls to the REST endpoints can be imported from the +link: https://www.getpostman.com/collections/ddc01c13b8ab4b5e853b. Use the option Import > Import from Link option in +Postman to import the collection. The collection file is also included in this repo for reference. + +

+Postman Import Collection +

+ +### Connecting to the Database + +The JDBC url to connect to the database would be printed in the console in node startup. Use the url to connect to the +database using a suitable client. The default username is 'sa' and password is '' (blank). You could download H2 Console +to connect to h2 database here: +http://www.h2database.com/html/download.html + +

+ Database URL +

+ +Refer here for more details regarding connecting to the node database. +https://docs.corda.net/head/node-database-access-h2.html diff --git a/Features/customquery-carinsurance/TRADEMARK b/Features/customquery-carinsurance/TRADEMARK new file mode 100644 index 00000000..d2e056b5 --- /dev/null +++ b/Features/customquery-carinsurance/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/Features/customquery-carinsurance/build.gradle b/Features/customquery-carinsurance/build.gradle new file mode 100644 index 00000000..fdfa3b91 --- /dev/null +++ b/Features/customquery-carinsurance/build.gradle @@ -0,0 +1,144 @@ +buildscript { + Properties constants = new Properties() + file("$projectDir/../constants.properties").withInputStream { constants.load(it) } + + ext { + corda_release_group = constants.getProperty("cordaReleaseGroup") + corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") + corda_release_version = constants.getProperty("cordaVersion") + corda_core_release_version = constants.getProperty("cordaCoreVersion") + 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' + } + + repositories { + mavenLocal() + mavenCentral() + maven { url 'https://download.corda.net/maven/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 from: "${rootProject.projectDir}/repositories.gradle" + apply plugin: 'java' + + repositories { + mavenLocal() + + mavenCentral() + maven { url 'https://download.corda.net/maven/corda-dependencies' } + 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 { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_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") + + 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" + + cordaDriver "net.corda:corda-shell:4.10" + +} + +cordapp { + info { + name "Queryablestate Car Insurance" + vendor "Corda Open Source" + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + } +} + +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + nodeDefaults { + projectCordapp { + deploy = false + } + cordapp project(":workflows") + cordapp project(":contracts") + runSchemaMigration = true //This configuration is for any CorDapps with custom schema, We will leave this as true to avoid + //problems for developers who are not familiar with Corda. If you are not using custom schemas, you can change + //it to false for quicker project compiling time. + } + + node { + name "O=Notary,L=London,C=GB" + notary = [validating : false] + p2pPort 10002 + rpcSettings { + address("localhost:10003") + adminAddress("localhost:10043") + } + cordapps.clear() + } + + node { + name "O=Insurer,L=London,C=GB" + p2pPort 10005 + rpcSettings { + address("localhost:10006") + adminAddress("localhost:10046") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + extraConfig = ['h2Settings.address' : 'localhost:20041'] + } + + node { + name "O=Insuree,L=New York,C=US" + p2pPort 10008 + rpcSettings { + address("localhost:10009") + adminAddress("localhost:10049") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + extraConfig = ['h2Settings.address' : 'localhost:20042'] + } +} diff --git a/Features/customquery-carinsurance/clients/build.gradle b/Features/customquery-carinsurance/clients/build.gradle new file mode 100644 index 00000000..069ee7c4 --- /dev/null +++ b/Features/customquery-carinsurance/clients/build.gradle @@ -0,0 +1,31 @@ +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(":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" +} + +springBoot { + mainClassName = "net.corda.samples.carinsurance.webserver.Starter" +} + +// Note that the bootRun task is built into gradle so there is no webserver task defined here. diff --git a/Features/customquery-carinsurance/clients/src/main/java/net/corda/samples/carinsurance/Client.java b/Features/customquery-carinsurance/clients/src/main/java/net/corda/samples/carinsurance/Client.java new file mode 100644 index 00000000..3f484f6e --- /dev/null +++ b/Features/customquery-carinsurance/clients/src/main/java/net/corda/samples/carinsurance/Client.java @@ -0,0 +1,36 @@ +package net.corda.samples.carinsurance; + +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); + } +} diff --git a/Features/customquery-carinsurance/clients/src/main/java/net/corda/samples/carinsurance/webserver/Controller.java b/Features/customquery-carinsurance/clients/src/main/java/net/corda/samples/carinsurance/webserver/Controller.java new file mode 100644 index 00000000..7ab53779 --- /dev/null +++ b/Features/customquery-carinsurance/clients/src/main/java/net/corda/samples/carinsurance/webserver/Controller.java @@ -0,0 +1,54 @@ +package net.corda.samples.carinsurance.webserver; + +import net.corda.core.identity.Party; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.samples.carinsurance.flows.ClaimInfo; +import net.corda.samples.carinsurance.flows.InsuranceClaimFlow; +import net.corda.samples.carinsurance.flows.InsuranceInfo; +import net.corda.samples.carinsurance.flows.IssueInsuranceFlow; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.*; + +import java.util.Set; + +/** + * 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; + } + + /* + * API to trigger the Insurance Issuance flow. + **/ + @PostMapping(value = "/vehicleInsurance/{insuree}") + private String vehicleSale(@RequestBody InsuranceInfo insuranceInfo, @PathVariable String insuree) { + + // Get the Party object from the partyName. + Set matchingParties = proxy.partiesFromName(insuree, false); + + // Trigger IssueInsuranceInitiator flow. + proxy.startFlowDynamic(IssueInsuranceFlow.IssueInsuranceInitiator.class, insuranceInfo, + matchingParties.iterator().next()); + return "Issue Insurance Completed"; + } + + /* + * API to trigger the Insurance Claim flow. It accepts the claim containing details of the claim and the + * policyNumber of the insurance in passed as path variable. + **/ + @PostMapping(value = "/vehicleInsurance/claim/{policyNumber}") + private String claim(@RequestBody ClaimInfo claimInfo, @PathVariable String policyNumber) { + + // Trigger InsuranceClaimInitiator flow. + proxy.startFlowDynamic(InsuranceClaimFlow.InsuranceClaimInitiator.class, claimInfo, policyNumber); + return "Insurance Claim Completed"; + } +} diff --git a/Features/customquery-carinsurance/clients/src/main/java/net/corda/samples/carinsurance/webserver/NodeRPCConnection.java b/Features/customquery-carinsurance/clients/src/main/java/net/corda/samples/carinsurance/webserver/NodeRPCConnection.java new file mode 100644 index 00000000..807b1f9e --- /dev/null +++ b/Features/customquery-carinsurance/clients/src/main/java/net/corda/samples/carinsurance/webserver/NodeRPCConnection.java @@ -0,0 +1,48 @@ +package net.corda.samples.carinsurance.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(); + } +} diff --git a/Features/customquery-carinsurance/clients/src/main/java/net/corda/samples/carinsurance/webserver/Starter.java b/Features/customquery-carinsurance/clients/src/main/java/net/corda/samples/carinsurance/webserver/Starter.java new file mode 100644 index 00000000..7388e816 --- /dev/null +++ b/Features/customquery-carinsurance/clients/src/main/java/net/corda/samples/carinsurance/webserver/Starter.java @@ -0,0 +1,23 @@ +package net.corda.samples.carinsurance.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); + } +} diff --git a/Features/customquery-carinsurance/clients/src/main/resources/application.yml b/Features/customquery-carinsurance/clients/src/main/resources/application.yml new file mode 100644 index 00000000..bc335169 --- /dev/null +++ b/Features/customquery-carinsurance/clients/src/main/resources/application.yml @@ -0,0 +1,5 @@ +config.rpc: + host: localhost + username: user1 + password: test + port: 10006 \ No newline at end of file diff --git a/Features/customquery-carinsurance/clients/src/main/resources/static/JDBC-url.png b/Features/customquery-carinsurance/clients/src/main/resources/static/JDBC-url.png new file mode 100644 index 00000000..d40b1ae6 Binary files /dev/null and b/Features/customquery-carinsurance/clients/src/main/resources/static/JDBC-url.png differ diff --git a/Features/customquery-carinsurance/clients/src/main/resources/static/Postman_screenshot.png b/Features/customquery-carinsurance/clients/src/main/resources/static/Postman_screenshot.png new file mode 100644 index 00000000..c71e3f67 Binary files /dev/null and b/Features/customquery-carinsurance/clients/src/main/resources/static/Postman_screenshot.png differ diff --git a/Features/customquery-carinsurance/clients/src/main/resources/static/app.js b/Features/customquery-carinsurance/clients/src/main/resources/static/app.js new file mode 100644 index 00000000..c58d2de8 --- /dev/null +++ b/Features/customquery-carinsurance/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/Features/customquery-carinsurance/clients/src/main/resources/static/index.html b/Features/customquery-carinsurance/clients/src/main/resources/static/index.html new file mode 100644 index 00000000..758501dd --- /dev/null +++ b/Features/customquery-carinsurance/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/Features/customquery-carinsurance/config/dev/log4j2.xml b/Features/customquery-carinsurance/config/dev/log4j2.xml new file mode 100644 index 00000000..34ba4d45 --- /dev/null +++ b/Features/customquery-carinsurance/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/Features/customquery-carinsurance/config/test/log4j2.xml b/Features/customquery-carinsurance/config/test/log4j2.xml new file mode 100644 index 00000000..cd9926ca --- /dev/null +++ b/Features/customquery-carinsurance/config/test/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n + > + + + + + + + + + + + + diff --git a/Features/customquery-carinsurance/contracts/build.gradle b/Features/customquery-carinsurance/contracts/build.gradle new file mode 100644 index 00000000..6e7348cc --- /dev/null +++ b/Features/customquery-carinsurance/contracts/build.gradle @@ -0,0 +1,49 @@ +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 "Queryablestate Car Insurance" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/dev") + } + } + test { + java { + srcDir 'src/test/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/test") + } + } + integrationTest { + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integrationTest/java') + } + } +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" +} diff --git a/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/contracts/InsuranceContract.java b/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/contracts/InsuranceContract.java new file mode 100644 index 00000000..966b0b25 --- /dev/null +++ b/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/contracts/InsuranceContract.java @@ -0,0 +1,51 @@ +package net.corda.samples.carinsurance.contracts; + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.CommandWithParties; +import net.corda.core.contracts.Contract; +import net.corda.core.contracts.ContractState; +import net.corda.core.transactions.LedgerTransaction; + +import java.util.List; + +import static net.corda.core.contracts.ContractsDSL.requireSingleCommand; +import static net.corda.core.contracts.ContractsDSL.requireThat; + +// ************ +// * Contract * +// ************ +public class InsuranceContract implements Contract { + // This is used to identify our contract when building a transaction. + public static final String ID = "net.corda.samples.carinsurance.contracts.InsuranceContract"; + + // A transaction is valid if the verify() function of the contract of all the transaction's input and output states + // does not throw an exception. + @Override + public void verify(LedgerTransaction tx) { + CommandWithParties command = requireSingleCommand(tx.getCommands(), InsuranceContract.Commands.class); + List inputs = tx.getInputStates(); + + if (command.getValue() instanceof InsuranceContract.Commands.IssueInsurance) { + requireThat(req -> { + req.using("Transaction must have no input states.", inputs.isEmpty()); + return null; + }); + } else if (command.getValue() instanceof InsuranceContract.Commands.AddClaim) { + requireThat(req -> { + req.using("Insurance transaction must have input states, the insurance police", (!inputs.isEmpty())); + return null; + }); + } else { + throw new IllegalArgumentException("Unrecognized command"); + } + } + + // Used to indicate the transaction's intent. + public interface Commands extends CommandData { + class IssueInsurance implements Commands { + } + + class AddClaim implements Commands { + } + } +} diff --git a/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/schema/InsuranceSchemaFamily.java b/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/schema/InsuranceSchemaFamily.java new file mode 100644 index 00000000..aa28dce4 --- /dev/null +++ b/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/schema/InsuranceSchemaFamily.java @@ -0,0 +1,9 @@ +package net.corda.samples.carinsurance.schema; + +/** + * Schema Family for Insurance Mapped Schema + * + * MappedSchema should be associated with a schema family which is consistent across versions of the schema. + * It allows the SchemaService to select the appropriate version of the schema if it has evolved over time. + */ +public class InsuranceSchemaFamily { } diff --git a/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/schema/InsuranceSchemaV1.java b/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/schema/InsuranceSchemaV1.java new file mode 100644 index 00000000..7ac11897 --- /dev/null +++ b/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/schema/InsuranceSchemaV1.java @@ -0,0 +1,26 @@ +package net.corda.samples.carinsurance.schema; + +import com.google.common.collect.ImmutableList; +import net.corda.core.schemas.MappedSchema; +//4.6 changes +import org.jetbrains.annotations.Nullable; +/** + * MappedSchema subclass representing the custom schema for the Insurance QueryableState. + */ +public class InsuranceSchemaV1 extends MappedSchema { + + /** + * The constructor of the MappedSchema requires the schemafamily, verison, and a list of all JPA entity classes for + * the Schema. + */ + public InsuranceSchemaV1() { + super(InsuranceSchemaFamily.class, 1, ImmutableList.of(PersistentInsurance.class, + PersistentVehicle.class, PersistentClaim.class)); + } + + @Nullable + @Override + public String getMigrationResource() { + return "insurance.changelog-master"; + } +} diff --git a/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/schema/PersistentClaim.java b/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/schema/PersistentClaim.java new file mode 100644 index 00000000..861e102f --- /dev/null +++ b/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/schema/PersistentClaim.java @@ -0,0 +1,53 @@ +package net.corda.samples.carinsurance.schema; + +import javax.persistence.*; +import java.util.UUID; +//4.6 changes +import org.hibernate.annotations.Type; + + +/** + * JPA Entity for saving claim details to the database table + */ +@Entity +@Table(name = "CLAIM_DETAIL") +public class PersistentClaim { + + @Id @Type (type = "uuid-char") private final UUID id; + @Column private final String claimNumber; + @Column private final String claimDescription; + @Column private final Integer claimAmount; + + /** + * Default constructor required by Hibernate + */ + public PersistentClaim() { + this.id = null; + this.claimNumber = null; + this.claimDescription = null; + this.claimAmount = null; + } + + public PersistentClaim(String claimNumber, String claimDescription, Integer claimAmount) { + this.id = UUID.randomUUID(); + this.claimNumber = claimNumber; + this.claimDescription = claimDescription; + this.claimAmount = claimAmount; + } + + public UUID getId() { + return id; + } + + public String getClaimNumber() { + return claimNumber; + } + + public String getClaimDescription() { + return claimDescription; + } + + public Integer getClaimAmount() { + return claimAmount; + } +} diff --git a/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/schema/PersistentInsurance.java b/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/schema/PersistentInsurance.java new file mode 100644 index 00000000..dda536b1 --- /dev/null +++ b/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/schema/PersistentInsurance.java @@ -0,0 +1,81 @@ +package net.corda.samples.carinsurance.schema; + +import net.corda.core.schemas.PersistentState; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.List; + + +/** + * JPA Entity for saving insurance details to the database table + */ +@Entity +@Table(name = "INSURANCE_DETAIL") +public class PersistentInsurance extends PersistentState implements Serializable { + + @Column private final String policyNumber; + @Column private final Long insuredValue; + @Column private final Integer duration; + @Column private final Integer premium; + + @OneToOne(cascade = CascadeType.PERSIST) + @JoinColumns({ + @JoinColumn(name = "id", referencedColumnName = "id"), + @JoinColumn(name = "registrationNumber", referencedColumnName = "registrationNumber"), + }) + private final PersistentVehicle vehicle; + + @OneToMany(cascade = CascadeType.PERSIST) + @JoinColumns({ + @JoinColumn(name = "output_index", referencedColumnName = "output_index"), + @JoinColumn(name = "transaction_id", referencedColumnName = "transaction_id"), + }) + private List claims; + + /** + * Default constructor required by Hibernate + */ + public PersistentInsurance() { + this.policyNumber = null; + this.insuredValue = null; + this.duration = null; + this.premium = null; + this.vehicle = null; + this.claims = null; + } + + public PersistentInsurance(String policyNumber, Long insuredValue, Integer duration, Integer premium, PersistentVehicle vehicle, + List claims) { + this.policyNumber = policyNumber; + this.insuredValue = insuredValue; + this.duration = duration; + this.premium = premium; + this.vehicle = vehicle; + this.claims = claims; + } + + public String getPolicyNumber() { + return policyNumber; + } + + public Long getInsuredValue() { + return insuredValue; + } + + public Integer getPremium() { + return premium; + } + + public Integer getDuration() { + return duration; + } + + public PersistentVehicle getVehicle() { + return vehicle; + } + + public List getClaims() { + return claims; + } +} diff --git a/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/schema/PersistentVehicle.java b/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/schema/PersistentVehicle.java new file mode 100644 index 00000000..06076919 --- /dev/null +++ b/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/schema/PersistentVehicle.java @@ -0,0 +1,87 @@ +package net.corda.samples.carinsurance.schema; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.io.Serializable; +import java.util.UUID; +//4.6 changes +import org.hibernate.annotations.Type; + + +/** + * JPA Entity for saving vehicle details to the database table + */ +@Entity +@Table(name = "VEHICLE_DETAIL") +public class PersistentVehicle implements Serializable { + + @Id @Type (type = "uuid-char") private final UUID id; + @Column private final String registrationNumber; + @Column private final String chasisNumber; + @Column private final String make; + @Column private final String model; + @Column private final String variant; + @Column private final String color; + @Column private final String fuelType; + + /** + * Default constructor required by Hibernate + */ + public PersistentVehicle() { + this.id = null; + this.registrationNumber = null; + this.chasisNumber = null; + this.make = null; + this.model = null; + this.variant = null; + this.color = null; + this.fuelType = null; + } + + public PersistentVehicle(String registrationNumber, String chasisNumber, String make, String model, String variant, + String color, String fuelType) { + this.id = UUID.randomUUID(); + this.registrationNumber = registrationNumber; + this.chasisNumber = chasisNumber; + this.make = make; + this.model = model; + this.variant = variant; + this.color = color; + this.fuelType = fuelType; + } + + public UUID getId() { + return id; + } + + public String getRegistrationNumber() { + return registrationNumber; + } + + public String getChasisNumber() { + return chasisNumber; + } + + public String getMake() { + return make; + } + + public String getModel() { + return model; + } + + public String getVariant() { + return variant; + } + + public String getColor() { + return color; + } + + public String getFuelType() { + return fuelType; + } + +} diff --git a/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/states/Claim.java b/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/states/Claim.java new file mode 100644 index 00000000..156393f9 --- /dev/null +++ b/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/states/Claim.java @@ -0,0 +1,34 @@ +package net.corda.samples.carinsurance.states; + +import net.corda.core.serialization.CordaSerializable; + +/** + * Simple POJO class for the claim details. + * Corda uses its own serialization framework hence the class needs to be annotated with @CordaSerializable, so that + * the objects of the class can be serialized to be passed across different nodes. + */ +@CordaSerializable +public class Claim { + + private final String claimNumber; + private final String claimDescription; + private final int claimAmount; + + public Claim(String claimNumber, String claimDescription, int claimAmount) { + this.claimNumber = claimNumber; + this.claimDescription = claimDescription; + this.claimAmount = claimAmount; + } + + public String getClaimNumber() { + return claimNumber; + } + + public String getClaimDescription() { + return claimDescription; + } + + public int getClaimAmount() { + return claimAmount; + } +} diff --git a/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/states/InsuranceState.java b/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/states/InsuranceState.java new file mode 100644 index 00000000..b2a0cb5d --- /dev/null +++ b/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/states/InsuranceState.java @@ -0,0 +1,151 @@ +package net.corda.samples.carinsurance.states; + +import com.google.common.collect.ImmutableList; +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.core.schemas.MappedSchema; +import net.corda.core.schemas.PersistentState; +import net.corda.core.schemas.QueryableState; +import net.corda.samples.carinsurance.contracts.InsuranceContract; +import net.corda.samples.carinsurance.schema.InsuranceSchemaV1; +import net.corda.samples.carinsurance.schema.PersistentClaim; +import net.corda.samples.carinsurance.schema.PersistentInsurance; +import net.corda.samples.carinsurance.schema.PersistentVehicle; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * Insurance State + * The state should implement the QueryableState to support custom schema development. + */ +@BelongsToContract(InsuranceContract.class) +public class InsuranceState implements QueryableState { + + // Represents the asset which is insured. + // This will be used to demonstrate one-to-one relationship + private final VehicleDetail vehicleDetail; + + // Fields related to the insurance state. + private final String policyNumber; + private final long insuredValue; + private final int duration; + private final int premium; + + private final Party insurer; + private final Party insuree; + + // Insurance claims made against the insurace policy + // This will be used to demonstrate one-to-many relationship + private final List claims; + + public InsuranceState(String policyNumber, long insuredValue, int duration, int premium, Party insurer, + Party insuree, VehicleDetail vehicleDetail, List claims) { + this.policyNumber = policyNumber; + this.insuredValue = insuredValue; + this.duration = duration; + this.premium = premium; + this.insurer = insurer; + this.insuree = insuree; + this.vehicleDetail = vehicleDetail; + this.claims = claims; + } + + /** + * Used to Generate the Entity for this Queryable State. + * This method is called by the SchemaService of the node, and the returned entity is handed over to the ORM tool + * to be persisted in custom database table. + * + * @param schema + * @return PersistentState + */ + @NotNull + @Override + public PersistentState generateMappedObject(@NotNull MappedSchema schema) { + if(schema instanceof InsuranceSchemaV1){ + + // Create list of PersistentClaim entity against every Claims object. + List persistentClaims = new ArrayList<>(); + if(claims != null && claims.size() > 0) { + for(Claim claim: claims){ + PersistentClaim persistentClaim = new PersistentClaim( + claim.getClaimNumber(), + claim.getClaimDescription(), + claim.getClaimAmount() + ); + persistentClaims.add(persistentClaim); + } + } + + return new PersistentInsurance( + this.policyNumber, + this.insuredValue, + this.duration, + this.premium, + this.vehicleDetail==null ? null : new PersistentVehicle( + vehicleDetail.getRegistrationNumber(), + vehicleDetail.getChasisNumber(), + vehicleDetail.getMake(), + vehicleDetail.getModel(), + vehicleDetail.getVariant(), + vehicleDetail.getColor(), + vehicleDetail.getFuelType() + ), + this.claims == null? null: persistentClaims + ); + }else{ + throw new IllegalArgumentException("Unsupported Schema"); + } + } + + /** + * Returns a list of supported Schemas by this Queryable State. + * + * @return Iterable + */ + @NotNull + @Override + public Iterable supportedSchemas() { + return ImmutableList.of(new InsuranceSchemaV1()); + } + + @NotNull + @Override + public List getParticipants() { + return ImmutableList.of(insuree, insurer); + } + + public String getPolicyNumber() { + return policyNumber; + } + + public long getInsuredValue() { + return insuredValue; + } + + public int getDuration() { + return duration; + } + + public int getPremium() { + return premium; + } + + public Party getInsurer() { + return insurer; + } + + public Party getInsuree() { + return insuree; + } + + public VehicleDetail getVehicleDetail() { + return vehicleDetail; + } + + public List getClaims() { + return claims; + } +} diff --git a/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/states/VehicleDetail.java b/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/states/VehicleDetail.java new file mode 100644 index 00000000..cdbf911e --- /dev/null +++ b/Features/customquery-carinsurance/contracts/src/main/java/net/corda/samples/carinsurance/states/VehicleDetail.java @@ -0,0 +1,60 @@ +package net.corda.samples.carinsurance.states; + + +import net.corda.core.serialization.CordaSerializable; + +/** + * Simple POJO class for the vehicle details. + * Corda uses its own serialization framework hence the class needs to be annotated with @CordaSerializable, so that + * the objects of the class can be serialized to be passed across different nodes. + */ +@CordaSerializable +public class VehicleDetail { + + private final String registrationNumber; + private final String chasisNumber; + private final String make; + private final String model; + private final String variant; + private final String color; + private final String fuelType; + + public VehicleDetail(String registrationNumber, String chasisNumber, String make, String model, String variant, + String color, String fuelType) { + this.registrationNumber = registrationNumber; + this.chasisNumber = chasisNumber; + this.make = make; + this.model = model; + this.variant = variant; + this.color = color; + this.fuelType = fuelType; + } + + public String getRegistrationNumber() { + return registrationNumber; + } + + public String getChasisNumber() { + return chasisNumber; + } + + public String getMake() { + return make; + } + + public String getModel() { + return model; + } + + public String getVariant() { + return variant; + } + + public String getColor() { + return color; + } + + public String getFuelType() { + return fuelType; + } +} diff --git a/Features/customquery-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/contracts/InsuranceContractTests.java b/Features/customquery-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/contracts/InsuranceContractTests.java new file mode 100644 index 00000000..b2db25db --- /dev/null +++ b/Features/customquery-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/contracts/InsuranceContractTests.java @@ -0,0 +1,119 @@ +package net.corda.samples.carinsurance.contracts; + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.contracts.TypeOnlyCommandData; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.samples.carinsurance.states.Claim; +import net.corda.samples.carinsurance.states.InsuranceState; +import net.corda.samples.carinsurance.states.VehicleDetail; +import net.corda.testing.core.DummyCommandData; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.node.MockServices; +import org.junit.Test; + +import java.util.Arrays; + +import static net.corda.testing.node.NodeTestUtils.transaction; + +public class InsuranceContractTests { + + // A pre-defined dummy command. + public interface Commands extends CommandData { + class DummyCommand extends TypeOnlyCommandData implements Commands { + } + } + + private MockServices ledgerServices = new MockServices( + Arrays.asList("net.corda.samples.carinsurance.contracts") + ); + + private TestIdentity a = new TestIdentity(new CordaX500Name("Alice", "", "GB")); + private TestIdentity b = new TestIdentity(new CordaX500Name("Bob", "", "GB")); + + String registrationNumber = "registration number: 2ds9Fvk"; + String chassisNum = "chassis# aedl3sc"; + String make = "Toyota"; + String model = "Corolla"; + String variant = "SE"; + String color = "hot rod beige"; + String fuelType = "regular"; + + VehicleDetail vd = new VehicleDetail( + registrationNumber, + chassisNum, + make, + model, + variant, + color, + fuelType); + + String desc = "claim description: my car was hit by a blockchain"; + String claimNumber = "B-132022"; + int claimAmount = 3000; + + Claim c = new Claim(claimNumber, desc, claimAmount); + + // in this test scenario, alice is our insurer. + String policyNum = "R3-Policy-A4byCd"; + long insuredValue = 100000L; + int duration = 50; + int premium = 5; + Party insurer = a.getParty(); + Party insuree = b.getParty(); + + InsuranceState st = new InsuranceState( + policyNum, + insuredValue, + duration, + premium, + insurer, + insuree, + vd, + Arrays.asList(c)); + + @Test + public void contractImplementsContract() { + assert (new InsuranceContract() instanceof Contract); + } + + @Test + public void contractRequiresOneCommandInTheTransaction() { + transaction(ledgerServices, tx -> { + tx.output(InsuranceContract.ID, st); + // Has two commands, will fail. + tx.command(Arrays.asList(a.getPublicKey(), b.getPublicKey()), new InsuranceContract.Commands.IssueInsurance()); + tx.command(Arrays.asList(a.getPublicKey(), b.getPublicKey()), new InsuranceContract.Commands.IssueInsurance()); + tx.fails(); + return null; + }); + + transaction(ledgerServices, tx -> { + tx.output(InsuranceContract.ID, st); + // Has one command, will verify. + tx.command(Arrays.asList(a.getPublicKey(), b.getPublicKey()), new InsuranceContract.Commands.IssueInsurance()); + tx.verifies(); + return null; + }); + } + + @Test + public void contractRequiresTheTransactionsCommandToBeAnIssueCommand() { + transaction(ledgerServices, tx -> { + // Has wrong command type, will fail. + tx.output(InsuranceContract.ID, st); + tx.command(Arrays.asList(a.getPublicKey()), DummyCommandData.INSTANCE); + tx.fails(); + return null; + }); + + transaction(ledgerServices, tx -> { + // Has correct command type, will verify. + tx.output(InsuranceContract.ID, st); + tx.command(Arrays.asList(a.getPublicKey(), b.getPublicKey()), new InsuranceContract.Commands.IssueInsurance()); + tx.verifies(); + return null; + }); + } +} diff --git a/Features/customquery-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/states/ClaimTests.java b/Features/customquery-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/states/ClaimTests.java new file mode 100644 index 00000000..7cff18a2 --- /dev/null +++ b/Features/customquery-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/states/ClaimTests.java @@ -0,0 +1,22 @@ +package net.corda.samples.carinsurance.states; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ClaimTests { + + private final String desc = "claim description: my car was hit by a blockchain"; + private final String claimNumber = "B-132022"; + private final int claimAmount = 3000; + + @Test + public void constructorTest() { + Claim st = new Claim(claimNumber, desc, claimAmount); + + assertEquals(claimNumber, st.getClaimNumber()); + assertEquals(desc, st.getClaimDescription()); + assertEquals(claimAmount, st.getClaimAmount()); + } + +} diff --git a/Features/customquery-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/states/InsuranceStateTests.java b/Features/customquery-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/states/InsuranceStateTests.java new file mode 100644 index 00000000..8fbe5552 --- /dev/null +++ b/Features/customquery-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/states/InsuranceStateTests.java @@ -0,0 +1,73 @@ +package net.corda.samples.carinsurance.states; + +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.testing.core.TestIdentity; +import org.junit.Test; + +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +public class InsuranceStateTests { + + TestIdentity a = new TestIdentity(new CordaX500Name("Alice", "", "GB")); + TestIdentity b = new TestIdentity(new CordaX500Name("Bob", "", "GB")); + + @Test + public void constructorTest() { + + String registrationNumber = "registration number: 2ds9Fvk"; + String chassisNum = "chassis# aedl3sc"; + String make = "Toyota"; + String model = "Corolla"; + String variant = "SE"; + String color = "hot rod beige"; + String fuelType = "regular"; + + VehicleDetail vd = new VehicleDetail( + registrationNumber, + chassisNum, + make, + model, + variant, + color, + fuelType); + + String desc = "claim description: my car was hit by a blockchain"; + String claimNumber = "B-132022"; + int claimAmount = 3000; + + Claim c = new Claim(claimNumber, desc, claimAmount); + + // in this test scenario, alice is our insurer. + String policyNum = "R3-Policy-A4byCd"; + long insuredValue = 100000L; + int duration = 50; + int premium = 5; + Party insurer = a.getParty(); + Party insuree = b.getParty(); + + InsuranceState st = new InsuranceState( + policyNum, + insuredValue, + duration, + premium, + insurer, + insuree, + vd, + Arrays.asList(c)); + + assertEquals(policyNum, st.getPolicyNumber()); + assertEquals(insuredValue, st.getInsuredValue()); + assertEquals(duration, st.getDuration()); + assertEquals(premium, st.getPremium()); + assertEquals(insurer, st.getInsurer()); + assertEquals(insuree, st.getInsuree()); + assertEquals(vd, st.getVehicleDetail()); + + assertTrue(st.getParticipants().contains(a.getParty())); + assertTrue(st.getParticipants().contains(b.getParty())); + } + +} diff --git a/Features/customquery-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/states/VehicleDetailTests.java b/Features/customquery-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/states/VehicleDetailTests.java new file mode 100644 index 00000000..e8d55cb1 --- /dev/null +++ b/Features/customquery-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/states/VehicleDetailTests.java @@ -0,0 +1,43 @@ +package net.corda.samples.carinsurance.states; + +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.core.TestIdentity; +import org.junit.Test; +import static org.junit.Assert.assertEquals; + +public class VehicleDetailTests { + + TestIdentity a = new TestIdentity(new CordaX500Name("Alice", "", "GB")); + TestIdentity b = new TestIdentity(new CordaX500Name("Bob", "", "GB")); + + + @Test + public void constructorTest() { + + String registrationNumber = "registration number: 2ds9Fvk"; + String chassisNum = "chassis# aedl3sc"; + String make = "Toyota"; + String model = "Corolla"; + String variant = "SE"; + String color = "hot rod beige"; + String fuelType = "regular"; + + VehicleDetail vd = new VehicleDetail( + registrationNumber, + chassisNum, + make, + model, + variant, + color, + fuelType); + + assertEquals(registrationNumber, vd.getRegistrationNumber()); + assertEquals(chassisNum, vd.getChasisNumber()); + assertEquals(make, vd.getMake()); + assertEquals(model, vd.getModel()); + assertEquals(variant, vd.getVariant()); + assertEquals(color, vd.getColor()); + assertEquals(fuelType, vd.getFuelType()); + } + +} diff --git a/Features/customquery-carinsurance/gradle.properties b/Features/customquery-carinsurance/gradle.properties new file mode 100644 index 00000000..9e31391f --- /dev/null +++ b/Features/customquery-carinsurance/gradle.properties @@ -0,0 +1,3 @@ +name=Queryablestate Car Insurance +group=com.carinsurance +version=0.1 diff --git a/Features/customquery-carinsurance/gradle/wrapper/gradle-wrapper.jar b/Features/customquery-carinsurance/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..99340b4a Binary files /dev/null and b/Features/customquery-carinsurance/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Features/customquery-carinsurance/gradle/wrapper/gradle-wrapper.properties b/Features/customquery-carinsurance/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..ae01072d --- /dev/null +++ b/Features/customquery-carinsurance/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-5.6.4-all.zip diff --git a/Features/customquery-carinsurance/gradlew b/Features/customquery-carinsurance/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/Features/customquery-carinsurance/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/Features/customquery-carinsurance/gradlew.bat b/Features/customquery-carinsurance/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/Features/customquery-carinsurance/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/Features/customquery-carinsurance/repositories.gradle b/Features/customquery-carinsurance/repositories.gradle new file mode 100644 index 00000000..8be7b630 --- /dev/null +++ b/Features/customquery-carinsurance/repositories.gradle @@ -0,0 +1,8 @@ +repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://jitpack.io' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } +} diff --git a/Features/customquery-carinsurance/settings.gradle b/Features/customquery-carinsurance/settings.gradle new file mode 100644 index 00000000..2514aca2 --- /dev/null +++ b/Features/customquery-carinsurance/settings.gradle @@ -0,0 +1,3 @@ +include 'workflows' +include 'contracts' +include 'clients' \ No newline at end of file diff --git a/Features/customquery-carinsurance/workflows/build.gradle b/Features/customquery-carinsurance/workflows/build.gradle new file mode 100644 index 00000000..e53991a3 --- /dev/null +++ b/Features/customquery-carinsurance/workflows/build.gradle @@ -0,0 +1,65 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + workflow { + name "Queryablestate Car Insurance" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/dev") + } + } + test { + java { + srcDir 'src/test/java' + java.outputDir = file('bin/main') + } + 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_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":contracts") +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} diff --git a/Features/customquery-carinsurance/workflows/src/integrationTest/java/net/corda/samples/carinsurance/DriverBasedTest.java b/Features/customquery-carinsurance/workflows/src/integrationTest/java/net/corda/samples/carinsurance/DriverBasedTest.java new file mode 100644 index 00000000..c01ffacd --- /dev/null +++ b/Features/customquery-carinsurance/workflows/src/integrationTest/java/net/corda/samples/carinsurance/DriverBasedTest.java @@ -0,0 +1,48 @@ +package net.corda.samples.carinsurance; + +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; + }); + } +} diff --git a/Features/customquery-carinsurance/workflows/src/main/java/net/corda/samples/carinsurance/flows/ClaimInfo.java b/Features/customquery-carinsurance/workflows/src/main/java/net/corda/samples/carinsurance/flows/ClaimInfo.java new file mode 100644 index 00000000..26a47000 --- /dev/null +++ b/Features/customquery-carinsurance/workflows/src/main/java/net/corda/samples/carinsurance/flows/ClaimInfo.java @@ -0,0 +1,28 @@ +package net.corda.samples.carinsurance.flows; + +import net.corda.core.serialization.CordaSerializable; + +@CordaSerializable +public class ClaimInfo { + private final String claimNumber; + private final String claimDescription; + private final int claimAmount; + + public ClaimInfo(String claimNumber, String claimDescription, int claimAmount) { + this.claimNumber = claimNumber; + this.claimDescription = claimDescription; + this.claimAmount = claimAmount; + } + + public String getClaimNumber() { + return claimNumber; + } + + public String getClaimDescription() { + return claimDescription; + } + + public int getClaimAmount() { + return claimAmount; + } +} diff --git a/Features/customquery-carinsurance/workflows/src/main/java/net/corda/samples/carinsurance/flows/InsuranceClaimFlow.java b/Features/customquery-carinsurance/workflows/src/main/java/net/corda/samples/carinsurance/flows/InsuranceClaimFlow.java new file mode 100644 index 00000000..d0c2d034 --- /dev/null +++ b/Features/customquery-carinsurance/workflows/src/main/java/net/corda/samples/carinsurance/flows/InsuranceClaimFlow.java @@ -0,0 +1,122 @@ +package net.corda.samples.carinsurance.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.google.common.collect.ImmutableList; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.flows.*; +import net.corda.core.internal.FetchDataFlow; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.Builder; +import net.corda.core.node.services.vault.CriteriaExpression; +import net.corda.core.node.services.vault.FieldInfo; +import net.corda.core.node.services.vault.PageSpecification; +import net.corda.core.node.services.vault.Sort; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.carinsurance.contracts.InsuranceContract; +import net.corda.samples.carinsurance.schema.InsuranceSchemaV1; +import net.corda.samples.carinsurance.schema.PersistentInsurance; +import net.corda.samples.carinsurance.states.Claim; +import net.corda.samples.carinsurance.states.InsuranceState; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Collections; + +public class InsuranceClaimFlow { + + private InsuranceClaimFlow(){} + + @InitiatingFlow + @StartableByRPC + public static class InsuranceClaimInitiator extends FlowLogic{ + + private final ClaimInfo claimInfo; + private final String policyNumber; + + public InsuranceClaimInitiator(ClaimInfo claimInfo, String policyNumber) { + this.claimInfo = claimInfo; + this.policyNumber = policyNumber; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException{ + /************************************************************************************* + * This section is the custom query code. Instead of query out all the insurance state and filter by their policy number, + * custom query will only retrieve the insurance state that matched the policy number. The filtering will happen behind the scene. + * **/ + Field valueField = null; + try{ + valueField = PersistentInsurance.class.getDeclaredField("policyNumber"); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + QueryCriteria insuranceQuery = new QueryCriteria.VaultCustomQueryCriteria(Builder.equal(valueField, policyNumber)); + /** And you can have joint custom criteria as well. Simply add additional criteria and add it to the criteria object by using and(). + * QueryCriteria insuranceQuery = new QueryCriteria.VaultCustomQueryCriteria(Builder.equal(valueField, insuredValue)); + * **/ + QueryCriteria generalCriteria = new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED).and(insuranceQuery); + Vault.Page insuranceStateAndRefs = getServiceHub().getVaultService().queryBy(InsuranceState.class, generalCriteria ); + /***************************************************************************************/ + + if(insuranceStateAndRefs.getStates().isEmpty()){ + throw new IllegalArgumentException("Policy not found"); + } + + StateAndRef inputStateAndRef = insuranceStateAndRefs.getStates().get(0); + Claim claim = new Claim(claimInfo.getClaimNumber(), claimInfo.getClaimDescription(), + claimInfo.getClaimAmount()); + InsuranceState input = inputStateAndRef.getState().getData(); + + List claims = new ArrayList<>(); + if(input.getClaims() == null || input.getClaims().size() == 0 ){ + claims.add(claim); + }else { + claims.addAll(input.getClaims()); + claims.add(claim); + } + + //Create the output state + InsuranceState output = new InsuranceState(input.getPolicyNumber(), input.getInsuredValue(), + input.getDuration(), input.getPremium(), input.getInsurer(), input.getInsuree(), + input.getVehicleDetail(), claims); + + // Build the transaction. + TransactionBuilder transactionBuilder = new TransactionBuilder(inputStateAndRef.getState().getNotary()) + .addInputState(inputStateAndRef) + .addOutputState(output, InsuranceContract.ID) + .addCommand(new InsuranceContract.Commands.AddClaim(), ImmutableList.of(getOurIdentity().getOwningKey())); + + // Verify the transaction + transactionBuilder.verify(getServiceHub()); + + // Sign the transaction + SignedTransaction signedTransaction = getServiceHub().signInitialTransaction(transactionBuilder); + + // Call finality Flow + FlowSession counterpartySession = initiateFlow(input.getInsuree()); + return subFlow(new FinalityFlow(signedTransaction, ImmutableList.of(counterpartySession))); + + } + } + + + @InitiatedBy(InsuranceClaimInitiator.class) + public static class InsuranceClaimResponder extends FlowLogic { + + private FlowSession counterpartySession; + + public InsuranceClaimResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + return subFlow(new ReceiveFinalityFlow(counterpartySession)); + } + } +} diff --git a/Features/customquery-carinsurance/workflows/src/main/java/net/corda/samples/carinsurance/flows/InsuranceInfo.java b/Features/customquery-carinsurance/workflows/src/main/java/net/corda/samples/carinsurance/flows/InsuranceInfo.java new file mode 100644 index 00000000..bf5dfd6e --- /dev/null +++ b/Features/customquery-carinsurance/workflows/src/main/java/net/corda/samples/carinsurance/flows/InsuranceInfo.java @@ -0,0 +1,42 @@ +package net.corda.samples.carinsurance.flows; + +import net.corda.core.serialization.CordaSerializable; + +@CordaSerializable +public class InsuranceInfo { + + private final VehicleInfo vehicleInfo; + + private final String policyNumber; + private final long insuredValue; + private final int duration; + private final int premium; + + public InsuranceInfo(String policyNumber, long insuredValue, int duration, int premium, VehicleInfo vehicleInfo) { + this.policyNumber = policyNumber; + this.insuredValue = insuredValue; + this.duration = duration; + this.premium = premium; + this.vehicleInfo = vehicleInfo; + } + + public String getPolicyNumber() { + return policyNumber; + } + + public long getInsuredValue() { + return insuredValue; + } + + public int getDuration() { + return duration; + } + + public int getPremium() { + return premium; + } + + public VehicleInfo getVehicleInfo() { + return vehicleInfo; + } +} diff --git a/Features/customquery-carinsurance/workflows/src/main/java/net/corda/samples/carinsurance/flows/IssueInsuranceFlow.java b/Features/customquery-carinsurance/workflows/src/main/java/net/corda/samples/carinsurance/flows/IssueInsuranceFlow.java new file mode 100644 index 00000000..1ac0c4b7 --- /dev/null +++ b/Features/customquery-carinsurance/workflows/src/main/java/net/corda/samples/carinsurance/flows/IssueInsuranceFlow.java @@ -0,0 +1,89 @@ +package net.corda.samples.carinsurance.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.google.common.collect.ImmutableList; +import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.core.utilities.ProgressTracker; +import net.corda.samples.carinsurance.contracts.InsuranceContract; +import net.corda.samples.carinsurance.states.InsuranceState; +import net.corda.samples.carinsurance.states.VehicleDetail; + +public class IssueInsuranceFlow { + + private IssueInsuranceFlow(){} + + @InitiatingFlow + @StartableByRPC + public static class IssueInsuranceInitiator extends FlowLogic { + + private final ProgressTracker progressTracker = new ProgressTracker(); + + private final InsuranceInfo insuranceInfo; + private final Party insuree; + + public IssueInsuranceInitiator(InsuranceInfo insuranceInfo, Party insuree) { + this.insuranceInfo = insuranceInfo; + this.insuree = insuree; + } + + @Override + public ProgressTracker getProgressTracker() { + return progressTracker; + } + + @Suspendable + @Override + public SignedTransaction call() throws FlowException { + + // Obtain a reference to a notary we wish to use. + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + Party insurer = getOurIdentity(); + + VehicleInfo vehicleInfo = insuranceInfo.getVehicleInfo(); + VehicleDetail vehicleDetail = new VehicleDetail(vehicleInfo.getRegistrationNumber(), + vehicleInfo.getChasisNumber(), vehicleInfo.getMake(), vehicleInfo.getModel(), + vehicleInfo.getVariant(), vehicleInfo.getColor(), vehicleInfo.getFuelType()); + + // Build the insurance output state. + InsuranceState insurance = new InsuranceState(insuranceInfo.getPolicyNumber(), insuranceInfo.getInsuredValue(), + insuranceInfo.getDuration(), insuranceInfo.getPremium(), insurer, insuree, vehicleDetail, + null); + + // Build the transaction + TransactionBuilder builder = new TransactionBuilder(notary) + .addOutputState(insurance, InsuranceContract.ID) + .addCommand(new InsuranceContract.Commands.IssueInsurance(), ImmutableList.of(insurer.getOwningKey())); + + // Verify the transaction + builder.verify(getServiceHub()); + + // Sign the transaction + SignedTransaction selfSignedTransaction = getServiceHub().signInitialTransaction(builder); + + // Call finality Flow + FlowSession ownerSession = initiateFlow(insuree); + return subFlow(new FinalityFlow(selfSignedTransaction, ImmutableList.of(ownerSession))); + } + } + + @InitiatedBy(IssueInsuranceInitiator.class) + public static class IssueInsuranceResponder extends FlowLogic { + + private FlowSession counterpartySession; + + public IssueInsuranceResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + return subFlow(new ReceiveFinalityFlow(counterpartySession)); + } + } +} diff --git a/Features/customquery-carinsurance/workflows/src/main/java/net/corda/samples/carinsurance/flows/VehicleInfo.java b/Features/customquery-carinsurance/workflows/src/main/java/net/corda/samples/carinsurance/flows/VehicleInfo.java new file mode 100644 index 00000000..795281d7 --- /dev/null +++ b/Features/customquery-carinsurance/workflows/src/main/java/net/corda/samples/carinsurance/flows/VehicleInfo.java @@ -0,0 +1,55 @@ +package net.corda.samples.carinsurance.flows; + +import net.corda.core.serialization.CordaSerializable; + +@CordaSerializable +public class VehicleInfo { + + private final String registrationNumber; + private final String chasisNumber; + private final String make; + private final String model; + private final String variant; + private final String color; + private final String fuelType; + + public VehicleInfo(String registrationNumber, String chasisNumber, String make, String model, String variant, + String color, String fuelType) { + this.registrationNumber = registrationNumber; + this.chasisNumber = chasisNumber; + this.make = make; + this.model = model; + this.variant = variant; + this.color = color; + this.fuelType = fuelType; + } + + public String getRegistrationNumber() { + return registrationNumber; + } + + public String getChasisNumber() { + return chasisNumber; + } + + public String getMake() { + return make; + } + + public String getModel() { + return model; + } + + public String getVariant() { + return variant; + } + + public String getColor() { + return color; + } + + public String getFuelType() { + return fuelType; + } + +} diff --git a/Features/customquery-carinsurance/workflows/src/main/resources/migration/claim-detail.changelog-v1.xml b/Features/customquery-carinsurance/workflows/src/main/resources/migration/claim-detail.changelog-v1.xml new file mode 100644 index 00000000..936301ea --- /dev/null +++ b/Features/customquery-carinsurance/workflows/src/main/resources/migration/claim-detail.changelog-v1.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Features/customquery-carinsurance/workflows/src/main/resources/migration/insurance-detail.changelog-v1.xml b/Features/customquery-carinsurance/workflows/src/main/resources/migration/insurance-detail.changelog-v1.xml new file mode 100644 index 00000000..64886826 --- /dev/null +++ b/Features/customquery-carinsurance/workflows/src/main/resources/migration/insurance-detail.changelog-v1.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Features/customquery-carinsurance/workflows/src/main/resources/migration/insurance.changelog-master.xml b/Features/customquery-carinsurance/workflows/src/main/resources/migration/insurance.changelog-master.xml new file mode 100644 index 00000000..980ada42 --- /dev/null +++ b/Features/customquery-carinsurance/workflows/src/main/resources/migration/insurance.changelog-master.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/Features/customquery-carinsurance/workflows/src/main/resources/migration/vehicle-detail.changelog-v1.xml b/Features/customquery-carinsurance/workflows/src/main/resources/migration/vehicle-detail.changelog-v1.xml new file mode 100644 index 00000000..101fe505 --- /dev/null +++ b/Features/customquery-carinsurance/workflows/src/main/resources/migration/vehicle-detail.changelog-v1.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Features/customquery-carinsurance/workflows/src/test/java/net/corda/samples/carinsurance/flows/FlowTests.java b/Features/customquery-carinsurance/workflows/src/test/java/net/corda/samples/carinsurance/flows/FlowTests.java new file mode 100644 index 00000000..4afaa4e3 --- /dev/null +++ b/Features/customquery-carinsurance/workflows/src/test/java/net/corda/samples/carinsurance/flows/FlowTests.java @@ -0,0 +1,97 @@ +package net.corda.samples.carinsurance.flows; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.transactions.SignedTransaction; +import net.corda.samples.carinsurance.states.InsuranceState; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import static org.junit.Assert.*; + +import static org.junit.Assert.assertEquals; + +public class FlowTests { + private final MockNetwork network = new MockNetwork(new MockNetworkParameters(ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.carinsurance.contracts"), + TestCordapp.findCordapp("net.corda.samples.carinsurance.flows") + )).withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); + private final StartedMockNode a = network.createNode(); + private final StartedMockNode b = network.createNode(); + + + @Before + public void setup() { + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + //simple example test to test if the issue insurance flow only carries one output. + @Test + public void issueInsuranceFlowTest() throws Exception { + VehicleInfo car = new VehicleInfo( + "I4U64FY56I48Y", + "165421658465465", + "BMW", + "M3", + "MPower", + "Black", + "gas"); + InsuranceInfo policy1 = new InsuranceInfo( + "8742", + 2000, + 18, + 49, + car); + + IssueInsuranceFlow.IssueInsuranceInitiator flow = new IssueInsuranceFlow.IssueInsuranceInitiator(policy1, b.getInfo().getLegalIdentities().get(0)); + CordaFuture future = a.startFlow(flow); + network.runNetwork(); + SignedTransaction ptx = future.get(); + + //assertion for single output + assertEquals(1, ptx.getTx().getOutputStates().size()); + + } + + @Test + public void issueInsuranceClaimFlowTest() throws Exception{ + VehicleInfo car = new VehicleInfo( + "I4U64FY56I48Y", + "165421658465465", + "BMW", + "M3", + "MPower", + "Black", + "gas"); + InsuranceInfo policy1 = new InsuranceInfo( + "8742", + 2000, + 18, + 49, + car); + + IssueInsuranceFlow.IssueInsuranceInitiator flow = new IssueInsuranceFlow.IssueInsuranceInitiator(policy1, b.getInfo().getLegalIdentities().get(0)); + CordaFuture future = a.startFlow(flow); + network.runNetwork(); + SignedTransaction ptx = future.get(); + + ClaimInfo claimInfo = new ClaimInfo("C001", "Minor Accident, Bumper Damaged", 1000); + String policyNumber = "8742"; + InsuranceClaimFlow.InsuranceClaimInitiator flow2 = new InsuranceClaimFlow.InsuranceClaimInitiator(claimInfo, policyNumber ); + CordaFuture future2 = a.startFlow(flow2); + network.runNetwork(); + SignedTransaction ptx2 = future2.get(); + + //assertion for single output + InsuranceState inState = (InsuranceState) ptx2.getTx().getOutput(0); + assertNotNull(inState); + } +} diff --git a/Features/customquery-carinsurance/workflows/src/test/java/net/corda/samples/carinsurance/flows/NodeDriver.java b/Features/customquery-carinsurance/workflows/src/test/java/net/corda/samples/carinsurance/flows/NodeDriver.java new file mode 100644 index 00000000..5e0a1eb5 --- /dev/null +++ b/Features/customquery-carinsurance/workflows/src/test/java/net/corda/samples/carinsurance/flows/NodeDriver.java @@ -0,0 +1,39 @@ +package net.corda.samples.carinsurance.flows; + +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/Features/dockerform-yocordapp/LICENCE b/Features/dockerform-yocordapp/LICENCE new file mode 100644 index 00000000..3ff572d1 --- /dev/null +++ b/Features/dockerform-yocordapp/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/Features/dockerform-yocordapp/README.md b/Features/dockerform-yocordapp/README.md new file mode 100644 index 00000000..37a14d38 --- /dev/null +++ b/Features/dockerform-yocordapp/README.md @@ -0,0 +1,79 @@ +# dockerform-yocordapp + +This time we've taken the original yo CorDapp and modified it to demonstrate an example of how you can use dockerForm to bootstrap a corda network on a single machine. + +For the purposes of this example, we'll use the yo CorDapp as a base to create a clear example for how to use the dockerForm gradle build task in a normal CorDapp setup. + +> Note: This is generally intended to be used on localhost. + + +## Concepts + +In the original yo application, the app sent what is essentially a nudge from one endpoint and another. + +In Corda, we can use abstractions to accomplish the same thing. + + +We define a state (the yo to be shared), define a contract (the way to make sure the yo is legit), and define the flow (the control flow of our CorDapp). + + +## Usage + +### Quick Start with Docker + +If you have docker installed you can use our gradle tasks to generate a valid docker compose file for your node configuration. + +```bash +# generate the docker-compose file +./gradlew prepareDockerNodes + +# run our corda network +docker-compose -f ./build/nodes/docker-compose.yml up +``` + +#### Sending a Yo + +We will interact with the nodes via their specific shells. When the nodes are up and running, use the following command to send a Yo to another node: + +```sh +# find the ssh port for PartyA using docker ps +ssh user1@0.0.0.0 -p 2222 + +# the password defined in the node config for PartyA is "test" +Password: test + + +Welcome to the Corda interactive shell. +You can see the available commands by typing 'help'. + +# you'll see the corda shell available and can run flows +Fri May 15 18:23:03 GMT 2020>>> flow start YoFlow target: PartyB + + ✓ Starting + ✓ Creating a new Yo! + ✓ Signing the Yo! + ✓ Verfiying the Yo! + ✓ Sending the Yo! + Requesting signature by notary service + Requesting signature by Notary service + Validating response from Notary service + ✓ Broadcasting transaction to participants +▶︎ Done +Flow completed with result: SignedTransaction(id=3F92F41B699719B2CDE578959BB09F50D3D4F5D51A496DEAB67E438B2614F48C) +``` + +Once this runs on your machine you've got everything you would need to run corda for development using docker! + +###### Note you can't send a Yo! to yourself because that's not cool! + +To see all the Yo's other nodes have sent you in your vault you can run a vault query from the Corda shell: + +```bash +run vaultQuery contractStateType: net.corda.samples.dockerform.states.YoState +``` + +As a quick note you can shut down your docker containers with the following command + +```bash +docker-compose -f ./build/nodes/docker-compose.yml stop +``` diff --git a/Features/dockerform-yocordapp/TRADEMARK b/Features/dockerform-yocordapp/TRADEMARK new file mode 100644 index 00000000..aa7e20f6 --- /dev/null +++ b/Features/dockerform-yocordapp/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/. diff --git a/Features/dockerform-yocordapp/build.gradle b/Features/dockerform-yocordapp/build.gradle new file mode 100644 index 00000000..c8a78c4b --- /dev/null +++ b/Features/dockerform-yocordapp/build.gradle @@ -0,0 +1,143 @@ +buildscript { + Properties constants = new Properties() + file("$projectDir/../constants.properties").withInputStream { constants.load(it) } + + ext { + corda_release_group = constants.getProperty("cordaReleaseGroup") + corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") + corda_release_version = constants.getProperty("cordaVersion") + corda_core_release_version = constants.getProperty("cordaCoreVersion") + 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() + } + + repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://download.corda.net/maven/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" + } +} + +allprojects { + apply from: "${rootProject.projectDir}/repositories.gradle" + apply plugin: 'java' + + repositories { + mavenLocal() + + mavenCentral() + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://jitpack.io' } + } + + tasks.withType(JavaCompile) { + options.compilerArgs << "-parameters" // Required for shell commands. + } + + jar { + // This makes the JAR's SHA-256 hash repeatable. + preserveFileTimestamps = false + reproducibleFileOrder = true + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} + +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 { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaCompile "$corda_release_group:corda-node-api:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + + // CorDapp dependencies. + cordapp project("contracts") + cordapp project("workflows") + + 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" + cordaDriver "net.corda:corda-shell:4.10" + +} + +cordapp { + info { + name "DockerForm Yo Cordapp" + vendor "Corda Open Source" + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + } +} + +task prepareDockerNodes(type: net.corda.plugins.Dockerform, dependsOn: ['jar']) { + + dockerImage = "corda/community:4.9.3-zulu-openjdk8" + + nodeDefaults { + projectCordapp { + deploy = false + } + cordapp project("contracts") + cordapp project("workflows") + rpcUsers = [[user: "user1", "password": "test", "permissions": ["ALL"]]] + } + + node { + name "O=Notary,L=London,C=GB" + notary = [validating: false] + p2pPort 10001 + p2pAddress "0.0.0.0" + rpcSettings { + address("0.0.0.0:10011") + adminAddress("0.0.0.0:10041") + } + sshdPort 2221 + } + + node { + name "O=PartyA,L=London,C=GB" + p2pPort 10002 + p2pAddress "0.0.0.0" + rpcSettings { + address("0.0.0.0:10012") + adminAddress("0.0.0.0:10042") + } + sshdPort 2222 + } + + node { + name "O=PartyB,L=New York,C=US" + p2pPort 10003 + p2pAddress "0.0.0.0" + rpcSettings { + address("0.0.0.0:10013") + adminAddress("0.0.0.0:10043") + } + sshdPort 2223 + } +} + + diff --git a/Features/dockerform-yocordapp/config/dev/log4j2.xml b/Features/dockerform-yocordapp/config/dev/log4j2.xml new file mode 100644 index 00000000..34ba4d45 --- /dev/null +++ b/Features/dockerform-yocordapp/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/Features/dockerform-yocordapp/config/test/log4j2.xml b/Features/dockerform-yocordapp/config/test/log4j2.xml new file mode 100644 index 00000000..cd9926ca --- /dev/null +++ b/Features/dockerform-yocordapp/config/test/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n + > + + + + + + + + + + + + diff --git a/Features/dockerform-yocordapp/contracts/build.gradle b/Features/dockerform-yocordapp/contracts/build.gradle new file mode 100644 index 00000000..ff6fbdb0 --- /dev/null +++ b/Features/dockerform-yocordapp/contracts/build.gradle @@ -0,0 +1,18 @@ +apply plugin: 'net.corda.plugins.cordapp' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + contract { + name "DockerForm Yo Cordapp" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_release_group:corda-core:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" +} diff --git a/Features/dockerform-yocordapp/contracts/src/main/java/net/corda/samples/dockerform/contracts/YoContract.java b/Features/dockerform-yocordapp/contracts/src/main/java/net/corda/samples/dockerform/contracts/YoContract.java new file mode 100644 index 00000000..daa09f9c --- /dev/null +++ b/Features/dockerform-yocordapp/contracts/src/main/java/net/corda/samples/dockerform/contracts/YoContract.java @@ -0,0 +1,42 @@ +package net.corda.samples.dockerform.contracts; + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.CommandWithParties; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import net.corda.samples.dockerform.states.YoState; +import org.jetbrains.annotations.NotNull; + +import static net.corda.core.contracts.ContractsDSL.requireSingleCommand; +import static net.corda.core.contracts.ContractsDSL.requireThat; + +// ************ +// * Contract * +// ************ +// Contract and state. +public class YoContract implements Contract { + // Used to identify our contract when building a transaction. + public static final String ID = "net.corda.samples.dockerform.contracts.YoContract"; + + // Contract code. + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + + CommandWithParties command = requireSingleCommand(tx.getCommands(), Commands.Send.class); + + requireThat(req -> { + req.using("There can be no inputs when Yo'ing other parties", tx.getInputs().isEmpty()); + req.using("There must be one output: The Yo!", tx.getOutputs().size() == 1); + YoState yo = tx.outputsOfType(YoState.class).get(0); + req.using("No sending Yo's to yourself!", !yo.getTarget().equals(yo.getOrigin())); + req.using("The Yo! must be signed by the sender.", command.getSigners().contains(yo.getOrigin().getOwningKey())); + return null; + }); + } + + // Used to indicate the transaction's intent. + public interface Commands extends CommandData { + class Send implements Commands { + } + } +} diff --git a/Features/dockerform-yocordapp/contracts/src/main/java/net/corda/samples/dockerform/states/YoState.java b/Features/dockerform-yocordapp/contracts/src/main/java/net/corda/samples/dockerform/states/YoState.java new file mode 100644 index 00000000..4107cf54 --- /dev/null +++ b/Features/dockerform-yocordapp/contracts/src/main/java/net/corda/samples/dockerform/states/YoState.java @@ -0,0 +1,58 @@ +package net.corda.samples.dockerform.states; + +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 net.corda.core.serialization.ConstructorForDeserialization; +import net.corda.samples.dockerform.contracts.YoContract; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; + +// ********* +// * State * +// ********* +@BelongsToContract(YoContract.class) +public class YoState implements ContractState { + private final Party origin; + private final Party target; + private final String yo; + + @ConstructorForDeserialization + public YoState(Party origin, Party target, String yo) { + this.origin = origin; + this.target = target; + this.yo = yo; + } + + public YoState(Party origin, Party target) { + this.origin = origin; + this.target = target; + this.yo = "Yo!"; + } + + public Party getOrigin() { + return this.origin; + } + + public Party getTarget() { + return this.target; + } + + public String getYo() { + return this.yo; + } + + @NotNull + @Override + public List getParticipants() { + return Collections.singletonList(this.target); + } + + @Override + public String toString() { + return this.origin.getName() + ": " + this.yo; + } +} diff --git a/Features/dockerform-yocordapp/gradle.properties b/Features/dockerform-yocordapp/gradle.properties new file mode 100644 index 00000000..5ad92e79 --- /dev/null +++ b/Features/dockerform-yocordapp/gradle.properties @@ -0,0 +1,3 @@ +name=DockerForm Yo Cordapp +group=net.corda.samples.dockerform +version=0.1 diff --git a/Features/dockerform-yocordapp/gradle/wrapper/gradle-wrapper.jar b/Features/dockerform-yocordapp/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..99340b4a Binary files /dev/null and b/Features/dockerform-yocordapp/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Features/dockerform-yocordapp/gradle/wrapper/gradle-wrapper.properties b/Features/dockerform-yocordapp/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..ae01072d --- /dev/null +++ b/Features/dockerform-yocordapp/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-5.6.4-all.zip diff --git a/Features/dockerform-yocordapp/gradlew b/Features/dockerform-yocordapp/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/Features/dockerform-yocordapp/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/Features/dockerform-yocordapp/gradlew.bat b/Features/dockerform-yocordapp/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/Features/dockerform-yocordapp/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/Features/dockerform-yocordapp/repositories.gradle b/Features/dockerform-yocordapp/repositories.gradle new file mode 100644 index 00000000..7adba3a3 --- /dev/null +++ b/Features/dockerform-yocordapp/repositories.gradle @@ -0,0 +1,8 @@ +repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://jitpack.io' } + maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } +} diff --git a/Features/dockerform-yocordapp/settings.gradle b/Features/dockerform-yocordapp/settings.gradle new file mode 100644 index 00000000..0473ad43 --- /dev/null +++ b/Features/dockerform-yocordapp/settings.gradle @@ -0,0 +1,2 @@ +include 'workflows' +include 'contracts' diff --git a/Features/dockerform-yocordapp/workflows/build.gradle b/Features/dockerform-yocordapp/workflows/build.gradle new file mode 100644 index 00000000..ba3b227d --- /dev/null +++ b/Features/dockerform-yocordapp/workflows/build.gradle @@ -0,0 +1,59 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + workflow { + name "DockerForm Yo Cordapp" + 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 { + 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(":contracts") +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} diff --git a/Features/dockerform-yocordapp/workflows/src/integrationTest/java/net/corda/samples/dockerform/DriverBasedTest.java b/Features/dockerform-yocordapp/workflows/src/integrationTest/java/net/corda/samples/dockerform/DriverBasedTest.java new file mode 100644 index 00000000..e3fa9370 --- /dev/null +++ b/Features/dockerform-yocordapp/workflows/src/integrationTest/java/net/corda/samples/dockerform/DriverBasedTest.java @@ -0,0 +1,43 @@ +package net.corda.samples.dockerform; + +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.driver.Driver; +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.Objects; +import java.util.concurrent.ExecutionException; + +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.driver(new DriverParameters().withStartNodesInProcess(true).withIsDebug(true), driverDSL -> { + NodeHandle partyAHandle = null; + NodeHandle partyBHandle = null; + try { + // Start a pair of nodes and wait for them both to be ready. + partyAHandle = driverDSL.startNode(new NodeParameters().withProvidedName(bankA.getName())).get(); + partyBHandle = driverDSL.startNode(new NodeParameters().withProvidedName(bankB.getName())).get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + + // 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. + assert partyAHandle != null; + assert partyBHandle != null; + assert (bankB.getName().equals(Objects.requireNonNull(partyAHandle.getRpc().wellKnownPartyFromX500Name(bankB.getName())).getName())); + assert (bankA.getName().equals(Objects.requireNonNull(partyBHandle.getRpc().wellKnownPartyFromX500Name(bankA.getName())).getName())); + return null; + }); + } +} diff --git a/Features/dockerform-yocordapp/workflows/src/main/java/net/corda/samples/dockerform/flows/YoFlow.java b/Features/dockerform-yocordapp/workflows/src/main/java/net/corda/samples/dockerform/flows/YoFlow.java new file mode 100644 index 00000000..6d037021 --- /dev/null +++ b/Features/dockerform-yocordapp/workflows/src/main/java/net/corda/samples/dockerform/flows/YoFlow.java @@ -0,0 +1,82 @@ +package net.corda.samples.dockerform.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.Command; +import net.corda.core.contracts.StateAndContract; +import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.core.utilities.ProgressTracker; +import net.corda.samples.dockerform.contracts.YoContract; +import net.corda.samples.dockerform.states.YoState; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Objects; + + +// ********* +// * Flows * +// ********* +@InitiatingFlow +@StartableByRPC +public class YoFlow extends FlowLogic { + private static final ProgressTracker.Step CREATING = new ProgressTracker.Step("Creating a new Yo!"); + private static final ProgressTracker.Step SIGNING = new ProgressTracker.Step("Signing the Yo!"); + private static final ProgressTracker.Step VERIFYING = new ProgressTracker.Step("Verfiying the Yo!"); + private static final ProgressTracker.Step FINALISING = new ProgressTracker.Step("Sending the Yo!") { + @Nullable + @Override + public ProgressTracker childProgressTracker() { + return FinalityFlow.tracker(); + } + }; + + ProgressTracker progressTracker = new ProgressTracker( + CREATING, + SIGNING, + VERIFYING, + FINALISING + ); + + @Nullable + @Override + public ProgressTracker getProgressTracker() { + return this.progressTracker; + } + + private final Party target; + + public YoFlow(Party target) { + this.target = target; + } + + @Suspendable + @Override + public SignedTransaction call() throws FlowException { + this.progressTracker.setCurrentStep(CREATING); + + Party me = getOurIdentity(); + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + Command command = new Command(new YoContract.Commands.Send(), Arrays.asList(me.getOwningKey())); + YoState state = new YoState(me, this.target); + StateAndContract stateAndContract = new StateAndContract(state, YoContract.ID); + TransactionBuilder utx = new TransactionBuilder(notary).withItems(stateAndContract, command); + + this.progressTracker.setCurrentStep(VERIFYING); + utx.verify(getServiceHub()); + + this.progressTracker.setCurrentStep(SIGNING); + SignedTransaction stx = getServiceHub().signInitialTransaction(utx); + + this.progressTracker.setCurrentStep(FINALISING); + FlowSession targetSession = initiateFlow(this.target); + return subFlow(new FinalityFlow(stx, Collections.singletonList(targetSession), Objects.requireNonNull(FINALISING.childProgressTracker()))); + } +} diff --git a/Features/dockerform-yocordapp/workflows/src/main/java/net/corda/samples/dockerform/flows/YoFlowResponder.java b/Features/dockerform-yocordapp/workflows/src/main/java/net/corda/samples/dockerform/flows/YoFlowResponder.java new file mode 100644 index 00000000..11c02a54 --- /dev/null +++ b/Features/dockerform-yocordapp/workflows/src/main/java/net/corda/samples/dockerform/flows/YoFlowResponder.java @@ -0,0 +1,20 @@ +package net.corda.samples.dockerform.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.flows.*; +import net.corda.core.transactions.SignedTransaction; + +@InitiatedBy(YoFlow.class) +public class YoFlowResponder extends FlowLogic { + private final FlowSession counterpartySession; + + public YoFlowResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public SignedTransaction call() throws FlowException { + return subFlow(new ReceiveFinalityFlow(this.counterpartySession)); + } +} diff --git a/Features/dockerform-yocordapp/workflows/src/test/java/net/corda/samples/dockerform/flows/FlowTests.java b/Features/dockerform-yocordapp/workflows/src/test/java/net/corda/samples/dockerform/flows/FlowTests.java new file mode 100644 index 00000000..0973b878 --- /dev/null +++ b/Features/dockerform-yocordapp/workflows/src/test/java/net/corda/samples/dockerform/flows/FlowTests.java @@ -0,0 +1,47 @@ +package net.corda.samples.dockerform.flows; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.transactions.SignedTransaction; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.ExecutionException; + +public class FlowTests { + private final MockNetwork network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.dockerform.contracts"), + TestCordapp.findCordapp("net.corda.samples.dockerform.flows") + )).withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB"))))); + private final StartedMockNode a = this.network.createNode(); + private final StartedMockNode b = this.network.createNode(); + + public FlowTests() { + ImmutableList.of(this.a, this.b).forEach(it -> { + it.registerInitiatedFlow(YoFlowResponder.class); + }); + } + + @Before + public void setup() { + this.network.runNetwork(); + } + + @After + public void tearDown() { + this.network.stopNodes(); + } + + //The yo flow should not have any input + //This test will check if the input list is empty + @Test + public void dummyTest() throws ExecutionException, InterruptedException { + CordaFuture future = this.a.startFlow(new YoFlow(this.b.getInfo().getLegalIdentities().get(0))); + this.network.runNetwork(); + SignedTransaction ptx = future.get(); + assert (ptx.getTx().getInputs().isEmpty()); + } +} diff --git a/Features/dockerform-yocordapp/workflows/src/test/java/net/corda/samples/dockerform/flows/NodeDriver.java b/Features/dockerform-yocordapp/workflows/src/test/java/net/corda/samples/dockerform/flows/NodeDriver.java new file mode 100644 index 00000000..25e868e5 --- /dev/null +++ b/Features/dockerform-yocordapp/workflows/src/test/java/net/corda/samples/dockerform/flows/NodeDriver.java @@ -0,0 +1,32 @@ +package net.corda.samples.dockerform.flows; + +import com.google.common.collect.ImmutableList; +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.driver.Driver; +import net.corda.testing.driver.DriverParameters; +import net.corda.testing.driver.NodeParameters; +import net.corda.testing.node.User; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; + +/** + * 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) { + List rpcUsers = ImmutableList.of(new User("user1", "test", Collections.singleton("ALL"))); + + Driver.driver(new DriverParameters().withStartNodesInProcess(true).withWaitForAllNodesToFinish(true), driverDSL -> { + try { + driverDSL.startNode(new NodeParameters().withProvidedName(new CordaX500Name("PartyA", "London", "GB")).withRpcUsers(rpcUsers)).get(); + driverDSL.startNode(new NodeParameters().withProvidedName(new CordaX500Name("PartyB", "New York", "US")).withRpcUsers(rpcUsers)).get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + return null; + }); + } +} diff --git a/Features/encumbrance-avatar/LICENCE b/Features/encumbrance-avatar/LICENCE new file mode 100644 index 00000000..3ff572d1 --- /dev/null +++ b/Features/encumbrance-avatar/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/Features/encumbrance-avatar/README.md b/Features/encumbrance-avatar/README.md new file mode 100644 index 00000000..7310f8b0 --- /dev/null +++ b/Features/encumbrance-avatar/README.md @@ -0,0 +1,67 @@ +

+ Corda +

+ +# Corda encumbrance sample + +Corda supports the idea of "Linked States", using the TransactionState.encumbrance property. When building a transaction, a state x can +point to other state y by specifying the index of y's state in the transaction output index. +In this situation x is linked to y, i.e. x is dependent on y. x cannot be consumed unless you consume y. +Hence if you want to consume x, y should also be present in the input of this transaction. +Hence y's contract is also always run, when x is about to be consumed. +In this situation, x is the encumbered state, and y is the encumbrance. +At present, if you do not specify any encumbrance, it defaults to NULL. + +There are many use cases which can use encumbrance like - +1. Cross chain Atomic Swaps +2. Layer 2 games like https://github.com/akichidis/lightning-chess etc. + +## About this sample + +This is a basic sample which shows how you can use encumbrance in Corda. For this sample, we will have an Avatar +created on Corda. We will transfer this Avatar from one party to the other within a specified time limit. +After this time window, the Avatar will be expired, and you cannot transfer it to anyone. + +Avatar state is locked up by the Expiry state which suggests that the Avatar will expire after a certain time, +and cannot be transferred to anyone after that. + +This sample can be extended further, where the Avatar can be represented as a NFT using Corda's Token SDK, and +can be traded and purchased by a buyer on the exchange. The tokens can be locked up using an encumbrance before +performing the DVP for the NFT against the tokens. + +## Pre-Requisites +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.10/community/getting-set-up.html). + +## How to use run this sample + +Build the CorDapp using below command. This will deploy three nodes - buyer, seller and notary. + + ./gradlew clean deployNodes + ./build/nodes/runnodes + + +Create the Avatar on PartyA node + + start CreateAvatarFlow avatarId : PETER-7526, expiryAfterMinutes : 3 + +Sell the Avatar to PartyB node from PartyA node + + start TransferAvatarFlow avatarId : PETER-7526, buyer : PartyB + +Confirm if PartyB owns the Avatar + + run vaultQuery contractStateType : Avatar + +Note +As you can see in both the flows, Avatar is encumbered by Expiry. But Encumbrances should form a complete directed cycle, +otherwise one can spend the "encumbrance" (Expiry) state, which would freeze the "encumbered" (Avatar) state forever. +That's why we also make Expiry dependent on Avatar. (See how we have added encumbrance index's to the output states in +both the flows.) + +To read more about encumbrance visit the docs site. https://training.corda.net/corda-details/reference-states/#encumbrances + +## Reminder + +This project is open source under an Apache 2.0 licence. That means you +can submit PRs to fix bugs and add new features if they are not currently +available. \ No newline at end of file diff --git a/Features/encumbrance-avatar/TRADEMARK b/Features/encumbrance-avatar/TRADEMARK new file mode 100644 index 00000000..d2e056b5 --- /dev/null +++ b/Features/encumbrance-avatar/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/Features/encumbrance-avatar/build.gradle b/Features/encumbrance-avatar/build.gradle new file mode 100644 index 00000000..b7806c87 --- /dev/null +++ b/Features/encumbrance-avatar/build.gradle @@ -0,0 +1,130 @@ +buildscript { + ext { + corda_release_group = 'net.corda' + corda_core_release_group = '4.10' + corda_release_version = '4.10' + corda_core_release_version = '4.10' + 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.30' + log4j_version = '2.17.0' + corda_platform_version = '12' + } + + repositories { + mavenLocal() + mavenCentral() + maven { url 'https://download.corda.net/maven/corda-releases' } + maven { url 'https://jitpack.io' } + } + + 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" + } +} + +allprojects { + apply plugin: 'java' + + repositories { + mavenLocal() + + mavenCentral() + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://jitpack.io' } + maven { url "https://repo.gradle.org/gradle/libs-releases-local/" } + } + + 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(":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" + cordaDriver "net.corda:corda-shell:4.10" +} + +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + nodeDefaults { + projectCordapp { + deploy = false + } + + 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/Features/encumbrance-avatar/contracts/build.gradle b/Features/encumbrance-avatar/contracts/build.gradle new file mode 100644 index 00000000..06b41594 --- /dev/null +++ b/Features/encumbrance-avatar/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 "Avatar 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" + cordaCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + testCompile "junit:junit:$junit_version" +} \ No newline at end of file diff --git a/Features/encumbrance-avatar/contracts/src/main/java/net/corda/samples/avatar/contracts/AvatarContract.java b/Features/encumbrance-avatar/contracts/src/main/java/net/corda/samples/avatar/contracts/AvatarContract.java new file mode 100644 index 00000000..79a0829d --- /dev/null +++ b/Features/encumbrance-avatar/contracts/src/main/java/net/corda/samples/avatar/contracts/AvatarContract.java @@ -0,0 +1,70 @@ +package net.corda.samples.avatar.contracts; + +import net.corda.samples.avatar.states.Avatar; +import net.corda.samples.avatar.states.Expiry; +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.CommandWithParties; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import org.jetbrains.annotations.NotNull; + +import java.security.PublicKey; +import java.util.List; + +import static net.corda.core.contracts.ContractsDSL.requireSingleCommand; +import static net.corda.core.contracts.ContractsDSL.requireThat; + +public class AvatarContract implements Contract { + + public static final String AVATAR_CONTRACT_ID = "net.corda.samples.avatar.contracts.AvatarContract"; + + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + CommandWithParties commandWithParties = requireSingleCommand(tx.getCommands(), Commands.class); + Commands value = commandWithParties.getValue(); + List signers = commandWithParties.getSigners(); + + if (value instanceof Commands.Create) { + requireThat(require -> { + require.using("There should be 0 input states.", tx.getInputs().isEmpty()); + require.using("There should be 2 output states.", tx.getOutputStates().size() == 2); + require.using("There should be 1 expiry state.", tx.outputsOfType(Expiry.class).size() == 1); + require.using("There shoule be 1 Avatar created.", tx.outputsOfType(Avatar.class).size() == 1); + + Avatar avatar = tx.outputsOfType(Avatar.class).get(0); + require.using("Avatar Owner must always sign the newly created Avatar.", signers.contains(avatar.getOwner().getOwningKey())); + + Integer avatarEncumbrance = tx.getOutputs().stream().filter(o -> o.getData() instanceof Avatar).findFirst().get().getEncumbrance(); + require.using("Avatar needs to be encumbered", avatarEncumbrance != null); + + return null; + }); + } else if (value instanceof Commands.Transfer) { + requireThat(require -> { + require.using("There should be 2 inputs.", tx.getInputs().size() == 2); + require.using("There must be 1 expiry as an input.", tx.inputsOfType(Expiry.class).size() == 1); + require.using("There must be 1 avatar as an input", tx.inputsOfType(Avatar.class).size() == 1); + + require.using("There should be two output states", tx.getInputs().size() == 2); + require.using("There should be 1 expiry state.", tx.outputsOfType(Expiry.class).size() == 1); + require.using("There shoule be 1 Avatar created.", tx.outputsOfType(Avatar.class).size() == 1); + + Avatar newAvatar = tx.outputsOfType(Avatar.class).stream().findFirst().orElseThrow(() -> new IllegalArgumentException("No Avatar created for transferring.")); + Avatar oldAvatar = tx.inputsOfType(Avatar.class).stream().findFirst().orElseThrow(() -> new IllegalArgumentException("Existing Avatar to transfer not found.")); + + require.using("New and old Avatar must just have the owners changed.", newAvatar.equals(oldAvatar)); + require.using("New Owner should sign the new Avatar", signers.contains(newAvatar.getOwner().getOwningKey())); + require.using("Old owner must sign the old Avatar", signers.contains(oldAvatar.getOwner().getOwningKey())); + + return null; + }); + + } + } + + public interface Commands extends CommandData { + class Create implements Commands { } + + class Transfer implements Commands { } + } +} diff --git a/Features/encumbrance-avatar/contracts/src/main/java/net/corda/samples/avatar/contracts/ExpiryContract.java b/Features/encumbrance-avatar/contracts/src/main/java/net/corda/samples/avatar/contracts/ExpiryContract.java new file mode 100644 index 00000000..08934d37 --- /dev/null +++ b/Features/encumbrance-avatar/contracts/src/main/java/net/corda/samples/avatar/contracts/ExpiryContract.java @@ -0,0 +1,45 @@ +package net.corda.samples.avatar.contracts; + +import net.corda.samples.avatar.states.Expiry; +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.contracts.TimeWindow; +import net.corda.core.transactions.LedgerTransaction; +import org.jetbrains.annotations.NotNull; + +import java.time.LocalDateTime; +import java.time.ZoneId; + +//ExpiryContract is also run when Avatar's contract is run +public class ExpiryContract implements Contract { + + public static final String EXPIRY_CONTRACT_ID = "net.corda.samples.avatar.contracts.ExpiryContract"; + + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + Expiry expiry; + if (tx.getCommands().stream().anyMatch(e -> e.getValue() instanceof AvatarContract.Commands.Transfer)) + expiry = tx.inputsOfType(Expiry.class).get(0); + else + expiry = tx.outputsOfType(Expiry.class).get(0); + + TimeWindow timeWindow = tx.getTimeWindow(); + if (timeWindow == null || timeWindow.getUntilTime() == null) { + throw new IllegalArgumentException("Make sure you specify the time window for the Avatar transaction."); + } + + //Expiry time should be after the time window, if the avatar expires before the time window, then the avatar + //cannot be sold + if (timeWindow.getUntilTime().isAfter(expiry.getExpiry())) { + throw new IllegalArgumentException("Avatar transfer time has expired! Expiry date & time was: " + LocalDateTime.ofInstant(expiry.getExpiry(), ZoneId.systemDefault())); + } + } + + public interface Commands extends CommandData { + class Create implements Commands { + } + + class Pass implements Commands { + } + } +} diff --git a/Features/encumbrance-avatar/contracts/src/main/java/net/corda/samples/avatar/states/Avatar.java b/Features/encumbrance-avatar/contracts/src/main/java/net/corda/samples/avatar/states/Avatar.java new file mode 100644 index 00000000..3be69ba6 --- /dev/null +++ b/Features/encumbrance-avatar/contracts/src/main/java/net/corda/samples/avatar/states/Avatar.java @@ -0,0 +1,51 @@ +package net.corda.samples.avatar.states; + +import com.google.common.collect.ImmutableList; +import net.corda.samples.avatar.contracts.AvatarContract; +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.ContractState; +import net.corda.core.identity.AbstractParty; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Objects; + +//Avatar can be thought of as any metaverse avatar which needs to be created and sold on at an exchange. This entity +//has an id and owner associated with it. We will see how this avatar can only be sold within a certain time limit. +@BelongsToContract(AvatarContract.class) +public class Avatar implements ContractState { + private final AbstractParty owner; + private final String avatarId; + + public Avatar(AbstractParty owner, String avatarId) { + this.owner = owner; + this.avatarId = avatarId; + } + + public AbstractParty getOwner() { + return owner; + } + + public String getAvatarId() { + return avatarId; + } + + @NotNull + @Override + public List getParticipants() { + return ImmutableList.of(owner); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Avatar avatar = (Avatar) o; + return avatarId.equals(avatar.avatarId); + } + + @Override + public int hashCode() { + return Objects.hash(avatarId); + } +} diff --git a/Features/encumbrance-avatar/contracts/src/main/java/net/corda/samples/avatar/states/Expiry.java b/Features/encumbrance-avatar/contracts/src/main/java/net/corda/samples/avatar/states/Expiry.java new file mode 100644 index 00000000..d0f0e213 --- /dev/null +++ b/Features/encumbrance-avatar/contracts/src/main/java/net/corda/samples/avatar/states/Expiry.java @@ -0,0 +1,59 @@ +package net.corda.samples.avatar.states; + +import com.google.common.collect.ImmutableList; +import net.corda.samples.avatar.contracts.ExpiryContract; +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.ContractState; +import net.corda.core.identity.AbstractParty; +import org.jetbrains.annotations.NotNull; + +import java.time.Instant; +import java.util.List; +import java.util.Objects; + +//Expiry represents an expiry date beyond which the avatar cannot be sold. This is the encumbrance state which encumbers +//the Avatar state. +@BelongsToContract(ExpiryContract.class) +public class Expiry implements ContractState { + + private final Instant expiry; + private final String avatarId; + private final AbstractParty owner; + + public Expiry(Instant expiry, String avatarId, AbstractParty owner) { + this.expiry = expiry; + this.avatarId = avatarId; + this.owner = owner; + } + + public Instant getExpiry() { + return expiry; + } + + public String getAvatarId() { + return avatarId; + } + + public AbstractParty getOwner() { + return owner; + } + + @NotNull + @Override + public List getParticipants() { + return ImmutableList.of(owner); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Expiry expiry1 = (Expiry) o; + return expiry.equals(expiry1.expiry) && avatarId.equals(expiry1.avatarId); + } + + @Override + public int hashCode() { + return Objects.hash(expiry, avatarId); + } +} diff --git a/Features/encumbrance-avatar/contracts/src/test/java/net/corda/samples/avatar/contracts/ContractTests.java b/Features/encumbrance-avatar/contracts/src/test/java/net/corda/samples/avatar/contracts/ContractTests.java new file mode 100644 index 00000000..a015d70c --- /dev/null +++ b/Features/encumbrance-avatar/contracts/src/test/java/net/corda/samples/avatar/contracts/ContractTests.java @@ -0,0 +1,177 @@ +package net.corda.samples.avatar.contracts; + +import com.google.common.collect.ImmutableList; +import net.corda.core.identity.CordaX500Name; +import net.corda.samples.avatar.states.Avatar; +import net.corda.samples.avatar.states.Expiry; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.node.MockServices; +import org.junit.Test; + +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +import static net.corda.testing.node.NodeTestUtils.ledger; + +public class ContractTests { + + static private final MockServices ledgerServices = new MockServices(); + static private final TestIdentity seller = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB")); + static private final TestIdentity buyer = new TestIdentity(new CordaX500Name("MiniCorp", "London", "GB")); + + + //Both the encumbrance and the encumbered state must be added to the transaction + @Test + public void thereMustBeTwoOutputs() { + ledger(ledgerServices, (ledger -> { + ledger.transaction(tx -> { + tx.output(AvatarContract.AVATAR_CONTRACT_ID, new Avatar(seller.getParty(), "1")); + tx.command(ImmutableList.of(seller.getPublicKey()), new AvatarContract.Commands.Create()); + tx.fails(); + return null; + }); + return null; + })); + } + + + @Test + public void encumbranceIndexMustBeSpecified() { + //not specifying the encumbrance index fails the contract + ledger(ledgerServices, (ledger -> { + ledger.transaction(tx -> { + tx.output(AvatarContract.AVATAR_CONTRACT_ID, new Avatar(seller.getParty(), "1")); + tx.output(ExpiryContract.EXPIRY_CONTRACT_ID, + new Expiry(Instant.now().plus(2, ChronoUnit.MINUTES), "1", seller.getParty())); + tx.command(ImmutableList.of(seller.getPublicKey()), new AvatarContract.Commands.Create()); + tx.timeWindow(Instant.now(), Duration.ofMinutes(1)); + tx.failsWith("Avatar needs to be encumbered"); + + return null; + }); + return null; + })); + + //specifying the encumbrance index while adding output states passes the contract + ledger(ledgerServices, (ledger -> { + ledger.transaction(tx -> { + tx.output(AvatarContract.AVATAR_CONTRACT_ID, 1, new Avatar(seller.getParty(), "1")); + tx.output(ExpiryContract.EXPIRY_CONTRACT_ID, 0, + new Expiry(Instant.now().plus(2, ChronoUnit.MINUTES), "1", seller.getParty())); + tx.command(ImmutableList.of(seller.getPublicKey()), new AvatarContract.Commands.Create()); + tx.timeWindow(Instant.now(), Duration.ofMinutes(1)); + tx.verifies(); + return null; + }); + return null; + })); + } + + //Specifying time window is mandatory. This is checked in the encumbrance Expiry state. + @Test + public void specifyTimeWindow() { + ledger(ledgerServices, (ledger -> { + ledger.transaction(tx -> { + //this fails as time window is not specified + tx.output(AvatarContract.AVATAR_CONTRACT_ID, 1, new Avatar(seller.getParty(), "1")); + tx.output(ExpiryContract.EXPIRY_CONTRACT_ID, 0, new Expiry(Instant.now().plus(2, ChronoUnit.MINUTES), "1", seller.getParty())); + tx.command(ImmutableList.of(seller.getPublicKey()), new AvatarContract.Commands.Create()); + tx.fails(); + + //this will pass once we specify time window + tx.timeWindow(Instant.now(), Duration.ofMinutes(1)); + return tx.verifies(); + }); + return null; + })); + } + + //For selling, the Expiry of avatar must be greater than the time window + @Test + public void avatarIsRejectedIfItIsExpired() { + ledger(ledgerServices, (ledger -> { + ledger.transaction(tx -> { + tx.output(AvatarContract.AVATAR_CONTRACT_ID, 1, new Avatar(seller.getParty(), "1")); + tx.output(ExpiryContract.EXPIRY_CONTRACT_ID, 0, new Expiry(Instant.now().plus(2, ChronoUnit.MINUTES), "1", seller.getParty())); + tx.command(ImmutableList.of(seller.getPublicKey()), new AvatarContract.Commands.Create()); + tx.timeWindow(Instant.now(), Duration.ofMinutes(3)); + tx.fails(); + return null; + }); + return null; + })); + } + + //For selling, the Expiry of avatar must be greater than the time window + @Test + public void expirationDateShouldBeAfterTheTimeWindow() { + ledger(ledgerServices, (ledger -> { + ledger.transaction(tx -> { + tx.output(AvatarContract.AVATAR_CONTRACT_ID, 1, new Avatar(seller.getParty(), "1")); + tx.output(ExpiryContract.EXPIRY_CONTRACT_ID, 0, new Expiry(Instant.now().plus(3, ChronoUnit.MINUTES), "1", seller.getParty())); + tx.command(ImmutableList.of(seller.getPublicKey()), new AvatarContract.Commands.Create()); + tx.timeWindow(Instant.now(), Duration.ofMinutes(2)); + tx.verifies(); + return null; + }); + return null; + })); + } + + //test transaction which has encumbered states as inputs + @Test + public void transferAvatar() { + ledger(ledgerServices, ledger -> { + ledger.unverifiedTransaction(tx -> { + tx.output(AvatarContract.AVATAR_CONTRACT_ID, "avatarLabel", 1, + new Avatar(seller.getParty(), "1")); + tx.output(ExpiryContract.EXPIRY_CONTRACT_ID, "expiryLabel", 0, + new Expiry(Instant.now().plus(3, ChronoUnit.MINUTES), "1", seller.getParty())); + return null; + }); + ledger.transaction(tx -> { + tx.input("avatarLabel"); + tx.input("expiryLabel"); + tx.output(AvatarContract.AVATAR_CONTRACT_ID, "avatarLabel2", 1, + new Avatar(buyer.getParty(), "1")); + tx.output(ExpiryContract.EXPIRY_CONTRACT_ID, "expiryLabel2", 0, + new Expiry(Instant.now().plus(3, ChronoUnit.MINUTES), "1", buyer.getParty())); + tx.command(ImmutableList.of(seller.getPublicKey(), buyer.getPublicKey()), new AvatarContract.Commands.Transfer()); + tx.command(ImmutableList.of(seller.getPublicKey(), buyer.getPublicKey()), new ExpiryContract.Commands.Pass()); + tx.timeWindow(Instant.now(), Duration.ofMinutes(2)); + tx.verifies(); + return null; + }); + return null; + }); + } + + + @Test + public void avatarCannotBeSpentWithoutExpiry() { + ledger(ledgerServices, ledger -> { + ledger.unverifiedTransaction(tx -> { + tx.output(AvatarContract.AVATAR_CONTRACT_ID, "avatarLabel", 1, + new Avatar(seller.getParty(), "1")); + tx.output(ExpiryContract.EXPIRY_CONTRACT_ID, "expiryLabel", 0, + new Expiry(Instant.now().plus(3, ChronoUnit.MINUTES), "1", seller.getParty())); + return null; + }); + ledger.transaction(tx -> { + tx.input("avatarLabel"); +// tx.input(expiryLabel); + tx.output(AvatarContract.AVATAR_CONTRACT_ID, "avatarLabel2", 1, + new Avatar(buyer.getParty(), "1")); + tx.output(ExpiryContract.EXPIRY_CONTRACT_ID, "expiryLabel2", 0, + new Expiry(Instant.now().plus(3, ChronoUnit.MINUTES), "1", buyer.getParty())); + tx.command(ImmutableList.of(seller.getPublicKey(), buyer.getPublicKey()), new AvatarContract.Commands.Transfer()); + tx.command(ImmutableList.of(seller.getPublicKey(), buyer.getPublicKey()), new ExpiryContract.Commands.Pass()); + tx.timeWindow(Instant.now(), Duration.ofMinutes(2)); + tx.fails(); + return null; + }); + return null; + }); + } +} \ No newline at end of file diff --git a/Features/encumbrance-avatar/gradle.properties b/Features/encumbrance-avatar/gradle.properties new file mode 100644 index 00000000..f84267cd --- /dev/null +++ b/Features/encumbrance-avatar/gradle.properties @@ -0,0 +1,3 @@ +name=Test +group=net.corda.samples.avatar +version=0.1 \ No newline at end of file diff --git a/Features/encumbrance-avatar/gradle/wrapper/gradle-wrapper.jar b/Features/encumbrance-avatar/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..99340b4a Binary files /dev/null and b/Features/encumbrance-avatar/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Features/encumbrance-avatar/gradle/wrapper/gradle-wrapper.properties b/Features/encumbrance-avatar/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..aef7cb78 --- /dev/null +++ b/Features/encumbrance-avatar/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/Features/encumbrance-avatar/gradlew b/Features/encumbrance-avatar/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/Features/encumbrance-avatar/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/Features/encumbrance-avatar/gradlew.bat b/Features/encumbrance-avatar/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/Features/encumbrance-avatar/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/Features/encumbrance-avatar/settings.gradle b/Features/encumbrance-avatar/settings.gradle new file mode 100644 index 00000000..b4446eaf --- /dev/null +++ b/Features/encumbrance-avatar/settings.gradle @@ -0,0 +1,2 @@ +include 'workflows' +include 'contracts' \ No newline at end of file diff --git a/Features/encumbrance-avatar/workflows/build.gradle b/Features/encumbrance-avatar/workflows/build.gradle new file mode 100644 index 00000000..cb34144c --- /dev/null +++ b/Features/encumbrance-avatar/workflows/build.gradle @@ -0,0 +1,53 @@ +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 "Avatar 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" + + // CorDapp dependencies. + cordapp project(":contracts") +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} \ No newline at end of file diff --git a/Features/encumbrance-avatar/workflows/src/integrationTest/java/net/corda/samples/avatar/DriverBasedTest.java b/Features/encumbrance-avatar/workflows/src/integrationTest/java/net/corda/samples/avatar/DriverBasedTest.java new file mode 100644 index 00000000..8dc29dfc --- /dev/null +++ b/Features/encumbrance-avatar/workflows/src/integrationTest/java/net/corda/samples/avatar/DriverBasedTest.java @@ -0,0 +1,48 @@ +package net.corda.samples.avatar; + +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/Features/encumbrance-avatar/workflows/src/main/java/net/corda/samples/avatar/flows/CreateAvatarFlow.java b/Features/encumbrance-avatar/workflows/src/main/java/net/corda/samples/avatar/flows/CreateAvatarFlow.java new file mode 100644 index 00000000..32038fda --- /dev/null +++ b/Features/encumbrance-avatar/workflows/src/main/java/net/corda/samples/avatar/flows/CreateAvatarFlow.java @@ -0,0 +1,56 @@ +package net.corda.samples.avatar.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.samples.avatar.contracts.AvatarContract; +import net.corda.samples.avatar.contracts.ExpiryContract; +import net.corda.samples.avatar.states.Avatar; +import net.corda.samples.avatar.states.Expiry; +import net.corda.core.flows.*; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; + +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; + +@InitiatingFlow +@StartableByRPC +public class CreateAvatarFlow extends FlowLogic { + + private final String avatarId; + private final long expiryAfterMinutes; + + public CreateAvatarFlow(String avatarId, long expiryAfterMinutes) { + this.avatarId = avatarId; + if (expiryAfterMinutes <= 0) + throw new IllegalArgumentException("please provide positive value for expireAfterMinutes"); + this.expiryAfterMinutes = expiryAfterMinutes; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + + Avatar avatar = new Avatar(this.getOurIdentity(), avatarId); + Expiry expiry = new Expiry(Instant.now().plus(expiryAfterMinutes, ChronoUnit.MINUTES), avatarId, avatar.getOwner()); + + //add expiry and avatar as outputs by specifying encumbrance as index. add time window + //encumbrance can be identified by the output index. expiry is at output index 1 so we add 1 as the encumbrance + //value while adding avatar as an output state and vice versa. + TransactionBuilder txBuilder = new TransactionBuilder(notary) + .addOutputState(avatar, AvatarContract.AVATAR_CONTRACT_ID, notary, 1) //specify the encumbrance as the 3rd parameter + .addOutputState(expiry, ExpiryContract.EXPIRY_CONTRACT_ID, notary, 0) //specify the encumbrance as the 3rd parameter + .addCommand(new AvatarContract.Commands.Create(), avatar.getOwner().getOwningKey()) + .addCommand(new ExpiryContract.Commands.Create(), expiry.getOwner().getOwningKey()) + .setTimeWindow(Instant.now(), Duration.ofSeconds(10)); + + txBuilder.verify(getServiceHub()); + + SignedTransaction signedTransaction = getServiceHub().signInitialTransaction(txBuilder); + + return subFlow(new FinalityFlow(signedTransaction, Arrays.asList())); + } +} diff --git a/Features/encumbrance-avatar/workflows/src/main/java/net/corda/samples/avatar/flows/TransferAvatarFlow.java b/Features/encumbrance-avatar/workflows/src/main/java/net/corda/samples/avatar/flows/TransferAvatarFlow.java new file mode 100644 index 00000000..526a1688 --- /dev/null +++ b/Features/encumbrance-avatar/workflows/src/main/java/net/corda/samples/avatar/flows/TransferAvatarFlow.java @@ -0,0 +1,78 @@ +package net.corda.samples.avatar.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.samples.avatar.contracts.AvatarContract; +import net.corda.samples.avatar.contracts.ExpiryContract; +import net.corda.samples.avatar.states.Avatar; +import net.corda.samples.avatar.states.Expiry; +import net.corda.core.CordaRuntimeException; +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.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; + +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.Collections; + +@InitiatingFlow +@StartableByRPC +public class TransferAvatarFlow extends FlowLogic { + + private final String avatarId; + private final String buyer; + + public TransferAvatarFlow(String avatarId, String buyer) { + this.avatarId = avatarId; + this.buyer = buyer; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + Party buyerParty = getServiceHub().getIdentityService().partiesFromName(buyer, true).iterator().next(); + + Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + + //get avatar from db + Vault.Page avatarPage = getServiceHub().getVaultService().queryBy(Avatar.class); + StateAndRef avatarStateAndRef = avatarPage.getStates().stream().filter(i -> + i.getState().getData().getAvatarId().equalsIgnoreCase(avatarId)).findAny().orElseThrow(() -> + new CordaRuntimeException("No avatar found with avatar id as : " + avatarId));; + + //get expiry from db + Vault.Page expiryPage = getServiceHub().getVaultService().queryBy(Expiry.class); + StateAndRef expiryStateAndRef = expiryPage.getStates().stream().filter(i -> + i.getState().getData().getAvatarId().equalsIgnoreCase(avatarId)).findAny().orElseThrow(() -> + new CordaRuntimeException("No expiry found with avatar id as " + avatarId)); + + //change owner + Avatar avatar = new Avatar(buyerParty, avatarId); + Expiry expiry = new Expiry(expiryStateAndRef.getState().getData().getExpiry(), avatarId, buyerParty); + + //consume existing states, encumbering states will trigger the expiry contract to run + TransactionBuilder transactionBuilder = new TransactionBuilder(notary) + .addInputState(avatarStateAndRef) + .addInputState(expiryStateAndRef) + .addOutputState(avatar, AvatarContract.AVATAR_CONTRACT_ID, notary, 1) + .addOutputState(expiry, ExpiryContract.EXPIRY_CONTRACT_ID, notary, 0) + .addCommand(new AvatarContract.Commands.Transfer(), Arrays.asList(buyerParty.getOwningKey(), + avatarStateAndRef.getState().getData().getOwner().getOwningKey())) + .addCommand(new ExpiryContract.Commands.Pass(), Arrays.asList(buyerParty.getOwningKey(), + expiryStateAndRef.getState().getData().getOwner().getOwningKey())) + .setTimeWindow(Instant.now(), Duration.ofSeconds(10)); + + transactionBuilder.verify(getServiceHub()); + + SignedTransaction partiallySignedTx = getServiceHub().signInitialTransaction(transactionBuilder); + FlowSession buyerSession = initiateFlow(buyerParty); + + final SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(partiallySignedTx, Collections.singletonList(buyerSession))); + + return subFlow(new FinalityFlow(fullySignedTx, Collections.singletonList(buyerSession))); + } +} diff --git a/Features/encumbrance-avatar/workflows/src/main/java/net/corda/samples/avatar/flows/TransferAvatarResponderFlow.java b/Features/encumbrance-avatar/workflows/src/main/java/net/corda/samples/avatar/flows/TransferAvatarResponderFlow.java new file mode 100644 index 00000000..75a040ab --- /dev/null +++ b/Features/encumbrance-avatar/workflows/src/main/java/net/corda/samples/avatar/flows/TransferAvatarResponderFlow.java @@ -0,0 +1,33 @@ +package net.corda.samples.avatar.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.crypto.SecureHash; +import net.corda.core.flows.*; +import net.corda.core.transactions.SignedTransaction; + +@InitiatedBy(TransferAvatarFlow.class) +public class TransferAvatarResponderFlow extends FlowLogic { + private final FlowSession counterpartySession; + + public TransferAvatarResponderFlow(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public SignedTransaction call() throws FlowException { + // This only gets called when we send or receive send and receive is called + class SignTxFlow extends SignTransactionFlow { + private SignTxFlow(FlowSession otherPartyFlow) { + super(otherPartyFlow); + } + @Override + protected void checkTransaction(SignedTransaction stx) { + } + } + final SignTxFlow signTxFlow = new SignTxFlow(counterpartySession); + final SecureHash txId = subFlow(signTxFlow).getId(); + + return subFlow(new ReceiveFinalityFlow(counterpartySession, txId)); + } +} \ No newline at end of file diff --git a/Features/encumbrance-avatar/workflows/src/test/java/net/corda/samples/avatar/FlowTests.java b/Features/encumbrance-avatar/workflows/src/test/java/net/corda/samples/avatar/FlowTests.java new file mode 100644 index 00000000..3b50f133 --- /dev/null +++ b/Features/encumbrance-avatar/workflows/src/test/java/net/corda/samples/avatar/FlowTests.java @@ -0,0 +1,64 @@ +package net.corda.samples.avatar; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; +import net.corda.samples.avatar.flows.CreateAvatarFlow; +import net.corda.samples.avatar.flows.TransferAvatarFlow; +import net.corda.samples.avatar.states.Avatar; +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 MockNetwork network; + private StartedMockNode a; + private StartedMockNode b; + + @Before + public void setup() { + network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.avatar.contracts"), + TestCordapp.findCordapp("net.corda.samples.avatar.flows")))); + a = network.createPartyNode(null); + b = network.createPartyNode(null); + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void CreateAvatarTest() { + CreateAvatarFlow createflow = new CreateAvatarFlow("PETER-7526", 3); + CordaFuture future = a.startFlow(createflow); + network.runNetwork(); + + //successful query means the state is stored at node b's vault. Flow went through. + QueryCriteria.VaultQueryCriteria inputCriteria = new QueryCriteria.VaultQueryCriteria().withStatus(Vault.StateStatus.UNCONSUMED); + Avatar state = a.getServices().getVaultService().queryBy(Avatar.class,inputCriteria).getStates().get(0).getState().getData(); + } + + @Test + public void TransferAvatarTest() { + CreateAvatarFlow createflow = new CreateAvatarFlow("PETER-7526", 3); + CordaFuture future = a.startFlow(createflow); + network.runNetwork(); + + TransferAvatarFlow transferflow = new TransferAvatarFlow("PETER-7526", b.getInfo().getLegalIdentities().get(0).getName().getOrganisation()); + CordaFuture future2 = a.startFlow(transferflow); + network.runNetwork(); + + //successful query means the state is stored at node b's vault. Flow went through. + QueryCriteria.VaultQueryCriteria inputCriteria = new QueryCriteria.VaultQueryCriteria().withStatus(Vault.StateStatus.UNCONSUMED); + Avatar state = b.getServices().getVaultService().queryBy(Avatar.class,inputCriteria).getStates().get(0).getState().getData(); + } +} diff --git a/Features/encumbrance-avatar/workflows/src/test/java/net/corda/samples/avatar/NodeDriver.java b/Features/encumbrance-avatar/workflows/src/test/java/net/corda/samples/avatar/NodeDriver.java new file mode 100644 index 00000000..b8fc2944 --- /dev/null +++ b/Features/encumbrance-avatar/workflows/src/test/java/net/corda/samples/avatar/NodeDriver.java @@ -0,0 +1,39 @@ +package net.corda.samples.avatar; + +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/Features/multioutput-transaction/LICENCE b/Features/multioutput-transaction/LICENCE new file mode 100644 index 00000000..3ff572d1 --- /dev/null +++ b/Features/multioutput-transaction/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/Features/multioutput-transaction/README.md b/Features/multioutput-transaction/README.md new file mode 100644 index 00000000..47f563da --- /dev/null +++ b/Features/multioutput-transaction/README.md @@ -0,0 +1,56 @@ +

+ Corda +

+ +# Multi-Output Transaction Cordapp + +In this CorDapp, we will demo how to produce two outputs in one transaction. +It is a simple use case that we will have two states. One state is called +SubcountState and the other state is called OmniState, for which the OmniState keeps track of the accumulated balance of +the SubCountState amounts. Our goal is to show you how to produce two outputs in one transaction. + +# Pre-Requisites + +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.10/community/getting-set-up.html). + +# Usage + +## Running the nodes + +Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) +``` +./gradlew clean build deployNodes +``` +Then type: (to run the nodes) +``` +./build/nodes/runnodes +``` + +## Interacting with the nodes +In PartyA's node, run the following commands to initiate a single output transaction to PartyB +``` +flow start SendOneOutputFlowInitiator receiver: PartyB, amount: 10 +``` +Then we can go to PartyB's node to look at the expected output from the first transaction. +``` +run vaultQuery contractStateType: net.corda.samples.multioutput.states.SubCountState +``` +Now, we would like to bring in the second output. Now, back to PartyA's node, run the following command: +``` +flow start SendTwoOutputFlowInitiator receiver: PartyB, amount: 15 +``` +we can now go to PartyB's node and look for the second output by: +``` +run vaultQuery contractStateType: net.corda.samples.multioutput.states.OmniCountState +``` +Lastly, we would like to show how to introduce new output and update old output at the same transaction. At PartyA's node, run: +``` +flow start UpdateOmniCountFromSubCountFlowInitiator receiver: PartyB, amount: 15 +``` +And now if we go to PartyB's node and look for the OmniState, we should see it now shows 30. + + + + + + diff --git a/Features/multioutput-transaction/TRADEMARK b/Features/multioutput-transaction/TRADEMARK new file mode 100644 index 00000000..d2e056b5 --- /dev/null +++ b/Features/multioutput-transaction/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/Features/multioutput-transaction/build.gradle b/Features/multioutput-transaction/build.gradle new file mode 100644 index 00000000..884ef237 --- /dev/null +++ b/Features/multioutput-transaction/build.gradle @@ -0,0 +1,134 @@ +buildscript {//properties that you need to build the project + Properties constants = new Properties() + file("$projectDir/../constants.properties").withInputStream { constants.load(it) } + + ext { + corda_release_group = constants.getProperty("cordaReleaseGroup") + corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") + corda_release_version = constants.getProperty("cordaVersion") + corda_core_release_version = constants.getProperty("cordaCoreVersion") + 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' + } + + repositories { + mavenLocal() + mavenCentral() + maven { url 'https://download.corda.net/maven/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 {//Properties that you need to compile your project (The application) + apply from: "${rootProject.projectDir}/repositories.gradle" + apply plugin: 'java' + + repositories { + mavenLocal() + mavenCentral() + maven { url 'https://download.corda.net/maven/corda-dependencies' } + 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 + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} + +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") + } + } +} +//Module dependencis +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_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") + + 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" + cordaDriver "net.corda:corda-shell:4.10" + +} + + +//Task to deploy the nodes in order to bootstrap a network +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + + /* This property will load the CorDapps to each of the node by default, including the Notary. You can find them + * in the cordapps folder of the node at build/nodes/Notary/cordapps. However, the notary doesn't really understand + * the notion of cordapps. In production, Notary does not need cordapps as well. This is just a short cut to load + * the Corda network bootstrapper. + */ + nodeDefaults { + projectCordapp { + deploy = false + } + cordapp project(':contracts') + cordapp project(':workflows') + runSchemaMigration = true //This configuration is for any CorDapps with custom schema, We will leave this as true to avoid + //problems for developers who are not familiar with Corda. If you are not using custom schemas, you can change + //it to false for quicker project compiling time. + } + node { + name "O=Notary,L=London,C=GB" + notary = [validating : false] + p2pPort 10002 + rpcSettings { + address("localhost:10003") + adminAddress("localhost:10043") + } + } + 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"]]] + } +} diff --git a/Features/multioutput-transaction/config/dev/log4j2.xml b/Features/multioutput-transaction/config/dev/log4j2.xml new file mode 100644 index 00000000..34ba4d45 --- /dev/null +++ b/Features/multioutput-transaction/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/Features/multioutput-transaction/config/test/log4j2.xml b/Features/multioutput-transaction/config/test/log4j2.xml new file mode 100644 index 00000000..cd9926ca --- /dev/null +++ b/Features/multioutput-transaction/config/test/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n + > + + + + + + + + + + + + diff --git a/Features/multioutput-transaction/contracts/build.gradle b/Features/multioutput-transaction/contracts/build.gradle new file mode 100644 index 00000000..6ed0308f --- /dev/null +++ b/Features/multioutput-transaction/contracts/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + contract { + name "Template Contracts" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main{ + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + } + test{ + java{ + srcDir 'src/test/java' + java.outputDir = file('bin/test') + } + } +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_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/Features/multioutput-transaction/contracts/src/main/java/net/corda/samples/multioutput/contracts/OmniCountContract.java b/Features/multioutput-transaction/contracts/src/main/java/net/corda/samples/multioutput/contracts/OmniCountContract.java new file mode 100644 index 00000000..380c6e0b --- /dev/null +++ b/Features/multioutput-transaction/contracts/src/main/java/net/corda/samples/multioutput/contracts/OmniCountContract.java @@ -0,0 +1,32 @@ +package net.corda.samples.multioutput.contracts; + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.CommandWithParties; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import net.corda.samples.multioutput.states.OmniCountState; +import org.jetbrains.annotations.NotNull; + +import static net.corda.core.contracts.ContractsDSL.requireThat; + +public class OmniCountContract implements Contract { + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + final CommandWithParties command = tx.getCommands().get(0); + if (command.getValue() instanceof Commands.dualUpdate) { + requireThat(require -> { + OmniCountState outputState = tx.outputsOfType(OmniCountState.class).get(0); + require.using("The Omni Balance cannot exceed 200", outputState.getOmniAmount() < 200); + return null; + }); + } + } + + // Used to indicate the transaction's intent. + public interface Commands extends CommandData { + //In our hello-world app, We will only have one command. + class Send implements OmniCountContract.Commands {} + class dualUpdate implements OmniCountContract.Commands {} + + } +} diff --git a/Features/multioutput-transaction/contracts/src/main/java/net/corda/samples/multioutput/contracts/SubCountContract.java b/Features/multioutput-transaction/contracts/src/main/java/net/corda/samples/multioutput/contracts/SubCountContract.java new file mode 100644 index 00000000..28663212 --- /dev/null +++ b/Features/multioutput-transaction/contracts/src/main/java/net/corda/samples/multioutput/contracts/SubCountContract.java @@ -0,0 +1,20 @@ +package net.corda.samples.multioutput.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 SubCountContract implements Contract { + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + + } + + // Used to indicate the transaction's intent. + public interface Commands extends CommandData { + //In our hello-world app, We will only have one command. + class Send implements SubCountContract.Commands {} + } + +} diff --git a/Features/multioutput-transaction/contracts/src/main/java/net/corda/samples/multioutput/contracts/TemplateContract.java b/Features/multioutput-transaction/contracts/src/main/java/net/corda/samples/multioutput/contracts/TemplateContract.java new file mode 100644 index 00000000..f3f00158 --- /dev/null +++ b/Features/multioutput-transaction/contracts/src/main/java/net/corda/samples/multioutput/contracts/TemplateContract.java @@ -0,0 +1,45 @@ +package net.corda.samples.multioutput.contracts; + +import net.corda.samples.multioutput.states.TemplateState; +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; + +import static net.corda.core.contracts.ContractsDSL.requireThat; + +// ************ +// * Contract * +// ************ +public class TemplateContract implements Contract { + // This is used to identify our contract when building a transaction. + public static final String ID = "com.template.contracts.TemplateContract"; + + // A transaction is valid if the verify() function of the contract of all the transaction's input and output states + // does not throw an exception. + @Override + public void verify(LedgerTransaction tx) { + + /* We can use the requireSingleCommand function to extract command data from transaction. + * However, it is possible to have multiple commands in a signle transaction.*/ + //final CommandWithParties command = requireSingleCommand(tx.getCommands(), Commands.class); + final CommandData commandData = tx.getCommands().get(0).getValue(); + + if (commandData instanceof Commands.Send) { + //Retrieve the output state of the transaction + TemplateState output = tx.outputsOfType(TemplateState.class).get(0); + + //Using Corda DSL function requireThat to replicate conditions-checks + requireThat(require -> { + require.using("No inputs should be consumed when sending the Hello-World message.", tx.getInputStates().size() == 0); + require.using("The message must be Hello-World", output.getMsg().equals("Hello-World")); + return null; + }); + } + } + + // Used to indicate the transaction's intent. + public interface Commands extends CommandData { + //In our hello-world app, We will only have one command. + class Send implements Commands {} + } +} \ No newline at end of file diff --git a/Features/multioutput-transaction/contracts/src/main/java/net/corda/samples/multioutput/states/OmniCountState.java b/Features/multioutput-transaction/contracts/src/main/java/net/corda/samples/multioutput/states/OmniCountState.java new file mode 100644 index 00000000..1f026cfd --- /dev/null +++ b/Features/multioutput-transaction/contracts/src/main/java/net/corda/samples/multioutput/states/OmniCountState.java @@ -0,0 +1,54 @@ +package net.corda.samples.multioutput.states; + +import net.corda.samples.multioutput.contracts.OmniCountContract; +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.ContractState; +import net.corda.core.contracts.LinearState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +@BelongsToContract(OmniCountContract.class) +public class OmniCountState implements ContractState, LinearState { + + //private variables + private Integer omniAmount; + private Party borrowerHost; + private Party settler; + private UniqueIdentifier linearId; + + public OmniCountState(Integer omniAmount, Party borrowerHost, Party settler, UniqueIdentifier linearId) { + this.omniAmount = omniAmount; + this.borrowerHost = borrowerHost; + this.settler = settler; + this.linearId = linearId; + } + + @NotNull + @Override + public List getParticipants() { + return Arrays.asList(borrowerHost,settler); + } + + public Integer getOmniAmount() { + return omniAmount; + } + + public Party getBorrowerHost() { + return borrowerHost; + } + + public Party getSettler() { + return settler; + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return linearId; + } +} diff --git a/Features/multioutput-transaction/contracts/src/main/java/net/corda/samples/multioutput/states/SubCountState.java b/Features/multioutput-transaction/contracts/src/main/java/net/corda/samples/multioutput/states/SubCountState.java new file mode 100644 index 00000000..9bedee84 --- /dev/null +++ b/Features/multioutput-transaction/contracts/src/main/java/net/corda/samples/multioutput/states/SubCountState.java @@ -0,0 +1,43 @@ +package net.corda.samples.multioutput.states; + +import net.corda.samples.multioutput.contracts.SubCountContract; +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.Arrays; +import java.util.List; + +@BelongsToContract(SubCountContract.class) +public class SubCountState implements ContractState { + //private variables + private Integer amount; + private Party borrowerMe; + private Party loaner; + + public SubCountState(Integer amount, Party borrowerMe, Party loaner) { + this.amount = amount; + this.borrowerMe = borrowerMe; + this.loaner = loaner; + } + + @NotNull + @Override + public List getParticipants() { + return Arrays.asList(borrowerMe,loaner); + } + + public Integer getAmount() { + return amount; + } + + public Party getBorrowerMe() { + return borrowerMe; + } + + public Party getLoaner() { + return loaner; + } +} diff --git a/Features/multioutput-transaction/contracts/src/main/java/net/corda/samples/multioutput/states/TemplateState.java b/Features/multioutput-transaction/contracts/src/main/java/net/corda/samples/multioutput/states/TemplateState.java new file mode 100644 index 00000000..96846da3 --- /dev/null +++ b/Features/multioutput-transaction/contracts/src/main/java/net/corda/samples/multioutput/states/TemplateState.java @@ -0,0 +1,41 @@ +package net.corda.samples.multioutput.states; + +import net.corda.samples.multioutput.contracts.TemplateContract; +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 java.util.Arrays; +import java.util.List; + +// ********* +// * State * +// ********* +@BelongsToContract(TemplateContract.class) +public class TemplateState implements ContractState { + + //private variables + private String msg; + private Party sender; + private Party receiver; + + /* Constructor of your Corda state */ + public TemplateState(String msg, Party sender, Party receiver) { + this.msg = msg; + this.sender = sender; + this.receiver = receiver; + } + + //getters + public String getMsg() { return msg; } + public Party getSender() { return sender; } + public Party getReceiver() { return receiver; } + + /* This method will indicate who are the participants and required signers when + * this state is used in a transaction. */ + @Override + public List getParticipants() { + return Arrays.asList(sender,receiver); + } +} \ No newline at end of file diff --git a/Features/multioutput-transaction/contracts/src/test/java/net/corda/samples/multioutput/contracts/ContractTests.java b/Features/multioutput-transaction/contracts/src/test/java/net/corda/samples/multioutput/contracts/ContractTests.java new file mode 100644 index 00000000..c783f067 --- /dev/null +++ b/Features/multioutput-transaction/contracts/src/test/java/net/corda/samples/multioutput/contracts/ContractTests.java @@ -0,0 +1,37 @@ +//package net.corda.samples.multioutput.contracts; +// +//import net.corda.samples.multioutput.states.TemplateState; +//import net.corda.core.identity.CordaX500Name; +//import net.corda.testing.core.TestIdentity; +//import net.corda.testing.node.MockServices; +//import org.junit.Test; +// +//import java.util.Arrays; +// +//import static net.corda.testing.node.NodeTestUtils.ledger; +// +// +//public class ContractTests { +// private final MockServices ledgerServices = new MockServices(Arrays.asList("com.template")); +// TestIdentity alice = new TestIdentity(new CordaX500Name("Alice", "TestLand", "US")); +// TestIdentity bob = new TestIdentity(new CordaX500Name("Alice", "TestLand", "US")); +// +// @Test +// public void issuerAndRecipientCannotHaveSameEmail() { +// TemplateState state = new TemplateState("Hello-World",alice.getParty(),bob.getParty()); +// ledger(ledgerServices, l -> { +// l.transaction(tx -> { +// tx.input(TemplateContract.ID, state); +// tx.output(TemplateContract.ID, state); +// tx.command(alice.getPublicKey(), new TemplateContract.Commands.Send()); +// return tx.fails(); //fails because of having inputs +// }); +// l.transaction(tx -> { +// tx.output(TemplateContract.ID, state); +// tx.command(alice.getPublicKey(), new TemplateContract.Commands.Send()); +// return tx.verifies(); +// }); +// return null; +// }); +// } +//} \ No newline at end of file diff --git a/Features/multioutput-transaction/contracts/src/test/java/net/corda/samples/multioutput/contracts/StateTests.java b/Features/multioutput-transaction/contracts/src/test/java/net/corda/samples/multioutput/contracts/StateTests.java new file mode 100644 index 00000000..fe32b19e --- /dev/null +++ b/Features/multioutput-transaction/contracts/src/test/java/net/corda/samples/multioutput/contracts/StateTests.java @@ -0,0 +1,14 @@ +package net.corda.samples.multioutput.contracts; + +import net.corda.samples.multioutput.states.TemplateState; +import org.junit.Test; + +public class StateTests { + + //Mock State test check for if the state has correct parameters type + @Test + public void hasFieldOfCorrectType() throws NoSuchFieldException { + TemplateState.class.getDeclaredField("msg"); + assert (TemplateState.class.getDeclaredField("msg").getType().equals(String.class)); + } +} \ No newline at end of file diff --git a/Features/multioutput-transaction/gradle.properties b/Features/multioutput-transaction/gradle.properties new file mode 100644 index 00000000..7d7dcd3a --- /dev/null +++ b/Features/multioutput-transaction/gradle.properties @@ -0,0 +1,3 @@ +name=Test +group=com.template +version=0.1 \ No newline at end of file diff --git a/Features/multioutput-transaction/gradle/wrapper/gradle-wrapper.jar b/Features/multioutput-transaction/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..99340b4a Binary files /dev/null and b/Features/multioutput-transaction/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Features/multioutput-transaction/gradle/wrapper/gradle-wrapper.properties b/Features/multioutput-transaction/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..0188225a --- /dev/null +++ b/Features/multioutput-transaction/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +#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-5.6.4-all.zip + diff --git a/Features/multioutput-transaction/gradlew b/Features/multioutput-transaction/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/Features/multioutput-transaction/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/Features/multioutput-transaction/gradlew.bat b/Features/multioutput-transaction/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/Features/multioutput-transaction/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/Features/multioutput-transaction/repositories.gradle b/Features/multioutput-transaction/repositories.gradle new file mode 100644 index 00000000..9797c0ea --- /dev/null +++ b/Features/multioutput-transaction/repositories.gradle @@ -0,0 +1,7 @@ +repositories { + mavenLocal() + mavenCentral() + maven { url 'https://jitpack.io' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } +} diff --git a/Features/multioutput-transaction/settings.gradle b/Features/multioutput-transaction/settings.gradle new file mode 100644 index 00000000..2514aca2 --- /dev/null +++ b/Features/multioutput-transaction/settings.gradle @@ -0,0 +1,3 @@ +include 'workflows' +include 'contracts' +include 'clients' \ No newline at end of file diff --git a/Features/multioutput-transaction/workflows/build.gradle b/Features/multioutput-transaction/workflows/build.gradle new file mode 100644 index 00000000..c78fd179 --- /dev/null +++ b/Features/multioutput-transaction/workflows/build.gradle @@ -0,0 +1,64 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + workflow { + name "Template Flows" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/dev") + } + } + test { + java { + srcDir 'src/test/java' + java.outputDir = file('bin/main') + } + 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_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":contracts") +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} \ No newline at end of file diff --git a/Features/multioutput-transaction/workflows/src/integrationTest/java/net/corda/samples/multioutput/DriverBasedTest.java b/Features/multioutput-transaction/workflows/src/integrationTest/java/net/corda/samples/multioutput/DriverBasedTest.java new file mode 100644 index 00000000..4cb4dfd8 --- /dev/null +++ b/Features/multioutput-transaction/workflows/src/integrationTest/java/net/corda/samples/multioutput/DriverBasedTest.java @@ -0,0 +1,48 @@ +package net.corda.samples.multioutput; + +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/Features/multioutput-transaction/workflows/src/main/java/net/corda/samples/multioutput/flows/SendOneOutputFlow.java b/Features/multioutput-transaction/workflows/src/main/java/net/corda/samples/multioutput/flows/SendOneOutputFlow.java new file mode 100644 index 00000000..efb2214f --- /dev/null +++ b/Features/multioutput-transaction/workflows/src/main/java/net/corda/samples/multioutput/flows/SendOneOutputFlow.java @@ -0,0 +1,114 @@ +package net.corda.samples.multioutput.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.samples.multioutput.contracts.SubCountContract; +import net.corda.samples.multioutput.states.SubCountState; +import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class SendOneOutputFlow { + @InitiatingFlow + @StartableByRPC + public static class SendOneOutputFlowInitiator extends FlowLogic { + + //private variables + private Party borrowerMe ; + private Party receiver; + private Integer amount; + + + //public constructor + public SendOneOutputFlowInitiator(Party receiver, Integer amount) { + + this.receiver = receiver; + this.amount = amount; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + //Hello World message + this.borrowerMe = getOurIdentity(); + + // Step 1. Get a reference to the notary service on our network and our key pair. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + //Compose the State that carries the Hello World message + final SubCountState output = new SubCountState(amount,borrowerMe,receiver); + + // Step 3. Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // Step 4. Add the iou as an output state, as well as a command to the transaction builder. + builder.addOutputState(output); + builder.addCommand(new SubCountContract.Commands.Send(), + Arrays.asList(this.borrowerMe.getOwningKey(),this.receiver.getOwningKey()) ); + + + // Step 5. Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + // Step 6. Collect the other party's signature using the SignTransactionFlow. + List otherParties = output.getParticipants().stream().map(el -> (Party)el).collect(Collectors.toList()); + otherParties.remove(getOurIdentity()); + List sessions = otherParties.stream().map(el -> initiateFlow(el)).collect(Collectors.toList()); + + SignedTransaction stx = subFlow(new CollectSignaturesFlow(ptx, sessions)); + + // Step 7. Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(stx, sessions)); + } + } + + @InitiatedBy(SendOneOutputFlowInitiator.class) + public static class SendOneOutputFlowResponder extends FlowLogic{ + //private variable + private FlowSession counterpartySession; + + //Constructor + public SendOneOutputFlowResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + /* + * SignTransactionFlow will automatically verify the transaction and its signatures before signing it. + * However, just because a transaction is contractually valid doesn’t mean we necessarily want to sign. + * What if we don’t want to deal with the counterparty in question, or the value is too high, + * or we’re not happy with the transaction’s structure? checkTransaction + * allows us to define these additional checks. If any of these conditions are not met, + * we will not sign the transaction - even if the transaction and its signatures are contractually valid. + * ---------- + * For this hello-world cordapp, we will not implement any aditional checks. + * */ + } + }); + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } + } + +} + +//flow start SendOneOutputFlowInitiator receiver: PartyB, amount: 10 +//run vaultQuery contractStateType: net.corda.samples.multioutput.states.SubCountState +//flow start SendTwoOutputFlowInitiator receiver: PartyB, amount: 15 +//run vaultQuery contractStateType: net.corda.samples.multioutput.states.OmniCountState +//flow start UpdateOmniCountFromSubCountFlowInitiator receiver: PartyB, amount: 15 + diff --git a/Features/multioutput-transaction/workflows/src/main/java/net/corda/samples/multioutput/flows/SendTwoOutputFlow.java b/Features/multioutput-transaction/workflows/src/main/java/net/corda/samples/multioutput/flows/SendTwoOutputFlow.java new file mode 100644 index 00000000..ad986eee --- /dev/null +++ b/Features/multioutput-transaction/workflows/src/main/java/net/corda/samples/multioutput/flows/SendTwoOutputFlow.java @@ -0,0 +1,111 @@ +package net.corda.samples.multioutput.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.samples.multioutput.contracts.SubCountContract; +import net.corda.samples.multioutput.states.OmniCountState; +import net.corda.samples.multioutput.states.SubCountState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class SendTwoOutputFlow { + @InitiatingFlow + @StartableByRPC + public static class SendTwoOutputFlowInitiator extends FlowLogic { + + //private variables + private Party borrowerMe ; + private Party receiver; + private Integer amount; + + + //public constructor + public SendTwoOutputFlowInitiator(Party receiver, Integer amount) { + + this.receiver = receiver; + this.amount = amount; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + //Hello World message + this.borrowerMe = getOurIdentity(); + + // Step 1. Get a reference to the notary service on our network and our key pair. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + //Compose the State that carries the Hello World message + final SubCountState output = new SubCountState(amount,borrowerMe,receiver); + final OmniCountState omniCount = new OmniCountState(amount,borrowerMe,receiver,new UniqueIdentifier()); + + // Step 3. Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // Step 4. Add the iou as an output state, as well as a command to the transaction builder. + builder.addOutputState(output); + builder.addOutputState(omniCount); + builder.addCommand(new SubCountContract.Commands.Send(), + Arrays.asList(this.borrowerMe.getOwningKey(),this.receiver.getOwningKey()) ); + + + // Step 5. Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + // Step 6. Collect the other party's signature using the SignTransactionFlow. + List otherParties = output.getParticipants().stream().map(el -> (Party)el).collect(Collectors.toList()); + otherParties.remove(getOurIdentity()); + List sessions = otherParties.stream().map(el -> initiateFlow(el)).collect(Collectors.toList()); + + SignedTransaction stx = subFlow(new CollectSignaturesFlow(ptx, sessions)); + + // Step 7. Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(stx, sessions)); + } + } + + @InitiatedBy(SendTwoOutputFlowInitiator.class) + public static class SendTwoOutputFlowResponder extends FlowLogic{ + //private variable + private FlowSession counterpartySession; + + //Constructor + public SendTwoOutputFlowResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + /* + * SignTransactionFlow will automatically verify the transaction and its signatures before signing it. + * However, just because a transaction is contractually valid doesn’t mean we necessarily want to sign. + * What if we don’t want to deal with the counterparty in question, or the value is too high, + * or we’re not happy with the transaction’s structure? checkTransaction + * allows us to define these additional checks. If any of these conditions are not met, + * we will not sign the transaction - even if the transaction and its signatures are contractually valid. + * ---------- + * For this hello-world cordapp, we will not implement any aditional checks. + * */ + } + }); + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } + } + +} diff --git a/Features/multioutput-transaction/workflows/src/main/java/net/corda/samples/multioutput/flows/TemplateFlow.java b/Features/multioutput-transaction/workflows/src/main/java/net/corda/samples/multioutput/flows/TemplateFlow.java new file mode 100644 index 00000000..a78d1bc2 --- /dev/null +++ b/Features/multioutput-transaction/workflows/src/main/java/net/corda/samples/multioutput/flows/TemplateFlow.java @@ -0,0 +1,105 @@ +package net.corda.samples.multioutput.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.samples.multioutput.contracts.TemplateContract; +import net.corda.samples.multioutput.states.TemplateState; +import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class TemplateFlow { + + @InitiatingFlow + @StartableByRPC + public static class TemplateFlowInitiator extends FlowLogic{ + + //private variables + private Party sender ; + private Party receiver; + + //public constructor + public TemplateFlowInitiator(Party receiver) { + this.receiver = receiver; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + //Hello World message + String msg = "Hello-World"; + this.sender = getOurIdentity(); + + // Step 1. Get a reference to the notary service on our network and our key pair. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + //Compose the State that carries the Hello World message + final TemplateState output = new TemplateState(msg,sender,receiver); + + // Step 3. Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // Step 4. Add the iou as an output state, as well as a command to the transaction builder. + builder.addOutputState(output); + builder.addCommand(new TemplateContract.Commands.Send(), Arrays.asList(this.sender.getOwningKey(),this.receiver.getOwningKey()) ); + + + // Step 5. Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + + // Step 6. Collect the other party's signature using the SignTransactionFlow. + List otherParties = output.getParticipants().stream().map(el -> (Party)el).collect(Collectors.toList()); + otherParties.remove(getOurIdentity()); + List sessions = otherParties.stream().map(el -> initiateFlow(el)).collect(Collectors.toList()); + + SignedTransaction stx = subFlow(new CollectSignaturesFlow(ptx, sessions)); + + // Step 7. Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(stx, sessions)); + } + } + + @InitiatedBy(TemplateFlowInitiator.class) + public static class TemplateFlowResponder extends FlowLogic{ + //private variable + private FlowSession counterpartySession; + + //Constructor + public TemplateFlowResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + /* + * SignTransactionFlow will automatically verify the transaction and its signatures before signing it. + * However, just because a transaction is contractually valid doesn’t mean we necessarily want to sign. + * What if we don’t want to deal with the counterparty in question, or the value is too high, + * or we’re not happy with the transaction’s structure? checkTransaction + * allows us to define these additional checks. If any of these conditions are not met, + * we will not sign the transaction - even if the transaction and its signatures are contractually valid. + * ---------- + * For this hello-world cordapp, we will not implement any aditional checks. + * */ + } + }); + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } + } + +} \ No newline at end of file diff --git a/Features/multioutput-transaction/workflows/src/main/java/net/corda/samples/multioutput/flows/UpdateOmniCountFromSubCountFlow.java b/Features/multioutput-transaction/workflows/src/main/java/net/corda/samples/multioutput/flows/UpdateOmniCountFromSubCountFlow.java new file mode 100644 index 00000000..2d2102c3 --- /dev/null +++ b/Features/multioutput-transaction/workflows/src/main/java/net/corda/samples/multioutput/flows/UpdateOmniCountFromSubCountFlow.java @@ -0,0 +1,118 @@ +package net.corda.samples.multioutput.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.samples.multioutput.contracts.OmniCountContract; +import net.corda.samples.multioutput.contracts.SubCountContract; +import net.corda.samples.multioutput.states.OmniCountState; +import net.corda.samples.multioutput.states.SubCountState; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class UpdateOmniCountFromSubCountFlow { + + @InitiatingFlow + @StartableByRPC + public static class UpdateOmniCountFromSubCountFlowInitiator extends FlowLogic { + + //private variables + private Party borrowerMe ; + private Party receiver; + private Integer amount; + + + //public constructor + public UpdateOmniCountFromSubCountFlowInitiator(Party receiver, Integer amount) { + + this.receiver = receiver; + this.amount = amount; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + //Hello World message + this.borrowerMe = getOurIdentity(); + + // Step 1. Get a reference to the notary service on our network and our key pair. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + //Compose the State that carries the Hello World message + final SubCountState output = new SubCountState(amount,borrowerMe,receiver); + + StateAndRef inputStateAndRef = getServiceHub().getVaultService().queryBy(OmniCountState.class).getStates().get(0); + OmniCountState oldOmni = (OmniCountState) inputStateAndRef.getState().getData(); + + final OmniCountState omniCount = new OmniCountState(oldOmni.getOmniAmount()+amount,borrowerMe,receiver,oldOmni.getLinearId()); + + // Step 3. Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // Step 4. Add the iou as an output state, as well as a command to the transaction builder. + builder.addOutputState(output); //SubCount state + builder.addInputState(inputStateAndRef);//Consume the old OmniState + builder.addOutputState(omniCount); //new OmniSTate with new balance + builder.addCommand(new OmniCountContract.Commands.dualUpdate(), + Arrays.asList(this.borrowerMe.getOwningKey(),this.receiver.getOwningKey()) ); + + + // Step 5. Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + // Step 6. Collect the other party's signature using the SignTransactionFlow. + List otherParties = output.getParticipants().stream().map(el -> (Party)el).collect(Collectors.toList()); + otherParties.remove(getOurIdentity()); + List sessions = otherParties.stream().map(el -> initiateFlow(el)).collect(Collectors.toList()); + + SignedTransaction stx = subFlow(new CollectSignaturesFlow(ptx, sessions)); + + // Step 7. Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(stx, sessions)); + } + } + + @InitiatedBy(UpdateOmniCountFromSubCountFlowInitiator.class) + public static class UpdateOmniCountFromSubCountFlowResponder extends FlowLogic{ + //private variable + private FlowSession counterpartySession; + + //Constructor + public UpdateOmniCountFromSubCountFlowResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + /* + * SignTransactionFlow will automatically verify the transaction and its signatures before signing it. + * However, just because a transaction is contractually valid doesn’t mean we necessarily want to sign. + * What if we don’t want to deal with the counterparty in question, or the value is too high, + * or we’re not happy with the transaction’s structure? checkTransaction + * allows us to define these additional checks. If any of these conditions are not met, + * we will not sign the transaction - even if the transaction and its signatures are contractually valid. + * ---------- + * For this hello-world cordapp, we will not implement any aditional checks. + * */ + } + }); + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } + } + +} diff --git a/Features/multioutput-transaction/workflows/src/test/java/net/corda/samples/multioutput/FlowTests.java b/Features/multioutput-transaction/workflows/src/test/java/net/corda/samples/multioutput/FlowTests.java new file mode 100644 index 00000000..a43b855e --- /dev/null +++ b/Features/multioutput-transaction/workflows/src/test/java/net/corda/samples/multioutput/FlowTests.java @@ -0,0 +1,49 @@ +//package net.corda.samples.multioutput; +// +//import com.google.common.collect.ImmutableList; +//import net.corda.samples.multioutput.flows.TemplateFlow; +//import net.corda.samples.multioutput.states.TemplateState; +//import net.corda.core.identity.CordaX500Name; +//import net.corda.core.node.services.Vault; +//import net.corda.core.node.services.vault.QueryCriteria; +//import net.corda.core.transactions.SignedTransaction; +//import net.corda.testing.node.*; +//import org.junit.After; +//import org.junit.Before; +//import org.junit.Test; +// +//import java.util.concurrent.Future; +// +//public class FlowTests { +// private MockNetwork network; +// private StartedMockNode a; +// private StartedMockNode b; +// +// @Before +// public void setup() { +// network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( +// TestCordapp.findCordapp("com.template.contracts"), +// TestCordapp.findCordapp("com.template.flows"))) +// .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB"))))); +// a = network.createPartyNode(null); +// b = network.createPartyNode(null); +// network.runNetwork(); +// } +// +// @After +// public void tearDown() { +// network.stopNodes(); +// } +// +// @Test +// public void dummyTest() { +// TemplateFlow.TemplateFlowInitiator flow = new TemplateFlow.TemplateFlowInitiator(b.getInfo().getLegalIdentities().get(0)); +// Future future = a.startFlow(flow); +// network.runNetwork(); +// +// //successful query means the state is stored at node b's vault. Flow went through. +// QueryCriteria inputCriteria = new QueryCriteria.VaultQueryCriteria().withStatus(Vault.StateStatus.UNCONSUMED); +// TemplateState state = b.getServices().getVaultService().queryBy(TemplateState.class,inputCriteria) +// .getStates().get(0).getState().getData(); +// } +//} diff --git a/Features/notarychange-iou/README.md b/Features/notarychange-iou/README.md index 3c8fb849..dedf06ac 100644 --- a/Features/notarychange-iou/README.md +++ b/Features/notarychange-iou/README.md @@ -6,14 +6,14 @@ using one of the Corda library flow called `NotaryChangeFlow`. ## Concepts Notary is a critical component of a Corda network. It helps prevent double-spending -attempts in a Corda network. Thus all states issued in Corda are tied to a Notary. +attempts in a Corda network. Thus, all states issued in Corda are tied to a Notary. Any transaction involving the update of a state must be notarised from the Notary that the state is tied to, since other notaries wouldn't have seen any previous transaction involving the state would not be able to prevent a double spending attempt. However, a need to spend a state at a notary other than the one its tied to -become unavoidable at times. Thus Corda provides `NotaryChangeFlow` to cater to such +become unavoidable at times. Thus, Corda provides `NotaryChangeFlow` to cater to such needs. This demo uses the IOU Demo to demonstrate a Notary Change Transaction. Here we would @@ -25,14 +25,14 @@ issue an IOU at a particular Notary and try to settle the IOU at a different Not ### Pre-Requisites -See https://docs.corda.net/getting-set-up.html. +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.10/community/getting-set-up.html). ### Running the CorDapp Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) ``` -./gradlew clean deployNodes +./gradlew clean build deployNodes ``` Then type: (to run the nodes) ``` @@ -40,8 +40,6 @@ Then type: (to run the nodes) ``` This should bring up 4 nodes (PartyA, PartyB, NotaryA and NotaryB) in 4 different terminals. -If you have any questions during setup, please go to -https://docs.corda.net/getting-set-up.html for detailed setup instructions. To issue an IOU go to PartyA terminal and run: ``` diff --git a/Features/notarychange-iou/build.gradle b/Features/notarychange-iou/build.gradle index 24350c5d..9e7afa01 100644 --- a/Features/notarychange-iou/build.gradle +++ b/Features/notarychange-iou/build.gradle @@ -20,8 +20,8 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -38,9 +38,9 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } } @@ -85,6 +85,8 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" + } task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { diff --git a/Features/notarychange-iou/repositories.gradle b/Features/notarychange-iou/repositories.gradle index 206b9c68..8be7b630 100644 --- a/Features/notarychange-iou/repositories.gradle +++ b/Features/notarychange-iou/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Features/notarychange-iou/workflows/src/main/java/net/corda/samples/notarychange/flows/IssueFlow.java b/Features/notarychange-iou/workflows/src/main/java/net/corda/samples/notarychange/flows/IssueFlow.java index 6954052a..e9c1e6d2 100644 --- a/Features/notarychange-iou/workflows/src/main/java/net/corda/samples/notarychange/flows/IssueFlow.java +++ b/Features/notarychange-iou/workflows/src/main/java/net/corda/samples/notarychange/flows/IssueFlow.java @@ -4,6 +4,7 @@ import net.corda.core.contracts.Command; import net.corda.core.contracts.UniqueIdentifier; import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; @@ -77,14 +78,8 @@ public ProgressTracker getProgressTracker() { public String call() throws FlowException { // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ if(this.notary == null) - this.notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + this.notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // Stage 1. progressTracker.setCurrentStep(GENERATING_TRANSACTION); diff --git a/Features/notarychange-iou/workflows/src/test/java/net/corda/samples/notarychange/FlowTests.java b/Features/notarychange-iou/workflows/src/test/java/net/corda/samples/notarychange/FlowTests.java index 8a540d24..83fbf80e 100644 --- a/Features/notarychange-iou/workflows/src/test/java/net/corda/samples/notarychange/FlowTests.java +++ b/Features/notarychange-iou/workflows/src/test/java/net/corda/samples/notarychange/FlowTests.java @@ -32,7 +32,7 @@ public class FlowTests { )) .withNotarySpecs( Arrays.asList( - new MockNetworkNotarySpec(new CordaX500Name("NotaryA", "Toronto", "CA")), + new MockNetworkNotarySpec(new CordaX500Name("Notary", "London", "GB")),//"O=Notary,L=London,C=GB" new MockNetworkNotarySpec(new CordaX500Name("NotaryB", "Toronto", "CA")))) ); diff --git a/Features/observablestates-tradereporting/README.md b/Features/observablestates-tradereporting/README.md index 3cf7affb..5d130bf1 100644 --- a/Features/observablestates-tradereporting/README.md +++ b/Features/observablestates-tradereporting/README.md @@ -1,6 +1,6 @@ # Trade Reporting -- ObservableStates -This CorDapp shows how Corda's [observable states](https://docs.corda.net/docs/corda-os/4.4/tutorial-observer-nodes.html#observer-nodes) feature works. Observable states is the ability for nodes who are not +This CorDapp shows how Corda's observable states feature works. Observable states is the ability for nodes who are not participants in a transaction to still store them if the transactions are sent to them. @@ -24,18 +24,18 @@ In this CorDapp, the seller runs the `TradeAndReport` flow to create a new `High ## Pre-Requisites -For development environment setup, please refer to: [Setup Guide](https://docs.corda.net/getting-set-up.html). +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.10/community/getting-set-up.html). ### Deploy and run the node ``` -./greadlew deployNodes +./greadlew clean build deployNodes ./build/node/runnodes ``` ### Interacting with the nodes: -Go to the [CRaSH](https://docs.corda.net/docs/corda-os/shell.html) shell of Seller, and create a new `HighlyRegulatedState` +Go to the interactive node shell of Seller, and create a new `HighlyRegulatedState` start TradeAndReport buyer: Buyer, stateRegulator: StateRegulator, nationalRegulator: NationalRegulator diff --git a/Features/observablestates-tradereporting/build.gradle b/Features/observablestates-tradereporting/build.gradle index d8936f3e..c9854356 100644 --- a/Features/observablestates-tradereporting/build.gradle +++ b/Features/observablestates-tradereporting/build.gradle @@ -19,8 +19,8 @@ buildscript {//properties that you need to build the project repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -36,9 +36,9 @@ allprojects {//Properties that you need to compile your project (The application repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } } @@ -79,6 +79,8 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" + } task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { diff --git a/Features/observablestates-tradereporting/repositories.gradle b/Features/observablestates-tradereporting/repositories.gradle index 206b9c68..8be7b630 100644 --- a/Features/observablestates-tradereporting/repositories.gradle +++ b/Features/observablestates-tradereporting/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Features/observablestates-tradereporting/workflows/src/main/java/net/corda/samples/observable/flows/TradeAndReport.java b/Features/observablestates-tradereporting/workflows/src/main/java/net/corda/samples/observable/flows/TradeAndReport.java index 06eabc92..b96cf0d0 100644 --- a/Features/observablestates-tradereporting/workflows/src/main/java/net/corda/samples/observable/flows/TradeAndReport.java +++ b/Features/observablestates-tradereporting/workflows/src/main/java/net/corda/samples/observable/flows/TradeAndReport.java @@ -3,6 +3,7 @@ import co.paralleluniverse.fibers.Suspendable; import net.corda.core.crypto.SecureHash; import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; @@ -31,13 +32,7 @@ public TradeAndReport(Party buyer, Party stateRegulator, Party nationalRegulator public Void call() throws FlowException { // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 HighlyRegulatedState outputState = new HighlyRegulatedState(buyer, getOurIdentity()); diff --git a/Features/observablestates-tradereporting/workflows/src/test/java/net/corda/samples/observable/FlowTests.java b/Features/observablestates-tradereporting/workflows/src/test/java/net/corda/samples/observable/FlowTests.java index ec336ebb..551bab84 100644 --- a/Features/observablestates-tradereporting/workflows/src/test/java/net/corda/samples/observable/FlowTests.java +++ b/Features/observablestates-tradereporting/workflows/src/test/java/net/corda/samples/observable/FlowTests.java @@ -8,10 +8,7 @@ import net.corda.samples.observable.flows.TradeAndReport; import net.corda.samples.observable.flows.TradeAndReportResponder; import net.corda.samples.observable.states.HighlyRegulatedState; -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 net.corda.testing.node.*; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -26,7 +23,7 @@ public class FlowTests { TestCordapp.findCordapp("net.corda.samples.observable.contracts"), TestCordapp.findCordapp("net.corda.samples.observable.flows") ) - ) + ).withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) ); private final StartedMockNode a = network.createNode(); diff --git a/Features/oracle-primenumber/README.md b/Features/oracle-primenumber/README.md index bef5585a..93bd4d00 100644 --- a/Features/oracle-primenumber/README.md +++ b/Features/oracle-primenumber/README.md @@ -26,25 +26,25 @@ This repo is split into three CorDapps: ## Pre-Requisites -For development environment setup, please refer to: [Setup Guide](https://docs.corda.net/getting-set-up.html). +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.10/community/getting-set-up.html). ### Running the CorDapp Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) ``` -./gradlew clean deployNodes +./gradlew clean build deployNodes ``` Then type: (to run the nodes) ``` ./build/nodes/runnodes ``` -Go to the [CRaSH](https://docs.corda.net/docs/corda-os/shell.html) shell for PartyA, and request the 5th prime from the oracle using the `CreatePrime` flow: +Go to the interactive node shell for PartyA, and request the 5th prime from the oracle using the `CreatePrime` flow: flow start CreatePrime index: 5 We can then see the state wrapping the 5th prime (11) in our vault by running: - run vaultQuery contractStateType: net.corda.examples.oracle.base.states.PrimeState + run vaultQuery contractStateType: net.corda.samples.oracle.states.PrimeState diff --git a/Features/oracle-primenumber/build.gradle b/Features/oracle-primenumber/build.gradle index 4518eefa..66c3fbdb 100644 --- a/Features/oracle-primenumber/build.gradle +++ b/Features/oracle-primenumber/build.gradle @@ -19,8 +19,8 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -36,9 +36,9 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } } @@ -91,6 +91,7 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" testCompile "junit:junit:$junit_version" testCompile "$corda_release_group:corda-node-driver:$corda_release_version" diff --git a/Features/oracle-primenumber/contracts/src/test/java/net/corda/samples/oracle/contracts/PrimeContractTests.java b/Features/oracle-primenumber/contracts/src/test/java/net/corda/samples/oracle/contracts/PrimeContractTests.java index 683b5679..eb1d95c5 100644 --- a/Features/oracle-primenumber/contracts/src/test/java/net/corda/samples/oracle/contracts/PrimeContractTests.java +++ b/Features/oracle-primenumber/contracts/src/test/java/net/corda/samples/oracle/contracts/PrimeContractTests.java @@ -11,9 +11,11 @@ import org.junit.Test; import java.util.Arrays; +import java.util.Optional; import static net.corda.testing.node.NodeTestUtils.transaction; -import static org.jgroups.util.Util.assertEquals; +import static org.junit.Assert.assertEquals; +import static org.wildfly.common.Assert.assertTrue; public class PrimeContractTests { @@ -39,8 +41,8 @@ public void contractImplementsContract() { @Test public void constructorTest() { - assertEquals(1, st.getN()); - assertEquals(5, st.getNthPrime()); + assertTrue(st.getN() == 1); + assertTrue( st.getNthPrime() == 5); } @Test diff --git a/Features/oracle-primenumber/contracts/src/test/java/net/corda/samples/oracle/states/PrimeStateTests.java b/Features/oracle-primenumber/contracts/src/test/java/net/corda/samples/oracle/states/PrimeStateTests.java index d24010c1..1f95c722 100644 --- a/Features/oracle-primenumber/contracts/src/test/java/net/corda/samples/oracle/states/PrimeStateTests.java +++ b/Features/oracle-primenumber/contracts/src/test/java/net/corda/samples/oracle/states/PrimeStateTests.java @@ -4,8 +4,8 @@ import net.corda.testing.core.TestIdentity; import org.junit.Test; -import static org.jgroups.util.Util.assertTrue; import static org.junit.Assert.assertEquals; +import static org.wildfly.common.Assert.assertTrue; public class PrimeStateTests { diff --git a/Features/oracle-primenumber/repositories.gradle b/Features/oracle-primenumber/repositories.gradle index 206b9c68..8be7b630 100644 --- a/Features/oracle-primenumber/repositories.gradle +++ b/Features/oracle-primenumber/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Features/oracle-primenumber/workflows/src/main/java/net/corda/samples/oracle/flows/CreatePrime.java b/Features/oracle-primenumber/workflows/src/main/java/net/corda/samples/oracle/flows/CreatePrime.java index f2c81472..9ac22f80 100644 --- a/Features/oracle-primenumber/workflows/src/main/java/net/corda/samples/oracle/flows/CreatePrime.java +++ b/Features/oracle-primenumber/workflows/src/main/java/net/corda/samples/oracle/flows/CreatePrime.java @@ -63,13 +63,7 @@ public SignedTransaction call() throws FlowException { progressTracker.setCurrentStep(SET_UP); // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 CordaX500Name oracleName = new CordaX500Name("Oracle", "New York", "US"); Party oracle = getServiceHub().getNetworkMapCache().getNodeByLegalName(oracleName) diff --git a/Features/oracle-primenumber/workflows/src/test/java/net/corda/samples/oracle/flows/PrimeClientTests.java b/Features/oracle-primenumber/workflows/src/test/java/net/corda/samples/oracle/flows/PrimeClientTests.java index 6c47adb3..c33b32f6 100644 --- a/Features/oracle-primenumber/workflows/src/test/java/net/corda/samples/oracle/flows/PrimeClientTests.java +++ b/Features/oracle-primenumber/workflows/src/test/java/net/corda/samples/oracle/flows/PrimeClientTests.java @@ -12,7 +12,7 @@ import java.util.concurrent.ExecutionException; -import static org.jgroups.util.Util.assertEquals; +import static org.junit.Assert.assertEquals; public class PrimeClientTests { @@ -20,7 +20,7 @@ public class PrimeClientTests { ImmutableList.of( TestCordapp.findCordapp("net.corda.samples.oracle.services"), TestCordapp.findCordapp("net.corda.samples.oracle.contracts") - )) + )).withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) ); private final StartedMockNode a = mockNet.createNode(); diff --git a/Features/postgres-cordapp/.ci/Jenkinsfile b/Features/postgres-cordapp/.ci/Jenkinsfile new file mode 100644 index 00000000..7421cafe --- /dev/null +++ b/Features/postgres-cordapp/.ci/Jenkinsfile @@ -0,0 +1,37 @@ +#!groovy +/** + * Jenkins pipeline to build the java CorDapp template + */ + +/** + * Kill already started job. + * Assume new commit takes precedence and results from previousunfinished builds are not required. + * This feature doesn't play well with disableConcurrentBuilds() option + */ +@Library('corda-shared-build-pipeline-steps') +import static com.r3.build.BuildControl.killAllExistingBuildsForJob +killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) + +pipeline { + agent { + label 'eight-cores' + } + options { + ansiColor('xterm') + timestamps() + timeout(3*60) // 3 hours + buildDiscarder(logRotator(daysToKeepStr: '7', artifactDaysToKeepStr: '7')) + } + stages { + stage('Build') { + steps { + sh './gradlew --no-daemon -s clean build test deployNodes' + } + } + } + post { + cleanup { + deleteDir() + } + } +} diff --git a/Features/postgres-cordapp/.gitignore b/Features/postgres-cordapp/.gitignore new file mode 100644 index 00000000..0d75547d --- /dev/null +++ b/Features/postgres-cordapp/.gitignore @@ -0,0 +1,78 @@ +# Eclipse, ctags, Mac metadata, log files +.classpath +.project +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/ +*/bin/* + +# 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 diff --git a/Features/postgres-cordapp/.settings/org.eclipse.jdt.core.prefs b/Features/postgres-cordapp/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..ac86f90c --- /dev/null +++ b/Features/postgres-cordapp/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1 @@ +org.eclipse.jdt.core.compiler.codegen.methodParameters=generate \ No newline at end of file diff --git a/Features/postgres-cordapp/LICENCE b/Features/postgres-cordapp/LICENCE new file mode 100644 index 00000000..3ff572d1 --- /dev/null +++ b/Features/postgres-cordapp/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/Features/postgres-cordapp/README.md b/Features/postgres-cordapp/README.md new file mode 100644 index 00000000..9eebfb8f --- /dev/null +++ b/Features/postgres-cordapp/README.md @@ -0,0 +1,89 @@ +# Postgres CorDapp + +The latest versions of Corda Open Source support H2 and Postgres.H2 is the default database while this application will +demonstrate to you how to run corda with a postgres. The CorDapp being used is another copy of the yo CorDapp, as the +majority of the work here is in simply configuring the sql database connection. + +## Pre-Requisites + +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.10/community/getting-set-up.html). + +## Usage + +### Running the sample + +Start with provisioning the database: + +```sh +docker run --name postgres_for_corda -e POSTGRES_PASSWORD=test -d -p 5432:5432 postgres:11 + +# run sql scripts locally on the postgres instance to configure it for use +cat config.sql | docker exec -i postgres_for_corda psql -h localhost -p 5432 -U postgres + +./gradlew deployNodes + +./build/nodes/runnodes +``` + +You can then look for the shell for PartyA and run your flows. + +```sh +Thu Apr 22 13:03:05 EDT 2021>>> flow start net.corda.samples.postgres.flows.YoFlow target: PartyB + + ✅ Starting + ✅ Creating a new Yo! + ✅ Signing the Yo! + ✅ Verifying the Yo! + ✅ Sending the Yo! + Requesting signature by notary service + Requesting signature by Notary service + Validating response from Notary service + ✅ Broadcasting transaction to participants +➡️ Done +Flow completed with result: SignedTransaction(id=8B3FC06F685FC8FFD29001CC6205DAECBFF436E28E0439F74F5A89D11372C578) +``` + +### useful commands for interacting with your postgres container + +Here's a couple convenient postgres commands to get you started. + +```sh +# to list all databases +docker exec -i postgres_for_corda psql -U postgres -p 5432 -h localhost postgres -c "\l" + +# to make sure that the schemas were added to the database +docker exec -i postgres_for_corda psql -U postgres -p 5432 -h localhost postgres -c "\dn" + +# show all roles +docker exec -i postgres_for_corda psql -U postgres -p 5432 -h localhost postgres -c "\dg" +``` + +### Connecting to your database with dbeaver + +You can connect to your db with all kinds of tools like dbeaver, just open up a new connection, specify `postgreSQL` in +the search bar. + +The default username we provided in the command above is `postgres` and the password is `test`. + +![](./img/config-1.png) + +You can then open the schema editor and try running a couple queries: + +```sql +-- show all schemas +SELECT schema_name +FROM information_schema.schemata; +``` + +![](./img/config-2.png) + +### Troubleshooting + +You may run into some errors about node identity when running 'deployNodes', this is because the database will already +have the node information, so you will want to make sure to clear the database contents so that you don't run into +issues when recompiling the nodes. + +## Additional Resources + +- https://www.corda.net/blog/cordapp-database-setup-development-perspective/ +- https://medium.com/corda/cordapp-database-setup-production-perspective-2c400e60fae5 diff --git a/Features/postgres-cordapp/TRADEMARK b/Features/postgres-cordapp/TRADEMARK new file mode 100644 index 00000000..d2e056b5 --- /dev/null +++ b/Features/postgres-cordapp/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/Features/postgres-cordapp/build.gradle b/Features/postgres-cordapp/build.gradle new file mode 100644 index 00000000..f11c0efa --- /dev/null +++ b/Features/postgres-cordapp/build.gradle @@ -0,0 +1,157 @@ +buildscript {//properties that you need to build the project + Properties constants = new Properties() + file("$projectDir/../constants.properties").withInputStream { constants.load(it) } + + ext { + corda_release_group = constants.getProperty("cordaReleaseGroup") + corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") + corda_release_version = constants.getProperty("cordaVersion") + corda_core_release_version = constants.getProperty("cordaCoreVersion") + 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' + } + + repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://download.corda.net/maven/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 {//Properties that you need to compile your project (The application) + apply from: "${rootProject.projectDir}/repositories.gradle" + apply plugin: 'java' + + repositories { + mavenLocal() + + mavenCentral() + maven { url 'https://download.corda.net/maven/corda-dependencies' } + 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 + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} + +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") + } + } +} +//Module dependencis +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_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") + + 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" + cordaDriver "net.corda:corda-shell:4.10" + + cordaDriver "org.postgresql:postgresql:42.2.19" +} + + +//Task to deploy the nodes in order to bootstrap a network +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + + /* This property will load the CorDapps to each of the node by default, including the Notary. You can find them + * in the cordapps folder of the node at build/nodes/Notary/cordapps. However, the notary doesn't really understand + * the notion of cordapps. In production, Notary does not need cordapps as well. This is just a short cut to load + * the Corda network bootstrapper. + */ + nodeDefaults { + projectCordapp { + deploy = false + } + + cordapp project(':contracts') + cordapp project(':workflows') + // runSchemaMigration = true + + rpcUsers = [[user: "user1", "password": "test", "permissions": ["ALL"]]] + } + + node { + name "O=Notary,L=London,C=GB" + notary = [validating: false] + p2pPort 10001 + rpcSettings { + address("localhost:10011") + adminAddress("localhost:10021") + } + } + + node { + name "O=PartyA,L=London,C=GB" + p2pPort 10002 + rpcSettings { + address("localhost:10012") + adminAddress("localhost:10022") + } + + extraConfig = [ + "dataSourceProperties.dataSourceClassName": "org.postgresql.ds.PGSimpleDataSource", + "dataSourceProperties.dataSource.url" : "jdbc:postgresql://localhost:5432/postgres", + "dataSourceProperties.dataSource.user" : 'party_a', + "dataSourceProperties.dataSource.password": 'test', + "jarDirs" : ['${ rootProject.projectDir }/drivers'] + ] + + } + + node { + name "O=PartyB,L=New York,C=US" + p2pPort 10003 + rpcSettings { + address("localhost:10013") + adminAddress("localhost:10023") + } + + extraConfig = [ + "dataSourceProperties.dataSourceClassName": "org.postgresql.ds.PGSimpleDataSource", + "dataSourceProperties.dataSource.url" : "jdbc:postgresql://localhost:5432/postgres", + "dataSourceProperties.dataSource.user" : 'party_b', + "dataSourceProperties.dataSource.password": 'test', + "jarDirs" : ['${ rootProject.projectDir }/drivers'] + ] + } + +} diff --git a/Features/postgres-cordapp/config.sql b/Features/postgres-cordapp/config.sql new file mode 100644 index 00000000..1d3711b2 --- /dev/null +++ b/Features/postgres-cordapp/config.sql @@ -0,0 +1,24 @@ + +create USER "party_a" WITH LOGIN PASSWORD 'test'; +create SCHEMA "party_a_schema"; + +-- allow the user to access the schema and create objects in that schema. +grant USAGE, create ON SCHEMA "party_a_schema" TO "party_a"; + +-- adding permissions for current tables in that schema and the tables created in the future. +grant select, insert, update, delete, REFERENCES ON ALL tables IN SCHEMA "party_a_schema" TO "party_a"; +alter DEFAULT privileges IN SCHEMA "party_a_schema" grant select, insert, update, delete, REFERENCES ON tables TO "party_a"; +grant USAGE, select ON ALL sequences IN SCHEMA "party_a_schema" TO "party_a"; +alter DEFAULT privileges IN SCHEMA "party_a_schema" grant USAGE, select ON sequences TO "party_a"; +alter role "party_a" SET search_path = "party_a_schema"; + + +-- doing the same for party_b +create USER "party_b" with LOGIN PASSWORD 'test'; +create SCHEMA "party_b_schema"; +grant USAGE, create ON SCHEMA "party_b_schema" TO "party_b"; +grant select, insert, update, delete, REFERENCES ON ALL tables IN SCHEMA "party_b_schema" TO "party_b"; +alter DEFAULT privileges IN SCHEMA "party_b_schema" grant select, insert, update, delete, REFERENCES ON tables TO "party_b"; +grant USAGE, select ON ALL sequences IN SCHEMA "party_b_schema" TO "party_b"; +alter DEFAULT privileges IN SCHEMA "party_b_schema" grant USAGE, select ON sequences TO "party_b"; +alter role "party_b" SET search_path = "party_b_schema"; diff --git a/Features/postgres-cordapp/config/dev/log4j2.xml b/Features/postgres-cordapp/config/dev/log4j2.xml new file mode 100644 index 00000000..34ba4d45 --- /dev/null +++ b/Features/postgres-cordapp/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/Features/postgres-cordapp/config/test/log4j2.xml b/Features/postgres-cordapp/config/test/log4j2.xml new file mode 100644 index 00000000..cd9926ca --- /dev/null +++ b/Features/postgres-cordapp/config/test/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n + > + + + + + + + + + + + + diff --git a/Features/postgres-cordapp/contracts/build.gradle b/Features/postgres-cordapp/contracts/build.gradle new file mode 100644 index 00000000..6ed0308f --- /dev/null +++ b/Features/postgres-cordapp/contracts/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + contract { + name "Template Contracts" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main{ + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + } + test{ + java{ + srcDir 'src/test/java' + java.outputDir = file('bin/test') + } + } +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_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/Features/postgres-cordapp/contracts/src/main/java/net/corda/samples/postgres/contracts/YoContract.java b/Features/postgres-cordapp/contracts/src/main/java/net/corda/samples/postgres/contracts/YoContract.java new file mode 100644 index 00000000..b8836129 --- /dev/null +++ b/Features/postgres-cordapp/contracts/src/main/java/net/corda/samples/postgres/contracts/YoContract.java @@ -0,0 +1,40 @@ +package net.corda.samples.postgres.contracts; + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.CommandWithParties; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import net.corda.samples.postgres.states.YoState; +import org.jetbrains.annotations.NotNull; + +import static net.corda.core.contracts.ContractsDSL.requireSingleCommand; +import static net.corda.core.contracts.ContractsDSL.requireThat; + +// ************ +// * Contract * +// ************ +// Contract and state. +public class YoContract implements Contract { + // Used to identify our contract when building a transaction. + public static final String ID = "net.corda.samples.postgres.contracts.YoContract"; + + // Contract code. + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + CommandWithParties command = requireSingleCommand(tx.getCommands(), Commands.Send.class); + requireThat(req -> { + req.using("There can be no inputs when Yo'ing other parties", tx.getInputs().isEmpty()); + req.using("There must be one output: The Yo!", tx.getOutputs().size() == 1); + YoState yo = tx.outputsOfType(YoState.class).get(0); + req.using("No sending Yo's to yourself!", !yo.getTarget().equals(yo.getOrigin())); + req.using("The Yo! must be signed by the sender.", command.getSigners().contains(yo.getOrigin().getOwningKey())); + return null; + }); + } + + // Used to indicate the transaction's intent. + public interface Commands extends CommandData { + class Send implements Commands { + } + } +} diff --git a/Features/postgres-cordapp/contracts/src/main/java/net/corda/samples/postgres/states/YoState.java b/Features/postgres-cordapp/contracts/src/main/java/net/corda/samples/postgres/states/YoState.java new file mode 100644 index 00000000..5a1ccc5d --- /dev/null +++ b/Features/postgres-cordapp/contracts/src/main/java/net/corda/samples/postgres/states/YoState.java @@ -0,0 +1,58 @@ +package net.corda.samples.postgres.states; + +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 net.corda.core.serialization.ConstructorForDeserialization; +import net.corda.samples.postgres.contracts.YoContract; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +// ********* +// * State * +// ********* +@BelongsToContract(YoContract.class) +public class YoState implements ContractState { + private final Party origin; + private final Party target; + private final String yo; + + @ConstructorForDeserialization + public YoState(Party origin, Party target, String yo) { + this.origin = origin; + this.target = target; + this.yo = yo; + } + + public YoState(Party origin, Party target) { + this.origin = origin; + this.target = target; + this.yo = "Yo!"; + } + + public Party getOrigin() { + return origin; + } + + public Party getTarget() { + return target; + } + + public String getYo() { + return yo; + } + + @NotNull + @Override + public List getParticipants() { + return Arrays.asList(target); + } + + @Override + public String toString() { + return origin.getName() + ": " + yo; + } +} diff --git a/Features/postgres-cordapp/contracts/src/test/java/com/template/contracts/ContractTests.java b/Features/postgres-cordapp/contracts/src/test/java/com/template/contracts/ContractTests.java new file mode 100644 index 00000000..79116bca --- /dev/null +++ b/Features/postgres-cordapp/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/Features/postgres-cordapp/contracts/src/test/java/com/template/contracts/StateTests.java b/Features/postgres-cordapp/contracts/src/test/java/com/template/contracts/StateTests.java new file mode 100644 index 00000000..267b5625 --- /dev/null +++ b/Features/postgres-cordapp/contracts/src/test/java/com/template/contracts/StateTests.java @@ -0,0 +1,13 @@ +package com.template.contracts; + +import net.corda.testing.node.MockServices; +import org.junit.Test; + +public class StateTests { + private final MockServices ledgerServices = new MockServices(); + + @Test + public void dummyTest() { + + } +} \ No newline at end of file diff --git a/Features/postgres-cordapp/drivers/postgresql-42.2.19.jar b/Features/postgres-cordapp/drivers/postgresql-42.2.19.jar new file mode 100644 index 00000000..eb818ef4 Binary files /dev/null and b/Features/postgres-cordapp/drivers/postgresql-42.2.19.jar differ diff --git a/Features/postgres-cordapp/gradle.properties b/Features/postgres-cordapp/gradle.properties new file mode 100644 index 00000000..093e6dab --- /dev/null +++ b/Features/postgres-cordapp/gradle.properties @@ -0,0 +1,3 @@ +name=PostgreSQL Cordapp +group=net.corda.samples.postgres +version=0.1 diff --git a/Features/postgres-cordapp/gradle/wrapper/gradle-wrapper.jar b/Features/postgres-cordapp/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..99340b4a Binary files /dev/null and b/Features/postgres-cordapp/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Features/postgres-cordapp/gradle/wrapper/gradle-wrapper.properties b/Features/postgres-cordapp/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..0188225a --- /dev/null +++ b/Features/postgres-cordapp/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +#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-5.6.4-all.zip + diff --git a/Features/postgres-cordapp/gradlew b/Features/postgres-cordapp/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/Features/postgres-cordapp/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/Features/postgres-cordapp/gradlew.bat b/Features/postgres-cordapp/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/Features/postgres-cordapp/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/Features/postgres-cordapp/img/config-1.png b/Features/postgres-cordapp/img/config-1.png new file mode 100644 index 00000000..513ceb4c Binary files /dev/null and b/Features/postgres-cordapp/img/config-1.png differ diff --git a/Features/postgres-cordapp/img/config-2.png b/Features/postgres-cordapp/img/config-2.png new file mode 100644 index 00000000..555ec0f3 Binary files /dev/null and b/Features/postgres-cordapp/img/config-2.png differ diff --git a/Features/postgres-cordapp/repositories.gradle b/Features/postgres-cordapp/repositories.gradle new file mode 100644 index 00000000..8be7b630 --- /dev/null +++ b/Features/postgres-cordapp/repositories.gradle @@ -0,0 +1,8 @@ +repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://jitpack.io' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } +} diff --git a/Features/postgres-cordapp/settings.gradle b/Features/postgres-cordapp/settings.gradle new file mode 100644 index 00000000..2514aca2 --- /dev/null +++ b/Features/postgres-cordapp/settings.gradle @@ -0,0 +1,3 @@ +include 'workflows' +include 'contracts' +include 'clients' \ No newline at end of file diff --git a/Features/postgres-cordapp/workflows/build.gradle b/Features/postgres-cordapp/workflows/build.gradle new file mode 100644 index 00000000..c78fd179 --- /dev/null +++ b/Features/postgres-cordapp/workflows/build.gradle @@ -0,0 +1,64 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + workflow { + name "Template Flows" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/dev") + } + } + test { + java { + srcDir 'src/test/java' + java.outputDir = file('bin/main') + } + 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_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":contracts") +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} \ No newline at end of file diff --git a/Features/postgres-cordapp/workflows/src/integrationTest/java/com/template/DriverBasedTest.java b/Features/postgres-cordapp/workflows/src/integrationTest/java/com/template/DriverBasedTest.java new file mode 100644 index 00000000..a99b1528 --- /dev/null +++ b/Features/postgres-cordapp/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/Features/postgres-cordapp/workflows/src/main/java/net/corda/samples/postgres/flows/YoFlow.java b/Features/postgres-cordapp/workflows/src/main/java/net/corda/samples/postgres/flows/YoFlow.java new file mode 100644 index 00000000..1501d081 --- /dev/null +++ b/Features/postgres-cordapp/workflows/src/main/java/net/corda/samples/postgres/flows/YoFlow.java @@ -0,0 +1,86 @@ +package net.corda.samples.postgres.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.Command; +import net.corda.core.contracts.StateAndContract; +import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.core.utilities.ProgressTracker; +import net.corda.samples.postgres.contracts.YoContract; +import net.corda.samples.postgres.states.YoState; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.Objects; + +// ********* +// * Flows * +// ********* +@InitiatingFlow +@StartableByRPC +public class YoFlow extends FlowLogic { + private static final ProgressTracker.Step CREATING = new ProgressTracker.Step("Creating a new Yo!"); + private static final ProgressTracker.Step SIGNING = new ProgressTracker.Step("Signing the Yo!"); + private static final ProgressTracker.Step VERIFYING = new ProgressTracker.Step("Verifying the Yo!"); + private static final ProgressTracker.Step FINALISING = new ProgressTracker.Step("Sending the Yo!") { + @Nullable + @Override + public ProgressTracker childProgressTracker() { + return FinalityFlow.tracker(); + } + }; + + ProgressTracker progressTracker = new ProgressTracker( + CREATING, + SIGNING, + VERIFYING, + FINALISING + ); + + @Nullable + @Override + public ProgressTracker getProgressTracker() { + return progressTracker; + } + + private final Party target; + + public YoFlow(Party target) { + this.target = target; + } + + @Suspendable + @Override + public SignedTransaction call() throws FlowException { + // note we're creating a logger first with the shared name from our other example. + Logger logger = LoggerFactory.getLogger("net.corda"); + + progressTracker.setCurrentStep(CREATING); + + Party me = getOurIdentity(); + + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + Command command = new Command(new YoContract.Commands.Send(), Arrays.asList(me.getOwningKey())); + YoState state = new YoState(me, target); + StateAndContract stateAndContract = new StateAndContract(state, YoContract.ID); + TransactionBuilder utx = new TransactionBuilder(notary).withItems(stateAndContract, command); + + progressTracker.setCurrentStep(VERIFYING); + utx.verify(getServiceHub()); + + progressTracker.setCurrentStep(SIGNING); + SignedTransaction stx = getServiceHub().signInitialTransaction(utx); + + progressTracker.setCurrentStep(FINALISING); + FlowSession targetSession = initiateFlow(target); + return subFlow(new FinalityFlow(stx, Arrays.asList(targetSession), Objects.requireNonNull(FINALISING.childProgressTracker()))); + } +} diff --git a/Features/postgres-cordapp/workflows/src/main/java/net/corda/samples/postgres/flows/YoFlowResponder.java b/Features/postgres-cordapp/workflows/src/main/java/net/corda/samples/postgres/flows/YoFlowResponder.java new file mode 100644 index 00000000..f7c73f60 --- /dev/null +++ b/Features/postgres-cordapp/workflows/src/main/java/net/corda/samples/postgres/flows/YoFlowResponder.java @@ -0,0 +1,20 @@ +package net.corda.samples.postgres.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.flows.*; +import net.corda.core.transactions.SignedTransaction; + +@InitiatedBy(YoFlow.class) +public class YoFlowResponder extends FlowLogic { + private final FlowSession counterpartySession; + + public YoFlowResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public SignedTransaction call() throws FlowException { + return subFlow(new ReceiveFinalityFlow(counterpartySession)); + } +} diff --git a/Features/postgres-cordapp/workflows/src/test/java/net/corda/samples/postgres/flows/FlowTests.java b/Features/postgres-cordapp/workflows/src/test/java/net/corda/samples/postgres/flows/FlowTests.java new file mode 100644 index 00000000..395bc840 --- /dev/null +++ b/Features/postgres-cordapp/workflows/src/test/java/net/corda/samples/postgres/flows/FlowTests.java @@ -0,0 +1,51 @@ +package net.corda.samples.postgres.flows; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.transactions.SignedTransaction; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.ExecutionException; + +public class FlowTests { + private final MockNetwork network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.postgres.contracts"), + TestCordapp.findCordapp("net.corda.samples.postgres.flows") + )).withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB"))))); + private final StartedMockNode a = network.createNode(); + private final StartedMockNode b = network.createNode(); + + public FlowTests() { + ImmutableList.of(a, b).forEach(it -> { + it.registerInitiatedFlow(YoFlowResponder.class); + }); + } + + @Before + public void setup() { + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void emptyTest() { // ensures configuration is correct + } + + //The logging flow should not have any input + //This test will check if the input list is empty + @Test + public void dummyTest() throws ExecutionException, InterruptedException { + CordaFuture future = a.startFlow(new YoFlow(b.getInfo().getLegalIdentities().get(0))); + network.runNetwork(); + SignedTransaction ptx = future.get(); + assert (ptx.getTx().getInputs().isEmpty()); + } +} diff --git a/Features/queryablestate-carinsurance/QueryableState.postman_collection.json b/Features/queryablestate-carinsurance/QueryableState.postman_collection.json new file mode 100644 index 00000000..593eaaf6 --- /dev/null +++ b/Features/queryablestate-carinsurance/QueryableState.postman_collection.json @@ -0,0 +1,93 @@ +{ + "info": { + "_postman_id": "381497f6-9903-47b2-9d74-7f1578532895", + "name": "Queryable State", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Vehicle Insurance", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"vehicleInfo\": {\n\t\t\"registrationNumber\": \"MH014343\",\n\t\t\"chasisNumber\": \"C232ND832\",\n\t\t\"make\": \"Hyundai\",\n\t\t\"model\": \"Elantra\",\n\t\t\"variant\": \"SX\",\n\t\t\"color\": \"Black\",\n\t\t\"fuelType\": \"Petrol\"\n\t},\n\t\"policyNumber\": \"8190\",\n\t\"insuredValue\": \"20000\",\n\t\"duration\": 1,\n\t\"premium\": \"3000\"\n}" + }, + "url": { + "raw": "http://localhost:8080/vehicleInsurance/Insuree", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "vehicleInsurance", + "Insuree" + ] + } + }, + "response": [] + }, + { + "name": "Claim", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"claimNumber\":\"C001\",\n\t\"claimDescription\": \"Minor Accident, Bumper Damaged\",\n\t\"claimAmount\": 1000\n}" + }, + "url": { + "raw": "http://localhost:8080//vehicleInsurance/claim/8190", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "", + "vehicleInsurance", + "claim", + "8190" + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] +} \ No newline at end of file diff --git a/Features/queryablestate-carinsurance/README.md b/Features/queryablestate-carinsurance/README.md index 0e94c408..f7f39dea 100644 --- a/Features/queryablestate-carinsurance/README.md +++ b/Features/queryablestate-carinsurance/README.md @@ -1,6 +1,6 @@ # Car Insurance -- QueryableState -This CorDapp demonstrates [QueryableState](https://docs.corda.net/docs/corda-os/api-persistence.html) works in Corda. Corda allows developers +This CorDapp demonstrates [QueryableState](https://docs.r3.com/en/platform/corda/4.10/enterprise/cordapps/api-states.html#the-queryablestate-and-schedulablestate-interfaces) works in Corda. Corda allows developers to have the ability to expose some or all parts of their states to a custom database table using an ORM tools. To support this feature the state must implement `QueryableState`. @@ -8,7 +8,7 @@ table using an ORM tools. To support this feature the state must implement In this CorDapp we would use an `Insurance` state and persist its properties in a custom table in the database. The `Insurance` state among other fields also contains an `VehicleDetail` object, which is the asset being insured. We have used -this `VehicleDetail` to demonstrate _One-to-One_ relationship. Similarly we also +this `VehicleDetail` to demonstrate _One-to-One_ relationship. Similarly, we also have a list of `Claim` objects in the `Insurance` state which represents claims made against the insurance. We use them to demonstrate _One-to-Many_ relationship. @@ -16,31 +16,31 @@ made against the insurance. We use them to demonstrate _One-to-Many_ relationshi ## Concepts -A spring boot client is provided with the cordapp, which exposes two REST endpoints -(see `Controller` in the clients module) to trigger the flows. +A spring boot client is provided with the CorDapp, which exposes two REST endpoints +(see `Controller` in the clients' module) to trigger the flows. Use the command `./gradlew bootRun` in the project root folder to run the [Spring Boot Server](https://spring.io/projects/spring-boot#overview). ### Flows -There are two flow in this cordapp: +There are two flow in this CorDapp: -1. IssueInsurance: It creates the insurance state with the associated vehicle information. +1. `IssueInsurance`: It creates the insurance state with the associated vehicle information. -2. InsuranceClaim: It creates the claims against the insurance. +2. `InsuranceClaim`: It creates the claims against the insurance. ## Usage ## Pre-Requisites -For development environment setup, please refer to: [Setup Guide](https://docs.corda.net/getting-set-up.html). +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.10/community/getting-set-up.html). ### Running the CorDapp Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) ``` -./gradlew clean deployNodes +./gradlew clean build deployNodes ``` Then type: (to run the nodes) ``` @@ -50,7 +50,8 @@ Then type: (to run the nodes) ### Interacting with the nodes The Postman collection containing API's calls to the REST endpoints can be imported -from the link: https://www.getpostman.com/collections/ddc01c13b8ab4b5e853b. +from [this link](https://www.getpostman.com/collections/ddc01c13b8ab4b5e853b). + Use the option Import > Import from Link option in Postman to import the collection.

diff --git a/Features/queryablestate-carinsurance/build.gradle b/Features/queryablestate-carinsurance/build.gradle index 9cef224f..a2f9fd30 100644 --- a/Features/queryablestate-carinsurance/build.gradle +++ b/Features/queryablestate-carinsurance/build.gradle @@ -23,8 +23,8 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -42,9 +42,9 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } } @@ -84,6 +84,9 @@ dependencies { 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" + + cordaDriver "net.corda:corda-shell:4.10" + } cordapp { diff --git a/Features/queryablestate-carinsurance/contracts/build.gradle b/Features/queryablestate-carinsurance/contracts/build.gradle index 12dcbf54..6e7348cc 100644 --- a/Features/queryablestate-carinsurance/contracts/build.gradle +++ b/Features/queryablestate-carinsurance/contracts/build.gradle @@ -44,6 +44,6 @@ dependencies { // Corda dependencies. cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" cordaRuntime "$corda_release_group:corda:$corda_release_version" - compileOnly "$corda_release_group:corda-testserver-impl:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" } diff --git a/Features/queryablestate-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/states/InsuranceStateTests.java b/Features/queryablestate-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/states/InsuranceStateTests.java index 8a78e4a8..c94b494c 100644 --- a/Features/queryablestate-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/states/InsuranceStateTests.java +++ b/Features/queryablestate-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/states/InsuranceStateTests.java @@ -7,8 +7,9 @@ import java.util.Arrays; -import static org.jgroups.util.Util.assertEquals; -import static org.jgroups.util.Util.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.wildfly.common.Assert.assertTrue; + public class InsuranceStateTests { diff --git a/Features/queryablestate-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/states/VehicleDetailTests.java b/Features/queryablestate-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/states/VehicleDetailTests.java index 547a62a8..b03ef72a 100644 --- a/Features/queryablestate-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/states/VehicleDetailTests.java +++ b/Features/queryablestate-carinsurance/contracts/src/test/java/net/corda/samples/carinsurance/states/VehicleDetailTests.java @@ -4,7 +4,7 @@ import net.corda.testing.core.TestIdentity; import org.junit.Test; -import static org.jgroups.util.Util.assertEquals; +import static org.junit.Assert.assertEquals; public class VehicleDetailTests { diff --git a/Features/queryablestate-carinsurance/repositories.gradle b/Features/queryablestate-carinsurance/repositories.gradle index 206b9c68..8be7b630 100644 --- a/Features/queryablestate-carinsurance/repositories.gradle +++ b/Features/queryablestate-carinsurance/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Features/queryablestate-carinsurance/workflows/build.gradle b/Features/queryablestate-carinsurance/workflows/build.gradle index a5fd876b..e53991a3 100644 --- a/Features/queryablestate-carinsurance/workflows/build.gradle +++ b/Features/queryablestate-carinsurance/workflows/build.gradle @@ -52,7 +52,7 @@ dependencies { // Corda dependencies. cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" cordaRuntime "$corda_release_group:corda:$corda_release_version" - compileOnly "$corda_release_group:corda-testserver-impl:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" // CorDapp dependencies. diff --git a/Features/queryablestate-carinsurance/workflows/src/main/java/net/corda/samples/carinsurance/flows/IssueInsuranceFlow.java b/Features/queryablestate-carinsurance/workflows/src/main/java/net/corda/samples/carinsurance/flows/IssueInsuranceFlow.java index 5815c60c..1ac0c4b7 100644 --- a/Features/queryablestate-carinsurance/workflows/src/main/java/net/corda/samples/carinsurance/flows/IssueInsuranceFlow.java +++ b/Features/queryablestate-carinsurance/workflows/src/main/java/net/corda/samples/carinsurance/flows/IssueInsuranceFlow.java @@ -3,6 +3,7 @@ import co.paralleluniverse.fibers.Suspendable; import com.google.common.collect.ImmutableList; import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; @@ -39,13 +40,7 @@ public ProgressTracker getProgressTracker() { public SignedTransaction call() throws FlowException { // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); Party insurer = getOurIdentity(); diff --git a/Features/queryablestate-carinsurance/workflows/src/test/java/net/corda/samples/carinsurance/flows/FlowTests.java b/Features/queryablestate-carinsurance/workflows/src/test/java/net/corda/samples/carinsurance/flows/FlowTests.java index 603cd1b2..c63a8228 100644 --- a/Features/queryablestate-carinsurance/workflows/src/test/java/net/corda/samples/carinsurance/flows/FlowTests.java +++ b/Features/queryablestate-carinsurance/workflows/src/test/java/net/corda/samples/carinsurance/flows/FlowTests.java @@ -2,11 +2,9 @@ import com.google.common.collect.ImmutableList; import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; import net.corda.core.transactions.SignedTransaction; -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 net.corda.testing.node.*; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -17,7 +15,8 @@ public class FlowTests { private final MockNetwork network = new MockNetwork(new MockNetworkParameters(ImmutableList.of( TestCordapp.findCordapp("net.corda.samples.carinsurance.contracts"), TestCordapp.findCordapp("net.corda.samples.carinsurance.flows") - ))); + )).withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); private final StartedMockNode a = network.createNode(); private final StartedMockNode b = network.createNode(); diff --git a/Features/referencestates-sanctionsbody/README.md b/Features/referencestates-sanctionsbody/README.md index 00c0aa0e..be971b8d 100644 --- a/Features/referencestates-sanctionsbody/README.md +++ b/Features/referencestates-sanctionsbody/README.md @@ -10,14 +10,14 @@ This CorDapp allows two nodes to enter into an IOU agreement, but enforces that ## Pre-Requisites -For development environment setup, please refer to: [Setup Guide](https://docs.corda.net/getting-set-up.html). +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.10/community/getting-set-up.html). ### Running the CorDapp Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) ``` -./gradlew clean deployNodes +./gradlew clean build deployNodes ``` Then type: (to run the nodes) ``` diff --git a/Features/referencestates-sanctionsbody/build.gradle b/Features/referencestates-sanctionsbody/build.gradle index 3f53db2d..49b1af1c 100644 --- a/Features/referencestates-sanctionsbody/build.gradle +++ b/Features/referencestates-sanctionsbody/build.gradle @@ -19,8 +19,8 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -36,9 +36,9 @@ allprojects { //Properties that you need to compile your project (The applicatio repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } } @@ -79,6 +79,8 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" + } task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { diff --git a/Features/referencestates-sanctionsbody/contracts/src/test/java/net/corda/samples/referencestates/states/SanctionableIOUStateTests.java b/Features/referencestates-sanctionsbody/contracts/src/test/java/net/corda/samples/referencestates/states/SanctionableIOUStateTests.java index a52b82b7..d3c9467b 100644 --- a/Features/referencestates-sanctionsbody/contracts/src/test/java/net/corda/samples/referencestates/states/SanctionableIOUStateTests.java +++ b/Features/referencestates-sanctionsbody/contracts/src/test/java/net/corda/samples/referencestates/states/SanctionableIOUStateTests.java @@ -8,8 +8,8 @@ import net.corda.testing.core.TestIdentity; import org.junit.Test; -import static org.jgroups.util.Util.assertEquals; -import static org.jgroups.util.Util.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.wildfly.common.Assert.assertTrue; public class SanctionableIOUStateTests { diff --git a/Features/referencestates-sanctionsbody/contracts/src/test/java/net/corda/samples/referencestates/states/SanctionedEntitiesTests.java b/Features/referencestates-sanctionsbody/contracts/src/test/java/net/corda/samples/referencestates/states/SanctionedEntitiesTests.java index bb1b708f..d5ad4c58 100644 --- a/Features/referencestates-sanctionsbody/contracts/src/test/java/net/corda/samples/referencestates/states/SanctionedEntitiesTests.java +++ b/Features/referencestates-sanctionsbody/contracts/src/test/java/net/corda/samples/referencestates/states/SanctionedEntitiesTests.java @@ -10,7 +10,9 @@ import java.util.Arrays; import java.util.List; -import static org.jgroups.util.Util.*; +import static org.junit.Assert.assertEquals; +import static org.wildfly.common.Assert.assertFalse; +import static org.wildfly.common.Assert.assertTrue; public class SanctionedEntitiesTests { diff --git a/Features/referencestates-sanctionsbody/repositories.gradle b/Features/referencestates-sanctionsbody/repositories.gradle index 206b9c68..8be7b630 100644 --- a/Features/referencestates-sanctionsbody/repositories.gradle +++ b/Features/referencestates-sanctionsbody/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Features/referencestates-sanctionsbody/workflows/src/main/java/net/corda/samples/referencestates/referencestates/flows/IOUIssueFlow.java b/Features/referencestates-sanctionsbody/workflows/src/main/java/net/corda/samples/referencestates/referencestates/flows/IOUIssueFlow.java index 26121462..d6b37bde 100644 --- a/Features/referencestates-sanctionsbody/workflows/src/main/java/net/corda/samples/referencestates/referencestates/flows/IOUIssueFlow.java +++ b/Features/referencestates-sanctionsbody/workflows/src/main/java/net/corda/samples/referencestates/referencestates/flows/IOUIssueFlow.java @@ -1,6 +1,7 @@ package net.corda.samples.referencestates.referencestates.flows; import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.referencestates.contracts.SanctionableIOUContract; import net.corda.samples.referencestates.states.SanctionableIOUState; import net.corda.samples.referencestates.states.SanctionedEntities; @@ -65,14 +66,10 @@ public ProgressTracker childProgressTracker() { @Suspendable @Override public SignedTransaction call() throws FlowException { + // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // Stage 1 progressTracker.setCurrentStep(GENERATING_TRANSACTION); diff --git a/Features/referencestates-sanctionsbody/workflows/src/main/java/net/corda/samples/referencestates/referencestates/flows/IssueSanctionsListFlow.java b/Features/referencestates-sanctionsbody/workflows/src/main/java/net/corda/samples/referencestates/referencestates/flows/IssueSanctionsListFlow.java index 21f1692c..10378a93 100644 --- a/Features/referencestates-sanctionsbody/workflows/src/main/java/net/corda/samples/referencestates/referencestates/flows/IssueSanctionsListFlow.java +++ b/Features/referencestates-sanctionsbody/workflows/src/main/java/net/corda/samples/referencestates/referencestates/flows/IssueSanctionsListFlow.java @@ -1,6 +1,7 @@ package net.corda.samples.referencestates.referencestates.flows; import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.referencestates.contracts.SanctionedEntitiesContract; import net.corda.samples.referencestates.states.SanctionedEntities; import net.corda.core.contracts.Command; @@ -49,13 +50,8 @@ public ProgressTracker childProgressTracker() { @Override public StateAndRef call() throws FlowException { // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // Stage 1 progressTracker.setCurrentStep(GENERATING_TRANSACTION); diff --git a/Features/referencestates-sanctionsbody/workflows/src/main/java/net/corda/samples/referencestates/referencestates/flows/UpdateSanctionsListFlow.java b/Features/referencestates-sanctionsbody/workflows/src/main/java/net/corda/samples/referencestates/referencestates/flows/UpdateSanctionsListFlow.java index c260c5a1..3e8a3ea4 100644 --- a/Features/referencestates-sanctionsbody/workflows/src/main/java/net/corda/samples/referencestates/referencestates/flows/UpdateSanctionsListFlow.java +++ b/Features/referencestates-sanctionsbody/workflows/src/main/java/net/corda/samples/referencestates/referencestates/flows/UpdateSanctionsListFlow.java @@ -43,14 +43,7 @@ public ProgressTracker childProgressTracker() { @Suspendable @Override public StateAndRef call() throws FlowException { - // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + StateAndRef oldList = getServiceHub().getVaultService().queryBy(SanctionedEntities.class).getStates().get(0); SanctionedEntities oldStateData = oldList.getState().getData(); @@ -62,6 +55,8 @@ public StateAndRef call() throws FlowException { oldStateData.getLinearId() ); + Party notary = oldList.getState().getNotary(); + // Stage 1 progressTracker.setCurrentStep(GENERATING_TRANSACTION); //Create an unsigned transaction diff --git a/Features/referencestates-sanctionsbody/workflows/src/test/java/net/corda/samples/referencestates/referencestates/flows/IOUFlowTests.java b/Features/referencestates-sanctionsbody/workflows/src/test/java/net/corda/samples/referencestates/referencestates/flows/IOUFlowTests.java index 486db80b..8e26e20b 100644 --- a/Features/referencestates-sanctionsbody/workflows/src/test/java/net/corda/samples/referencestates/referencestates/flows/IOUFlowTests.java +++ b/Features/referencestates-sanctionsbody/workflows/src/test/java/net/corda/samples/referencestates/referencestates/flows/IOUFlowTests.java @@ -3,6 +3,7 @@ import com.google.common.collect.ImmutableList; import net.corda.core.contracts.TransactionVerificationException; import net.corda.core.flows.NotaryException; +import net.corda.core.identity.CordaX500Name; import net.corda.core.identity.Party; import net.corda.core.node.NetworkParameters; import net.corda.core.transactions.SignedTransaction; @@ -44,7 +45,7 @@ public void setup() { ImmutableList.of(new MockNetworkNotarySpec(DUMMY_NOTARY_NAME, false)), new NetworkParameters(4, emptyList(), 10484760, 10484760 * 50, Instant.now(), 1, emptyMap(), Duration.ofDays(30)), ImmutableList.of(findCordapp("net.corda.samples.referencestates.contracts")) - ); + ).withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))); network = new MockNetwork(param); diff --git a/Features/referencestates-sanctionsbody/workflows/src/test/java/net/corda/samples/referencestates/referencestates/flows/SanctionsFlowTests.java b/Features/referencestates-sanctionsbody/workflows/src/test/java/net/corda/samples/referencestates/referencestates/flows/SanctionsFlowTests.java index 0cb2c429..2a6ed823 100644 --- a/Features/referencestates-sanctionsbody/workflows/src/test/java/net/corda/samples/referencestates/referencestates/flows/SanctionsFlowTests.java +++ b/Features/referencestates-sanctionsbody/workflows/src/test/java/net/corda/samples/referencestates/referencestates/flows/SanctionsFlowTests.java @@ -2,9 +2,9 @@ import com.google.common.collect.ImmutableList; import net.corda.core.contracts.StateAndRef; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.referencestates.states.SanctionedEntities; -import net.corda.testing.node.MockNetwork; -import net.corda.testing.node.StartedMockNode; +import net.corda.testing.node.*; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -23,8 +23,11 @@ public class SanctionsFlowTests { @Before public void setup() { - network = new MockNetwork( - ImmutableList.of("net.corda.samples.referencestates.contracts")); + network = new MockNetwork(new MockNetworkParameters(ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.referencestates.contracts") + )).withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); + a = network.createPartyNode(null); b = network.createPartyNode(null); c = network.createPartyNode(null); diff --git a/Features/schedulablestate-heartbeat/README.md b/Features/schedulablestate-heartbeat/README.md index de7dba7c..31eaf612 100644 --- a/Features/schedulablestate-heartbeat/README.md +++ b/Features/schedulablestate-heartbeat/README.md @@ -21,14 +21,14 @@ In this way, calling the `StartHeartbeatFlow` creates an endless chain of `Heart ## Pre-Requisites -For development environment setup, please refer to: [Setup Guide](https://docs.corda.net/getting-set-up.html). +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.10/community/getting-set-up.html). ### Running the CorDapp Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) ``` -./gradlew clean deployNodes +./gradlew clean build deployNodes ``` Then type: (to run the nodes) ``` @@ -38,7 +38,7 @@ Then type: (to run the nodes) ### Interacting with the nodes: -Go to the [CRaSH](https://docs.corda.net/docs/corda-os/shell.html) shell for PartyA, and run the `StartHeatbeatFlow`: +Go to the interactive node shell for PartyA, and run the `StartHeatbeatFlow`: start StartHeartbeatFlow diff --git a/Features/schedulablestate-heartbeat/build.gradle b/Features/schedulablestate-heartbeat/build.gradle index eff91a21..f1f0c4f7 100644 --- a/Features/schedulablestate-heartbeat/build.gradle +++ b/Features/schedulablestate-heartbeat/build.gradle @@ -19,8 +19,8 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -36,12 +36,12 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } //SDK lib - maven { url 'https://software.r3.com/artifactory/corda-lib' } + maven { url 'https://download.corda.net/maven/corda-lib' } //Gradle Plugins maven { url 'https://repo.gradle.org/gradle/libs-releases' } } @@ -83,6 +83,8 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" + } task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { diff --git a/Features/schedulablestate-heartbeat/contracts/src/test/java/net/corda/samples/heartbeat/states/StateTests.java b/Features/schedulablestate-heartbeat/contracts/src/test/java/net/corda/samples/heartbeat/states/StateTests.java index 07b8826e..4f530b2d 100644 --- a/Features/schedulablestate-heartbeat/contracts/src/test/java/net/corda/samples/heartbeat/states/StateTests.java +++ b/Features/schedulablestate-heartbeat/contracts/src/test/java/net/corda/samples/heartbeat/states/StateTests.java @@ -6,8 +6,8 @@ import net.corda.testing.core.TestIdentity; import org.junit.Test; -import static org.jgroups.util.Util.assertFalse; -import static org.jgroups.util.Util.assertTrue; +import static org.wildfly.common.Assert.assertFalse; +import static org.wildfly.common.Assert.assertTrue; public class StateTests { diff --git a/Features/schedulablestate-heartbeat/workflows/src/main/java/net/corda/samples/heartbeat/flows/HeartbeatFlow.java b/Features/schedulablestate-heartbeat/workflows/src/main/java/net/corda/samples/heartbeat/flows/HeartbeatFlow.java index 18707b1f..ab5f90a4 100644 --- a/Features/schedulablestate-heartbeat/workflows/src/main/java/net/corda/samples/heartbeat/flows/HeartbeatFlow.java +++ b/Features/schedulablestate-heartbeat/workflows/src/main/java/net/corda/samples/heartbeat/flows/HeartbeatFlow.java @@ -1,6 +1,7 @@ package net.corda.samples.heartbeat.flows; import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.heartbeat.contracts.HeartContract; import net.corda.samples.heartbeat.states.HeartState; import net.corda.core.contracts.*; @@ -58,13 +59,7 @@ public String call() throws FlowException { CommandData beatCmd = new HeartContract.Commands.Beat(); // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); TransactionBuilder txBuilder = new TransactionBuilder(notary) .addInputState(input) diff --git a/Features/schedulablestate-heartbeat/workflows/src/main/java/net/corda/samples/heartbeat/flows/StartHeartbeatFlow.java b/Features/schedulablestate-heartbeat/workflows/src/main/java/net/corda/samples/heartbeat/flows/StartHeartbeatFlow.java index 230a0229..7c64507e 100644 --- a/Features/schedulablestate-heartbeat/workflows/src/main/java/net/corda/samples/heartbeat/flows/StartHeartbeatFlow.java +++ b/Features/schedulablestate-heartbeat/workflows/src/main/java/net/corda/samples/heartbeat/flows/StartHeartbeatFlow.java @@ -1,6 +1,7 @@ package net.corda.samples.heartbeat.flows; import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.heartbeat.contracts.HeartContract; import net.corda.samples.heartbeat.states.HeartState; import net.corda.core.contracts.CommandData; @@ -56,13 +57,7 @@ public Void call() throws FlowException { CommandData cmd = new HeartContract.Commands.Beat(); // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); TransactionBuilder txBuilder = new TransactionBuilder(notary) .addOutputState(output, HeartContract.contractID) diff --git a/Features/schedulablestate-heartbeat/workflows/src/test/java/net/corda/samples/heartbeat/flows/FlowTests.java b/Features/schedulablestate-heartbeat/workflows/src/test/java/net/corda/samples/heartbeat/flows/FlowTests.java index 27c7a162..a3dc50bc 100644 --- a/Features/schedulablestate-heartbeat/workflows/src/test/java/net/corda/samples/heartbeat/flows/FlowTests.java +++ b/Features/schedulablestate-heartbeat/workflows/src/test/java/net/corda/samples/heartbeat/flows/FlowTests.java @@ -1,11 +1,9 @@ package net.corda.samples.heartbeat.flows; import com.google.common.collect.ImmutableList; +import net.corda.core.identity.CordaX500Name; import net.corda.core.transactions.SignedTransaction; -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 net.corda.testing.node.*; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -28,7 +26,9 @@ public void setup() { TestCordapp.findCordapp("net.corda.samples.heartbeat.flows"), TestCordapp.findCordapp("net.corda.samples.heartbeat.contracts") ) - )); + ) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); node = network.createNode(); } diff --git a/Features/state-reissuance/.ci/Jenkinsfile b/Features/state-reissuance/.ci/Jenkinsfile new file mode 100644 index 00000000..7421cafe --- /dev/null +++ b/Features/state-reissuance/.ci/Jenkinsfile @@ -0,0 +1,37 @@ +#!groovy +/** + * Jenkins pipeline to build the java CorDapp template + */ + +/** + * Kill already started job. + * Assume new commit takes precedence and results from previousunfinished builds are not required. + * This feature doesn't play well with disableConcurrentBuilds() option + */ +@Library('corda-shared-build-pipeline-steps') +import static com.r3.build.BuildControl.killAllExistingBuildsForJob +killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) + +pipeline { + agent { + label 'eight-cores' + } + options { + ansiColor('xterm') + timestamps() + timeout(3*60) // 3 hours + buildDiscarder(logRotator(daysToKeepStr: '7', artifactDaysToKeepStr: '7')) + } + stages { + stage('Build') { + steps { + sh './gradlew --no-daemon -s clean build test deployNodes' + } + } + } + post { + cleanup { + deleteDir() + } + } +} diff --git a/Features/state-reissuance/.settings/org.eclipse.jdt.core.prefs b/Features/state-reissuance/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..ac86f90c --- /dev/null +++ b/Features/state-reissuance/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1 @@ +org.eclipse.jdt.core.compiler.codegen.methodParameters=generate \ No newline at end of file diff --git a/Features/state-reissuance/LICENCE b/Features/state-reissuance/LICENCE new file mode 100644 index 00000000..3ff572d1 --- /dev/null +++ b/Features/state-reissuance/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/Features/state-reissuance/README.md b/Features/state-reissuance/README.md new file mode 100644 index 00000000..f73c31d2 --- /dev/null +++ b/Features/state-reissuance/README.md @@ -0,0 +1,102 @@ +

+ Corda +

+ +# State Reissuance Sample CorDapp + +This CorDapp serves as a sample for state reissuance feature of Corda. This feature enables developers to break long +transaction backchains by reissuing a state with a guaranteed state replacement. This is particularly useful in situations +when a party doesn't want to share state history with other parties for privacy or performance concerns. + +This samples demonstrates the feature with the help of a linear state, represented by a land title issued on Corda ledger. +The land title can be transferred multiple times and when the transaction backchain becomes long, the land title could be +reissued and the transaction backchain could be pruned. + +# Pre-Requisites + +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.10/community/getting-set-up.html). + +# Usage + +## Running the CorDapp + +Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) + + ./gradlew clean build deployNodes + +Then type: (to run the nodes) + + ./build/nodes/runnodes + +## Interacting with the CorDapp + +PartyA issues a land title to PartyB, Go to PartyA's terminal and run the below command + + start IssueLandTitleFlow owner: PartyB, dimensions: 40X50, area: 1200sqft + +Verify the land title has been issued correctly by querying the ledgers of PartyA and PartyB using the below command. +PartyA should be issuer and PartyB should be the owner of the land title. + + run vaultQuery contractStateType: net.corda.samples.statereissuance.states.LandTitleState + +Once land title has been issued to PartyB, they could transfer it to PartyC. Go to PartyB's terminal and run the below command + + start TransferLandTitleFlow owner: PartyC, plotIdentifier: + +You could find the `plot-identifier` from the result of the vaultQuery command used earlier to query the ledgers. +![Screenshot](image/1.jpeg) + +Verify the land title has been correctly transferred to PartyC by querying the ledgers of PartyA and PartyC using the below command + + run vaultQuery contractStateType: net.corda.samples.statereissuance.states.LandTitleState + +Note that PartyB is no more able to see the land title, since they are no longer a party to the state as they have transferred +the title to PartyC. It is currently only visible to PartyA and PartyC. + +Consider that PartyC now wants to reissue the title to get rid of the backchain. They need to request a reissuance to the issuer. +Go to PartyC's terminal and run the below command + + start RequestReissueLandStateFlow issuer: PartyA, plotIdentifier: + +Now a reissuance request is created on the ledgers of PartyA and PartyC, when can be verified using the below command + + run vaultQuery contractStateType: com.r3.corda.lib.reissuance.states.ReissuanceRequest + +The issuer could either accept or reject the reissuance request. Let's consider the case where the issuer accepts the +reissuance request. To accept the request goto PartyA's (issuer) terminal and run the below command + + start AcceptLandReissuanceFlow issuer: PartyA, stateRef: {index: , txhash: } + +The `` and `` are of the transaction which created the state to the reissued. They can be found +in the `ReissuanceRequest` queried earlier. +![Screenshot](image/2.jpeg) + +On successful completion of the above flow, a duplicate land title would be issued, however it would be currently +locked, and it could not be spent. In order to spend it, the older state which was requested to be reissued must be exited +and that would allow the new reissued state to be unlocked and spend. + +To exit the older land title run the below command from PartyC's terminal. + + start ExitLandTitleFlow stateRef: {index: , txhash: } + +Once the previous land title is exited, unlock the reissued land title using the below command from PartyC's terminal + + start UnlockReissuedLandStateFlow reissuedRef: {index: , txhash: }, reissuanceLockRef: {index: , txhash: }, exitTrnxId: + +The `reissuedRef` is the stateRef of the reissued state. + ![Screenshot](image/3.jpeg) + + And the `reissuanceLockRef` is the stateRef of the reissuance lock generated, which can queried using `run vaultQuery contractStateType: com.r3.corda.lib.reissuance.states.ReissuanceLock` + ![Screenshot](image/4.jpeg) + And the `exitTrnxId` is the transaction hash of the transaction used to exit the older state. + ![Screenshot](image/5.jpeg) + +Note that the lock uses the encumbrance feature of Corda. You can check out the sample on encumbrance [here](https://github.com/corda/samples-java/tree/master/Features/encumbrance-avatar) + +Now with all the information input into the function call, the reissue process is completed and the reissued state can be spent freely. + +![Screenshot](image/6.jpeg) + +You can see the encumbrance field is restored to `null` again, meaning the state is free to be transacted again. +![Screenshot](image/7.jpeg) + diff --git a/Features/state-reissuance/TRADEMARK b/Features/state-reissuance/TRADEMARK new file mode 100644 index 00000000..d2e056b5 --- /dev/null +++ b/Features/state-reissuance/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/Features/state-reissuance/build.gradle b/Features/state-reissuance/build.gradle new file mode 100644 index 00000000..23ed2f31 --- /dev/null +++ b/Features/state-reissuance/build.gradle @@ -0,0 +1,180 @@ +buildscript {//properties that you need to build the project + Properties constants = new Properties() + file("$projectDir/../constants.properties").withInputStream { constants.load(it) } + + ext { + corda_release_group = constants.getProperty("cordaReleaseGroup") + corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") + corda_release_version = constants.getProperty("cordaVersion") + corda_core_release_version = constants.getProperty("cordaCoreVersion") + corda_gradle_plugins_version = constants.getProperty("gradlePluginsVersion") + reissuance_release_group = 'com.r3.corda.lib.reissuance' + reissuance_release_version = '1.0-GA' + confidential_id_release_group = 'com.r3.corda.lib.ci' + confidential_id_release_version = '1.0' + tokens_release_version = '1.2' + tokens_release_group = 'com.r3.corda.lib.tokens' + 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' + } + + repositories { + mavenLocal() + mavenCentral() + maven { url 'https://download.corda.net/maven/corda-releases' } + maven { url 'https://download.corda.net/maven/corda-lib' } + } + + 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.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_gradle_plugin_version" + } +} + +allprojects {//Properties that you need to compile your project (The application) + apply from: "${rootProject.projectDir}/repositories.gradle" + apply plugin: 'java' + apply plugin: 'kotlin' + + repositories { + mavenLocal() + mavenCentral() + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://download.corda.net/maven/corda-lib' } + maven { url 'https://jitpack.io' } + + } + + tasks.withType(JavaCompile) { + options.compilerArgs << "-parameters" // Required by Corda's serialisation framework. + } + + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { + kotlinOptions { + languageVersion = "1.2" + apiVersion = "1.2" + jvmTarget = "1.8" + javaParameters = true // Useful for reflection. + } + } + + jar { + // This makes the JAR's SHA-256 hash repeatable. + preserveFileTimestamps = false + reproducibleFileOrder = true + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} + +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") + } + } +} + +//Module dependencis +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_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") + cordapp "$reissuance_release_group:reissuance-cordapp-contracts:$reissuance_release_version" + cordapp "$reissuance_release_group:reissuance-cordapp-workflows:$reissuance_release_version" + + // Token SDK dependencies. + cordapp "$tokens_release_group:tokens-contracts:$tokens_release_version" + cordapp "$tokens_release_group:tokens-workflows:$tokens_release_version" + + // CI dependencies. + cordapp "$confidential_id_release_group:ci-workflows:$confidential_id_release_version" + + 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" + cordaDriver "net.corda:corda-shell:4.10" + +} + + +//Task to deploy the nodes in order to bootstrap a network +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + + /* This property will load the CorDapps to each of the node by default, including the Notary. You can find them + * in the cordapps folder of the node at build/nodes/Notary/cordapps. However, the notary doesn't really understand + * the notion of cordapps. In production, Notary does not need cordapps as well. This is just a short cut to load + * the Corda network bootstrapper. + */ + nodeDefaults { + projectCordapp { + deploy = false + } + cordapp project(':contracts') + cordapp project(':workflows') + cordapp "$reissuance_release_group:reissuance-cordapp-contracts:$reissuance_release_version" + cordapp "$reissuance_release_group:reissuance-cordapp-workflows:$reissuance_release_version" + cordapp "$tokens_release_group:tokens-contracts:$tokens_release_version" + cordapp "$tokens_release_group:tokens-workflows:$tokens_release_version" + + cordapp "$confidential_id_release_group:ci-workflows:$confidential_id_release_version" + runSchemaMigration = true //This configuration is for any CorDapps with custom schema, We will leave this as true to avoid + //problems for developers who are not familiar with Corda. If you are not using custom schemas, you can change + //it to false for quicker project compiling time. + } + node { + name "O=Notary,L=London,C=GB" + notary = [validating : false] + p2pPort 10002 + rpcSettings { + address("localhost:10003") + adminAddress("localhost:10043") + } + } + 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=New York,C=US" + p2pPort 10011 + rpcSettings { + address("localhost:10012") + adminAddress("localhost:10052") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } +} diff --git a/Features/state-reissuance/config/dev/log4j2.xml b/Features/state-reissuance/config/dev/log4j2.xml new file mode 100644 index 00000000..34ba4d45 --- /dev/null +++ b/Features/state-reissuance/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/Features/state-reissuance/config/test/log4j2.xml b/Features/state-reissuance/config/test/log4j2.xml new file mode 100644 index 00000000..cd9926ca --- /dev/null +++ b/Features/state-reissuance/config/test/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n + > + + + + + + + + + + + + diff --git a/Features/state-reissuance/contracts/build.gradle b/Features/state-reissuance/contracts/build.gradle new file mode 100644 index 00000000..8c18caad --- /dev/null +++ b/Features/state-reissuance/contracts/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + contract { + name "State Re-Issuance Contracts" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main{ + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + } + test{ + java{ + srcDir 'src/test/java' + java.outputDir = file('bin/test') + } + } +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_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/Features/state-reissuance/contracts/src/main/java/net/corda/samples/statereissuance/contracts/LandTitleContract.java b/Features/state-reissuance/contracts/src/main/java/net/corda/samples/statereissuance/contracts/LandTitleContract.java new file mode 100644 index 00000000..1219e5df --- /dev/null +++ b/Features/state-reissuance/contracts/src/main/java/net/corda/samples/statereissuance/contracts/LandTitleContract.java @@ -0,0 +1,73 @@ +package net.corda.samples.statereissuance.contracts; + +import net.corda.core.contracts.Command; +import net.corda.samples.statereissuance.states.LandTitleState; +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; + +import static net.corda.core.contracts.ContractsDSL.requireThat; + +// ************ +// * Contract * +// ************ +public class LandTitleContract implements Contract { + + // A transaction is valid if the verify() function of the contract of all the transaction's input and output states + // does not throw an exception. + @Override + public void verify(LedgerTransaction tx) { + if(tx.getCommands().size() == 0){ + throw new IllegalArgumentException("One command Expected"); + } + + Command command = tx.getCommand(0); + if(command.getValue() instanceof Commands.Issue) + verifyIssue(tx); + + else if(command.getValue() instanceof Commands.Transfer) + verifyTransfer(tx); + + else if (command.getValue() instanceof Commands.Exit) + verifyExit(tx); + } + + private void verifyIssue(LedgerTransaction tx){ + // Land Title Issue Contract Verification Logic goes here + if(tx.getOutputStates().size() != 1) throw new IllegalArgumentException("One Output Expected"); + + Command command = tx.getCommand(0); + if(!(command.getSigners().contains(((LandTitleState)tx.getOutput(0)).getIssuer().getOwningKey()))) + throw new IllegalArgumentException("Issuer Signature Required"); + } + + private void verifyTransfer(LedgerTransaction tx){ + // Land Title Transfer Contract Verification Logic goes here + Command command = tx.getCommand(0); + LandTitleState landTitleState = (LandTitleState) tx.getInput(0); + if (!(command.getSigners().contains(landTitleState.getIssuer().getOwningKey())) && + (landTitleState.getOwner() != null + && command.getSigners().contains(landTitleState.getOwner().getOwningKey()))) + throw new IllegalArgumentException("Issuer and Owner must Sign"); + } + + private void verifyExit(LedgerTransaction tx){ + // Land Title Exit Contract Verification Logic goes here + + if(tx.getOutputStates().size() != 0) throw new IllegalArgumentException("Zero Output Expected"); + if(tx.getInputStates().size() != 1) throw new IllegalArgumentException("One Input Expected"); + + Command command = tx.getCommand(0); + if(!(command.getSigners().contains(((LandTitleState)tx.getInput(0)).getIssuer().getOwningKey()))) + throw new IllegalArgumentException("Issuer Signature Required"); + } + + + // Used to indicate the transaction's intent. + public interface Commands extends CommandData { + class Issue implements Commands {} + class Transfer implements Commands {} + class Exit implements Commands {} + class Reissue implements Commands {} + } +} \ No newline at end of file diff --git a/Features/state-reissuance/contracts/src/main/java/net/corda/samples/statereissuance/states/LandTitleState.java b/Features/state-reissuance/contracts/src/main/java/net/corda/samples/statereissuance/states/LandTitleState.java new file mode 100644 index 00000000..2434d1e6 --- /dev/null +++ b/Features/state-reissuance/contracts/src/main/java/net/corda/samples/statereissuance/states/LandTitleState.java @@ -0,0 +1,82 @@ +package net.corda.samples.statereissuance.states; + +import net.corda.core.contracts.LinearState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.samples.statereissuance.contracts.LandTitleContract; +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.Arrays; +import java.util.List; +import java.util.Objects; + +// ********* +// * State * +// ********* +@BelongsToContract(LandTitleContract.class) +public class LandTitleState implements LinearState { + + /* This is the unique identifier of the property */ + private UniqueIdentifier plotIdentifier; + private String dimensions; + private String area; + + private Party owner; + private Party issuer; + + /* Constructor of our Corda state */ + public LandTitleState(UniqueIdentifier plotIdentifier, String dimensions, String area, Party owner, Party issuer) { + this.plotIdentifier = plotIdentifier; + this.dimensions = dimensions; + this.area = area; + this.owner = owner; + this.issuer = issuer; + } + + //Getters + + public Party getOwner() { + return owner; + } + + public Party getIssuer() { + return issuer; + } + + public String getDimensions() { + return dimensions; + } + + public String getArea() { + return area; + } + + /* This method will indicate who are the participants and required signers when + * this state is used in a transaction. */ + @NotNull + @Override + public List getParticipants() { + return Arrays.asList(issuer, owner); + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return plotIdentifier; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + return plotIdentifier.equals(((LandTitleState) o).plotIdentifier); + } + + @Override + public int hashCode() { + return plotIdentifier.hashCode(); + } +} \ No newline at end of file diff --git a/Features/state-reissuance/contracts/src/test/java/net/corda/samples/statereissuance/contracts/ContractTests.java b/Features/state-reissuance/contracts/src/test/java/net/corda/samples/statereissuance/contracts/ContractTests.java new file mode 100644 index 00000000..77e7e50a --- /dev/null +++ b/Features/state-reissuance/contracts/src/test/java/net/corda/samples/statereissuance/contracts/ContractTests.java @@ -0,0 +1,20 @@ +package net.corda.samples.statereissuance.contracts; + +import net.corda.samples.statereissuance.states.LandTitleState; +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.node.MockServices; +import org.junit.Test; + +import java.util.Arrays; + +import static net.corda.testing.node.NodeTestUtils.ledger; + + +public class ContractTests { + private final MockServices ledgerServices = new MockServices(Arrays.asList("net.corda.samples.statereissuance")); + TestIdentity alice = new TestIdentity(new CordaX500Name("Alice", "TestLand", "US")); + TestIdentity bob = new TestIdentity(new CordaX500Name("Alice", "TestLand", "US")); + + +} \ No newline at end of file diff --git a/Features/state-reissuance/contracts/src/test/java/net/corda/samples/statereissuance/contracts/StateTests.java b/Features/state-reissuance/contracts/src/test/java/net/corda/samples/statereissuance/contracts/StateTests.java new file mode 100644 index 00000000..e8f5113b --- /dev/null +++ b/Features/state-reissuance/contracts/src/test/java/net/corda/samples/statereissuance/contracts/StateTests.java @@ -0,0 +1,10 @@ +package net.corda.samples.statereissuance.contracts; + +import net.corda.samples.statereissuance.states.LandTitleState; +import org.junit.Test; + +public class StateTests { + + //Mock State test check for if the state has correct parameters type + +} \ No newline at end of file diff --git a/Features/state-reissuance/gradle.properties b/Features/state-reissuance/gradle.properties new file mode 100644 index 00000000..1f0a9254 --- /dev/null +++ b/Features/state-reissuance/gradle.properties @@ -0,0 +1,3 @@ +name=Test +group=com.statereissuance +version=0.1 \ No newline at end of file diff --git a/Features/state-reissuance/gradle/wrapper/gradle-wrapper.jar b/Features/state-reissuance/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..99340b4a Binary files /dev/null and b/Features/state-reissuance/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Features/state-reissuance/gradle/wrapper/gradle-wrapper.properties b/Features/state-reissuance/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..0188225a --- /dev/null +++ b/Features/state-reissuance/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +#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-5.6.4-all.zip + diff --git a/Features/state-reissuance/gradlew b/Features/state-reissuance/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/Features/state-reissuance/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/Features/state-reissuance/gradlew.bat b/Features/state-reissuance/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/Features/state-reissuance/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/Features/state-reissuance/image/1.jpeg b/Features/state-reissuance/image/1.jpeg new file mode 100644 index 00000000..e905727a Binary files /dev/null and b/Features/state-reissuance/image/1.jpeg differ diff --git a/Features/state-reissuance/image/2.jpeg b/Features/state-reissuance/image/2.jpeg new file mode 100644 index 00000000..bc28719e Binary files /dev/null and b/Features/state-reissuance/image/2.jpeg differ diff --git a/Features/state-reissuance/image/3.jpeg b/Features/state-reissuance/image/3.jpeg new file mode 100644 index 00000000..21df8109 Binary files /dev/null and b/Features/state-reissuance/image/3.jpeg differ diff --git a/Features/state-reissuance/image/4.jpeg b/Features/state-reissuance/image/4.jpeg new file mode 100644 index 00000000..fe79a03a Binary files /dev/null and b/Features/state-reissuance/image/4.jpeg differ diff --git a/Features/state-reissuance/image/5.jpeg b/Features/state-reissuance/image/5.jpeg new file mode 100644 index 00000000..3ecd6895 Binary files /dev/null and b/Features/state-reissuance/image/5.jpeg differ diff --git a/Features/state-reissuance/image/6.jpeg b/Features/state-reissuance/image/6.jpeg new file mode 100644 index 00000000..ec48f5fe Binary files /dev/null and b/Features/state-reissuance/image/6.jpeg differ diff --git a/Features/state-reissuance/image/7.jpeg b/Features/state-reissuance/image/7.jpeg new file mode 100644 index 00000000..277c0be9 Binary files /dev/null and b/Features/state-reissuance/image/7.jpeg differ diff --git a/Features/state-reissuance/repositories.gradle b/Features/state-reissuance/repositories.gradle new file mode 100644 index 00000000..9797c0ea --- /dev/null +++ b/Features/state-reissuance/repositories.gradle @@ -0,0 +1,7 @@ +repositories { + mavenLocal() + mavenCentral() + maven { url 'https://jitpack.io' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } +} diff --git a/Features/state-reissuance/settings.gradle b/Features/state-reissuance/settings.gradle new file mode 100644 index 00000000..b4446eaf --- /dev/null +++ b/Features/state-reissuance/settings.gradle @@ -0,0 +1,2 @@ +include 'workflows' +include 'contracts' \ No newline at end of file diff --git a/Features/state-reissuance/workflows/build.gradle b/Features/state-reissuance/workflows/build.gradle new file mode 100644 index 00000000..bc6879c8 --- /dev/null +++ b/Features/state-reissuance/workflows/build.gradle @@ -0,0 +1,66 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + workflow { + name "State Re-Issuance Flows" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/dev") + } + } + test { + java { + srcDir 'src/test/java' + java.outputDir = file('bin/main') + } + 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_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":contracts") + cordapp "$reissuance_release_group:reissuance-cordapp-contracts:$reissuance_release_version" + cordapp "$reissuance_release_group:reissuance-cordapp-workflows:$reissuance_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/Features/state-reissuance/workflows/src/integrationTest/java/net/corda/samples/statereissuance/DriverBasedTest.java b/Features/state-reissuance/workflows/src/integrationTest/java/net/corda/samples/statereissuance/DriverBasedTest.java new file mode 100644 index 00000000..4173231d --- /dev/null +++ b/Features/state-reissuance/workflows/src/integrationTest/java/net/corda/samples/statereissuance/DriverBasedTest.java @@ -0,0 +1,48 @@ +package net.corda.samples.statereissuance; + +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/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/ExitLandTitleFlow.java b/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/ExitLandTitleFlow.java new file mode 100644 index 00000000..6e08b56b --- /dev/null +++ b/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/ExitLandTitleFlow.java @@ -0,0 +1,81 @@ +package net.corda.samples.statereissuance.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.StateRef; +import net.corda.core.flows.*; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.statereissuance.contracts.LandTitleContract; +import net.corda.samples.statereissuance.states.LandTitleState; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class ExitLandTitleFlow { + + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic { + + private StateRef stateRef; + + public Initiator(StateRef stateRef) { + this.stateRef = stateRef; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + List> landTitleStateAndRefs = getServiceHub().getVaultService() + .queryBy(LandTitleState.class).getStates(); + + StateAndRef inputStateAndRef = landTitleStateAndRefs.stream() + .filter(landTitleStateStateAndRef -> landTitleStateStateAndRef.getRef().equals(stateRef)) + .findAny().orElseThrow(() -> new IllegalArgumentException("Land Title Not Found")); + + LandTitleState input = inputStateAndRef.getState().getData(); + + TransactionBuilder builder = new TransactionBuilder(inputStateAndRef.getState().getNotary()) + .addInputState(inputStateAndRef) + .addCommand(new LandTitleContract.Commands.Exit(), Arrays.asList(getOurIdentity().getOwningKey(), + input.getIssuer().getOwningKey())); + + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + FlowSession issuerSession = initiateFlow(input.getIssuer()); + + SignedTransaction stx = subFlow(new CollectSignaturesFlow(ptx, Collections.singletonList(issuerSession))); + return subFlow(new FinalityFlow(stx, Collections.singletonList(issuerSession))); + } + } + + @InitiatedBy(Initiator.class) + public static class Responder extends FlowLogic{ + + private FlowSession counterpartySession; + + public Responder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Override + @Suspendable + public Void call() throws FlowException { + + subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + + } + }); + + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession)); + return null; + } + } +} diff --git a/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/IssueLandTitleFlow.java b/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/IssueLandTitleFlow.java new file mode 100644 index 00000000..2ce669e9 --- /dev/null +++ b/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/IssueLandTitleFlow.java @@ -0,0 +1,77 @@ +package net.corda.samples.statereissuance.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.statereissuance.contracts.LandTitleContract; +import net.corda.samples.statereissuance.states.LandTitleState; + +import java.util.Arrays; + +public class IssueLandTitleFlow { + + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic{ + + //private variables + private Party owner; + private String dimensions; + private String area; + + + //public constructor + public Initiator(Party owner,String dimensions, String area) { + this.owner = owner; + this.dimensions = dimensions; + this.area = area; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + // Get a reference to the notary service on our network and our key pair. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + Party issuer = getOurIdentity(); + + LandTitleState landTitleState = new LandTitleState(new UniqueIdentifier(), dimensions, area, owner, issuer); + + final TransactionBuilder builder = new TransactionBuilder(notary) + .addOutputState(landTitleState) + .addCommand(new LandTitleContract.Commands.Issue(), Arrays.asList(issuer.getOwningKey())); + + // Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + // Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(ptx, Arrays.asList(initiateFlow(owner)))); + } + } + + @InitiatedBy(Initiator.class) + public static class Responder extends FlowLogic{ + //private variable + private FlowSession counterpartySession; + + //Constructor + public Responder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession)); + return null; + } + } + +} \ No newline at end of file diff --git a/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/TransferLandTitleFlow.java b/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/TransferLandTitleFlow.java new file mode 100644 index 00000000..4b472e58 --- /dev/null +++ b/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/TransferLandTitleFlow.java @@ -0,0 +1,98 @@ +package net.corda.samples.statereissuance.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.statereissuance.contracts.LandTitleContract; +import net.corda.samples.statereissuance.states.LandTitleState; + +import java.util.Arrays; +import java.util.List; + +public class TransferLandTitleFlow { + + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic { + + private UniqueIdentifier plotIdentifier; + private Party owner; + + public Initiator(UniqueIdentifier plotIdentifier, Party owner) { + this.plotIdentifier = plotIdentifier; + this.owner = owner; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + List> landTitleStateAndRefs = getServiceHub().getVaultService() + .queryBy(LandTitleState.class).getStates(); + + StateAndRef inputStateAndRef = landTitleStateAndRefs.stream().filter(landTitleStateStateAndRef -> { + LandTitleState loanBidState = landTitleStateStateAndRef.getState().getData(); + return loanBidState.getLinearId().equals(plotIdentifier); + }).findAny().orElseThrow(() -> new IllegalArgumentException("Land Title Not Found")); + + LandTitleState inputState = inputStateAndRef.getState().getData(); + LandTitleState outputState = new LandTitleState(inputState.getLinearId(), inputState.getDimensions(), + inputState.getArea(), owner, inputState.getIssuer()); + + TransactionBuilder transactionBuilder = new TransactionBuilder(inputStateAndRef.getState().getNotary()) + .addInputState(inputStateAndRef) + .addOutputState(outputState) + .addCommand(new LandTitleContract.Commands.Transfer(), Arrays.asList( + inputState.getIssuer().getOwningKey(), + inputState.getOwner().getOwningKey() + )); + + transactionBuilder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(transactionBuilder); + + FlowSession issuerSession = initiateFlow(inputState.getIssuer()); + issuerSession.send(true); + FlowSession newOwnerSession = initiateFlow(owner); + newOwnerSession.send(false); + + SignedTransaction stx = subFlow(new CollectSignaturesFlow(ptx, Arrays.asList(issuerSession))); + return subFlow(new FinalityFlow(stx, Arrays.asList(issuerSession, newOwnerSession))); + } + } + + + @InitiatedBy(Initiator.class) + public static class Responder extends FlowLogic{ + //private variable + private FlowSession counterpartySession; + + //Constructor + public Responder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + + boolean signRequired = counterpartySession.receive(Boolean.class).unwrap(it -> it); + if(signRequired) { + subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + + } + }); + } + + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession)); + return null; + } + } +} diff --git a/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/reissuance/AcceptLandReissuanceFlow.java b/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/reissuance/AcceptLandReissuanceFlow.java new file mode 100644 index 00000000..a92df046 --- /dev/null +++ b/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/reissuance/AcceptLandReissuanceFlow.java @@ -0,0 +1,47 @@ +package net.corda.samples.statereissuance.flows.reissuance; + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.reissuance.flows.ReissueStates; +import com.r3.corda.lib.reissuance.states.ReissuanceRequest; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.StateRef; +import net.corda.core.crypto.SecureHash; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.InitiatingFlow; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.identity.Party; +import net.corda.samples.statereissuance.flows.ExitLandTitleFlow; + +import java.util.Arrays; +import java.util.List; + +@InitiatingFlow +@StartableByRPC +public class AcceptLandReissuanceFlow extends FlowLogic { + + private StateRef stateRef; + private Party issuer; + + public AcceptLandReissuanceFlow(StateRef stateRef, Party issuer) { + this.stateRef = stateRef; + this.issuer = issuer; + } + + @Override + @Suspendable + public SecureHash call() throws FlowException { + + List> reissueRequestStateAndRefs = getServiceHub().getVaultService() + .queryBy(ReissuanceRequest.class).getStates(); + + StateAndRef stateAndRef = reissueRequestStateAndRefs.stream().filter(reissuanceRequestStateAndRef -> { + ReissuanceRequest reissuanceRequest = reissuanceRequestStateAndRef.getState().getData(); + return reissuanceRequest.getStateRefsToReissue().contains(stateRef); + }).findAny().orElseThrow(() -> new IllegalArgumentException("ReIssuance Request does not exist")); + + SecureHash txHash = subFlow(new ReissueStates<>(stateAndRef, Arrays.asList(issuer))); + + return txHash; + } +} diff --git a/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/reissuance/RejectLandReissuanceFlow.java b/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/reissuance/RejectLandReissuanceFlow.java new file mode 100644 index 00000000..7137e4b9 --- /dev/null +++ b/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/reissuance/RejectLandReissuanceFlow.java @@ -0,0 +1,41 @@ +package net.corda.samples.statereissuance.flows.reissuance; + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.reissuance.flows.RejectReissuanceRequest; +import com.r3.corda.lib.reissuance.states.ReissuanceRequest; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.StateRef; +import net.corda.core.crypto.SecureHash; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.InitiatingFlow; +import net.corda.core.flows.StartableByRPC; + +import java.util.List; + +@InitiatingFlow +@StartableByRPC +public class RejectLandReissuanceFlow extends FlowLogic { + + private StateRef stateRef; + + public RejectLandReissuanceFlow(StateRef stateRef) { + this.stateRef = stateRef; + } + + @Override + @Suspendable + public SecureHash call() throws FlowException { + + List> reissueRequestStateAndRefs = getServiceHub().getVaultService() + .queryBy(ReissuanceRequest.class).getStates(); + + StateAndRef stateAndRef = reissueRequestStateAndRefs.stream().filter(reissuanceRequestStateAndRef -> { + ReissuanceRequest reissuanceRequest = reissuanceRequestStateAndRef.getState().getData(); + return reissuanceRequest.getStateRefsToReissue().contains(stateRef); + }).findAny().orElseThrow(() -> new IllegalArgumentException("ReIssuance Request does not exist")); + + SecureHash txHash = subFlow(new RejectReissuanceRequest<>(stateAndRef)); + return txHash; + } +} diff --git a/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/reissuance/RequestReissueLandStateFlow.java b/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/reissuance/RequestReissueLandStateFlow.java new file mode 100644 index 00000000..6fb9d62b --- /dev/null +++ b/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/reissuance/RequestReissueLandStateFlow.java @@ -0,0 +1,59 @@ +package net.corda.samples.statereissuance.flows.reissuance; + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.reissuance.flows.RequestReissuance; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.StateRef; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.crypto.SecureHash; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.InitiatingFlow; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.identity.Party; +import net.corda.samples.statereissuance.contracts.LandTitleContract; +import net.corda.samples.statereissuance.states.LandTitleState; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@InitiatingFlow +@StartableByRPC +public class RequestReissueLandStateFlow extends FlowLogic{ + + private Party issuer; + private UniqueIdentifier plotIdentifier; + + + public RequestReissueLandStateFlow(Party issuer, UniqueIdentifier plotIdentifier) { + this.issuer = issuer; + this.plotIdentifier = plotIdentifier; + } + + @Override + @Suspendable + public SecureHash call() throws FlowException { + + List> landTitleStateAndRefs = getServiceHub().getVaultService() + .queryBy(LandTitleState.class).getStates(); + + StateAndRef stateAndRef = landTitleStateAndRefs.stream().filter(landTitleStateAndRef -> { + LandTitleState landTitleState = landTitleStateAndRef.getState().getData(); + return landTitleState.getLinearId().equals(plotIdentifier); + }).findAny().orElseThrow(() -> new IllegalArgumentException("Land Not Found")); + + + SecureHash txId = subFlow(new RequestReissuance( + issuer, + Arrays.asList(stateAndRef.getRef()), + new LandTitleContract.Commands.Issue(), + Collections.EMPTY_LIST, + null) + ); + + return txId; + } + +} diff --git a/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/reissuance/kotlin/AcceptLandReissuanceResponder.kt b/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/reissuance/kotlin/AcceptLandReissuanceResponder.kt new file mode 100644 index 00000000..8d298c7b --- /dev/null +++ b/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/reissuance/kotlin/AcceptLandReissuanceResponder.kt @@ -0,0 +1,19 @@ +package net.corda.samples.statereissuance.flows.reissuance.kotlin + +import net.corda.core.flows.InitiatedBy +import com.r3.corda.lib.reissuance.flows.ReissueStates +import net.corda.core.flows.FlowSession +import com.r3.corda.lib.reissuance.flows.ReissueStatesResponder +import net.corda.core.transactions.SignedTransaction + +// Added Kotlin code because of a bug which doesnot allow to write Java + +@InitiatedBy(ReissueStates::class) +class AcceptLandReissuanceResponder( + otherSession: FlowSession +) : ReissueStatesResponder(otherSession) { + + override fun checkConstraints(stx: SignedTransaction) { + + } +} \ No newline at end of file diff --git a/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/reissuance/kotlin/UnlockReissuedLandStateFlow.kt b/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/reissuance/kotlin/UnlockReissuedLandStateFlow.kt new file mode 100644 index 00000000..fc108567 --- /dev/null +++ b/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/reissuance/kotlin/UnlockReissuedLandStateFlow.kt @@ -0,0 +1,41 @@ +package net.corda.samples.statereissuance.flows.reissuance.kotlin + +import co.paralleluniverse.fibers.Suspendable +import com.r3.corda.lib.reissuance.flows.UnlockReissuedStates +import com.r3.corda.lib.reissuance.states.ReissuanceLock +import net.corda.core.contracts.StateRef +import net.corda.core.flows.StartableByRPC +import net.corda.core.crypto.SecureHash +import net.corda.core.flows.FlowLogic +import net.corda.samples.statereissuance.states.LandTitleState +import net.corda.core.node.services.queryBy +import net.corda.core.node.services.vault.QueryCriteria +import net.corda.samples.statereissuance.contracts.LandTitleContract + + +@StartableByRPC +class UnlockReissuedLandStateFlow( + private val reissuedRef: StateRef, + private val reissuanceLockRef: StateRef, + private val exitTrnxId: SecureHash +): FlowLogic() { + + @Suspendable + override fun call(): SecureHash { + + val reissuanceLockStateAndRef = serviceHub.vaultService.queryBy>( + criteria= QueryCriteria.VaultQueryCriteria(stateRefs = listOf(reissuanceLockRef)) + ).states[0] + + val reissuedStatesStateAndRefs = serviceHub.vaultService.queryBy( + criteria= QueryCriteria.VaultQueryCriteria(stateRefs = listOf(reissuedRef)) + ).states + + return subFlow(UnlockReissuedStates( + reissuedStatesStateAndRefs, + reissuanceLockStateAndRef, + listOf(exitTrnxId), + LandTitleContract.Commands.Reissue() + )) + } +} \ No newline at end of file diff --git a/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/reissuance/kotlin/UnlockReissuedLandStateResponder.kt b/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/reissuance/kotlin/UnlockReissuedLandStateResponder.kt new file mode 100644 index 00000000..b01104fa --- /dev/null +++ b/Features/state-reissuance/workflows/src/main/java/net/corda/samples/statereissuance/flows/reissuance/kotlin/UnlockReissuedLandStateResponder.kt @@ -0,0 +1,18 @@ +package net.corda.samples.statereissuance.flows.reissuance.kotlin + + +import net.corda.core.flows.InitiatedBy +import com.r3.corda.lib.reissuance.flows.UnlockReissuedStates +import com.r3.corda.lib.reissuance.flows.UnlockReissuedStatesResponder +import net.corda.core.flows.FlowSession +import net.corda.core.transactions.SignedTransaction + +@InitiatedBy(UnlockReissuedStates::class) +class UnlockReissuedLandStateResponder( + otherSession: FlowSession +) : UnlockReissuedStatesResponder(otherSession) { + + override fun checkConstraints(stx: SignedTransaction) { + + } +} \ No newline at end of file diff --git a/Features/state-reissuance/workflows/src/test/java/net/corda/samples/statereissuance/FlowTests.java b/Features/state-reissuance/workflows/src/test/java/net/corda/samples/statereissuance/FlowTests.java new file mode 100644 index 00000000..1084b1cc --- /dev/null +++ b/Features/state-reissuance/workflows/src/test/java/net/corda/samples/statereissuance/FlowTests.java @@ -0,0 +1,81 @@ +package net.corda.samples.statereissuance; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.samples.statereissuance.flows.IssueLandTitleFlow; +import net.corda.samples.statereissuance.flows.TransferLandTitleFlow; +import net.corda.samples.statereissuance.states.LandTitleState; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class FlowTests { + private MockNetwork network; + private StartedMockNode a; + private StartedMockNode b; + private StartedMockNode c; + + @Before + public void setup() { + network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.statereissuance.contracts"), + TestCordapp.findCordapp("net.corda.samples.statereissuance.flows"))) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB"))))); + a = network.createPartyNode(null); + b = network.createPartyNode(null); + c = network.createPartyNode(null); + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void issueTest() throws Exception{ + LandTitleState landTitleState = issueLandTitle(a, b.getInfo().getLegalIdentities().get(0)); + assertNotNull(landTitleState); + assertEquals(landTitleState.getIssuer(), a.getInfo().getLegalIdentities().get(0)); + assertEquals(landTitleState.getOwner(), b.getInfo().getLegalIdentities().get(0)); + } + + @Test + public void transferTest() throws Exception{ + LandTitleState landTitleState = issueLandTitle(a, b.getInfo().getLegalIdentities().get(0)); + LandTitleState updatedLandTitleState = transferLandTitle(landTitleState, c.getInfo().getLegalIdentities().get(0), b); + assertNotNull(updatedLandTitleState); + assertEquals(updatedLandTitleState.getIssuer(), a.getInfo().getLegalIdentities().get(0)); + assertEquals(updatedLandTitleState.getOwner(), c.getInfo().getLegalIdentities().get(0)); + } + + private LandTitleState issueLandTitle(StartedMockNode issuer, Party owner) throws Exception{ + IssueLandTitleFlow.Initiator flow = new IssueLandTitleFlow.Initiator( + owner, "60X40", "2000sqft" + ); + CordaFuture future = issuer.startFlow(flow); + network.runNetwork(); + SignedTransaction transaction = future.get(); + LandTitleState landTitleState = (LandTitleState) transaction.getTx().getOutput(0); + return landTitleState; + } + + private LandTitleState transferLandTitle(LandTitleState landTitleState, Party newOwner, StartedMockNode owner) throws Exception { + TransferLandTitleFlow.Initiator flow = new TransferLandTitleFlow.Initiator( + landTitleState.getLinearId(), newOwner + ); + CordaFuture future = owner.startFlow(flow); + network.runNetwork(); + SignedTransaction transaction = future.get(); + LandTitleState updatedLandTitleState = (LandTitleState) transaction.getTx().getOutput(0); + return updatedLandTitleState; + } + +} diff --git a/README.md b/README.md index f7bc3f98..3bb562c5 100644 --- a/README.md +++ b/README.md @@ -6,66 +6,16 @@ ## Introduction This repository contains multiple sample apps, from CorDapps that help you get started, all the way to demonstrating specific features and advanced usage. -If you are new to Corda and/or would like to learn all of the fundamentals in a guided and incremental manner please visit the new -[Corda Training Site](https://training.corda.net) +If you are new to Corda and/or would like to learn all of the fundamentals in a guided and incremental manner please visit the Corda [documentation](https://docs.r3.com/) site. To get started explore the [Basic](./Basic) folder, or navigate to the [Advanced](./Advanced) and [Features](./Features) folders to see a description of whats available. You can find the exact same set of CorDapp demonstration in Kotlin language at [link](https://github.com/corda/samples-kotlin). ## Directories The samples are divided into 5 sections with the following desciption: -* [Accounts](./Accounts): These samples showcases how to utilize [Corda Accounts Libray](https://training.corda.net/libraries/accounts-lib/) to build CorDapps which aim to have massive user volume -* [Advanced](./Advanced): In these samples, we demonstrate more complex and sophisticated [features](https://training.corda.net/corda-details/introduction/) of Corda. -* [Basic](./Basic): They demonstrate [fundamental](https://training.corda.net/key-concepts/concepts/) and useful techniques for CorDapp development. -* [Features](./Features): These samples demonstrate specific Corda [functionalities](https://training.corda.net/corda-details/introduction/). -* [Tokens](./Tokens): These include TokenSDK related samples. Learn more at [TokenSDK](https://training.corda.net/libraries/tokens-sdk/). - - - -``` -. -├── Accounts -│   ├── README.md -│   ├── constants.properties -│   ├── supplychain -│   └── tictacthor -├── Advanced -│   ├── README.md -│   ├── auction-cordapp -│   ├── constants.properties -│   ├── negotiation-cordapp -│   ├── obligation-cordapp -│   ├── secretsanta-cordapp -│   └── snakesandladders-cordapp -├── Basic -│   ├── README.md -│   ├── constants.properties -│   ├── cordapp-example -│   ├── flow-database-access -│   ├── flow-http-access -│   └── ping-pong -├── Features -│   ├── README.md -│   ├── attachment-blacklist -│   ├── attachment-sendfile -│   ├── confidentialidentity-whistleblower -│   ├── constants.properties -│   ├── cordaservice-autopayroll -│   ├── customlogging-yocordapp -│   ├── notarychange-iou -│   ├── observablestates-tradereporting -│   ├── oracle-primenumber -│   ├── queryablestate-carinsurance -│   ├── referencestates-sanctionsbody -│   └── schedulablestate-heartbeat -├── README.md -└── Tokens - ├── README.md - ├── bikemarket - ├── constants.properties - ├── dollartohousetoken - ├── fungiblehousetoken - ├── spaceships-javaAPIs - ├── stockpaydividend - └── tokentofriend -``` +* [Accounts](./Accounts): These samples showcases how to utilize Corda Accounts Libray to build CorDapps which aim to have massive user volume +* [Advanced](./Advanced): In these samples, we demonstrate more complex and sophisticated features of Corda. +* [Basic](./Basic): They demonstrate fundamental and useful techniques for CorDapp development. +* [BusinessNetworks](./BusinessNetworks): These include Business Network Extension related samples. Learn more at [bn-extension](https://github.com/corda/bn-extension). +* [Features](./Features): These samples demonstrate specific Corda functionalities. +* [Tokens](./Tokens): These include TokenSDK related samples. Learn more at TokenSDK. diff --git a/Tokens/README.md b/Tokens/README.md index 84426029..d9399120 100644 --- a/Tokens/README.md +++ b/Tokens/README.md @@ -3,10 +3,10 @@ This folder features [TokenSDK](https://training.corda.net/libraries/tokens-sdk/) sample projects. ### [Fungible House Token](./fungiblehousetoken): -This Cordapp serves as a basic example to demostrate how to tokenize an asset into [Fungible](https://training.corda.net/libraries/tokens-sdk/#fungibletoken) tokens in Corda utilizing the TokenSDK. +This CorDapp serves as a basic example to demonstrate how to tokenize an asset into [Fungible](https://training.corda.net/libraries/tokens-sdk/#fungibletoken) tokens in Corda utilizing the TokenSDK. ### [Bike Market](./bikemarket): -This sample Cordapp demonstrate all four out-of-box TokenSDK flows (Create, Issue, Move, and Redeem flows). The cordapp demonstrate how to tokenize an asset as non-fungible tokens and excute a few transacting procedures. +This sample CorDapp demonstrates all four out-of-box TokenSDK flows (Create, Issue, Move, and Redeem flows). The CorDapp demonstrate how to tokenize an asset as non-fungible tokens and excute a few transacting procedures.

Corda

diff --git a/Tokens/bikemarket/README.md b/Tokens/bikemarket/README.md index a1bef80d..fb3da635 100644 --- a/Tokens/bikemarket/README.md +++ b/Tokens/bikemarket/README.md @@ -46,7 +46,7 @@ Throughout the sample, we will see how to create, transact, and redeem a Token. Deploy and run the nodes by: ``` -./gradlew deployNodes +./gradlew clean build deployNodes ./build/nodes/runnodes ``` if you have any questions during setup, please go to https://docs.corda.net/getting-set-up.html for detailed setup instructions. diff --git a/Tokens/bikemarket/build.gradle b/Tokens/bikemarket/build.gradle index 4c6aff6c..78cef069 100644 --- a/Tokens/bikemarket/build.gradle +++ b/Tokens/bikemarket/build.gradle @@ -16,13 +16,15 @@ buildscript { //Tokens tokens_release_group = 'com.r3.corda.lib.tokens' tokens_release_version = '1.2' + confidential_id_release_group = "com.r3.corda.lib.ci" + confidential_id_release_version = "1.0" + } repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -38,12 +40,12 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } //SDK lib - maven { url 'https://software.r3.com/artifactory/corda-lib' } + maven { url 'https://download.corda.net/maven/corda-lib' } //Gradle Plugins maven { url 'https://repo.gradle.org/gradle/libs-releases' } } @@ -95,6 +97,7 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" // Token SDK dependencies. cordapp "$tokens_release_group:tokens-contracts:$tokens_release_version" diff --git a/Tokens/bikemarket/repositories.gradle b/Tokens/bikemarket/repositories.gradle index 206b9c68..8be7b630 100644 --- a/Tokens/bikemarket/repositories.gradle +++ b/Tokens/bikemarket/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Tokens/bikemarket/workflows/src/main/java/net/corda/samples/bikemarket/flows/CreateFrameToken.java b/Tokens/bikemarket/workflows/src/main/java/net/corda/samples/bikemarket/flows/CreateFrameToken.java index 92c762bd..a7e719a0 100644 --- a/Tokens/bikemarket/workflows/src/main/java/net/corda/samples/bikemarket/flows/CreateFrameToken.java +++ b/Tokens/bikemarket/workflows/src/main/java/net/corda/samples/bikemarket/flows/CreateFrameToken.java @@ -1,6 +1,7 @@ package net.corda.samples.bikemarket.flows; import com.r3.corda.lib.tokens.workflows.flows.rpc.CreateEvolvableTokens; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.bikemarket.states.FrameTokenState; import net.corda.core.contracts.TransactionState; import net.corda.core.contracts.UniqueIdentifier; @@ -22,13 +23,8 @@ public CreateFrameToken(String frameSerial) { public String call() throws FlowException { // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); //create non-fungible frame token UniqueIdentifier uuid = new UniqueIdentifier(); diff --git a/Tokens/bikemarket/workflows/src/main/java/net/corda/samples/bikemarket/flows/CreateWheelToken.java b/Tokens/bikemarket/workflows/src/main/java/net/corda/samples/bikemarket/flows/CreateWheelToken.java index 722e04a9..879c7a63 100644 --- a/Tokens/bikemarket/workflows/src/main/java/net/corda/samples/bikemarket/flows/CreateWheelToken.java +++ b/Tokens/bikemarket/workflows/src/main/java/net/corda/samples/bikemarket/flows/CreateWheelToken.java @@ -1,6 +1,7 @@ package net.corda.samples.bikemarket.flows; import com.r3.corda.lib.tokens.workflows.flows.rpc.CreateEvolvableTokens; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.bikemarket.states.WheelsTokenState; import net.corda.core.contracts.TransactionState; import net.corda.core.contracts.UniqueIdentifier; @@ -22,13 +23,8 @@ public CreateWheelToken(String wheelsSerial) { public String call() throws FlowException { // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); //create non-fungible frame token UniqueIdentifier uuid = new UniqueIdentifier(); diff --git a/Tokens/bikemarket/workflows/src/main/java/net/corda/samples/bikemarket/flows/TransferBikeTokens.java b/Tokens/bikemarket/workflows/src/main/java/net/corda/samples/bikemarket/flows/TransferBikeTokens.java index 7017605b..0a7fc93c 100644 --- a/Tokens/bikemarket/workflows/src/main/java/net/corda/samples/bikemarket/flows/TransferBikeTokens.java +++ b/Tokens/bikemarket/workflows/src/main/java/net/corda/samples/bikemarket/flows/TransferBikeTokens.java @@ -7,6 +7,7 @@ import com.r3.corda.lib.tokens.workflows.internal.flows.distribution.UpdateDistributionListFlow; import com.r3.corda.lib.tokens.workflows.internal.flows.finality.ObserverAwareFinalityFlow; import com.r3.corda.lib.tokens.workflows.utilities.NotaryUtilities; +import net.corda.core.identity.CordaX500Name; import net.corda.core.transactions.TransactionBuilder; import net.corda.samples.bikemarket.states.FrameTokenState; import net.corda.samples.bikemarket.states.WheelsTokenState; @@ -61,9 +62,12 @@ public String call() throws FlowException { //get the pointer to the wheel TokenPointer wheeltokenPointer = wheeltokentype.toPointer(wheeltokentype.getClass()); + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); FlowSession sellerSession = initiateFlow(holder); - TransactionBuilder txBuilder = new TransactionBuilder(NotaryUtilities.getPreferredNotary(getServiceHub())); + TransactionBuilder txBuilder = new TransactionBuilder(notary); MoveTokensUtilities.addMoveNonFungibleTokens(txBuilder, getServiceHub(), frametokenPointer, holder); MoveTokensUtilities.addMoveNonFungibleTokens(txBuilder, getServiceHub(), wheeltokenPointer, holder); diff --git a/Tokens/bikemarket/workflows/src/main/java/net/corda/samples/bikemarket/flows/TransferPartTokens.java b/Tokens/bikemarket/workflows/src/main/java/net/corda/samples/bikemarket/flows/TransferPartTokens.java index ac4832af..82ebedc7 100644 --- a/Tokens/bikemarket/workflows/src/main/java/net/corda/samples/bikemarket/flows/TransferPartTokens.java +++ b/Tokens/bikemarket/workflows/src/main/java/net/corda/samples/bikemarket/flows/TransferPartTokens.java @@ -7,6 +7,7 @@ import com.r3.corda.lib.tokens.workflows.internal.flows.distribution.UpdateDistributionListFlow; import com.r3.corda.lib.tokens.workflows.internal.flows.finality.ObserverAwareFinalityFlow; import com.r3.corda.lib.tokens.workflows.utilities.NotaryUtilities; +import net.corda.core.identity.CordaX500Name; import net.corda.core.transactions.TransactionBuilder; import net.corda.samples.bikemarket.states.FrameTokenState; import net.corda.samples.bikemarket.states.WheelsTokenState; @@ -57,8 +58,12 @@ public String call() throws FlowException { //get the pointer to the frame TokenPointer frametokenPointer = frametokentype.toPointer(frametokentype.getClass()); + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + FlowSession sellerSession = initiateFlow(holder); - TransactionBuilder txBuilder = new TransactionBuilder(NotaryUtilities.getPreferredNotary(getServiceHub())); + TransactionBuilder txBuilder = new TransactionBuilder(notary); MoveTokensUtilities.addMoveNonFungibleTokens(txBuilder, getServiceHub(), frametokenPointer, holder); SignedTransaction ptx = getServiceHub().signInitialTransaction(txBuilder); diff --git a/Tokens/bikemarket/workflows/src/test/java/net/corda/samples/bikemarket/FlowTests.java b/Tokens/bikemarket/workflows/src/test/java/net/corda/samples/bikemarket/FlowTests.java index 57ad116e..9bd3e67a 100644 --- a/Tokens/bikemarket/workflows/src/test/java/net/corda/samples/bikemarket/FlowTests.java +++ b/Tokens/bikemarket/workflows/src/test/java/net/corda/samples/bikemarket/FlowTests.java @@ -2,17 +2,16 @@ import com.google.common.collect.ImmutableList; import net.corda.core.contracts.StateAndRef; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.bikemarket.flows.CreateFrameToken; import net.corda.samples.bikemarket.flows.CreateWheelToken; import net.corda.samples.bikemarket.states.FrameTokenState; import net.corda.samples.bikemarket.states.WheelsTokenState; -import net.corda.testing.node.MockNetwork; -import net.corda.testing.node.MockNetworkParameters; -import net.corda.testing.node.TestCordapp; +import net.corda.testing.node.*; import org.junit.After; import org.junit.Before; import org.junit.Test; -import net.corda.testing.node.StartedMockNode; + import java.util.concurrent.Future; public class FlowTests { @@ -27,7 +26,9 @@ public void setup() { TestCordapp.findCordapp("net.corda.samples.bikemarket.contracts"), TestCordapp.findCordapp("net.corda.samples.bikemarket.flows"), TestCordapp.findCordapp("com.r3.corda.lib.tokens.contracts"), - TestCordapp.findCordapp("com.r3.corda.lib.tokens.workflows")))); + TestCordapp.findCordapp("com.r3.corda.lib.tokens.workflows"))) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); a = network.createPartyNode(null); b = network.createPartyNode(null); network.runNetwork(); diff --git a/Tokens/constants.properties b/Tokens/constants.properties index 4e7613f9..91f58c68 100644 --- a/Tokens/constants.properties +++ b/Tokens/constants.properties @@ -1,13 +1,13 @@ -cordaCoreReleaseGroup=net.corda cordaReleaseGroup=net.corda -cordaVersion=4.6 -cordaCoreVersion=4.6 +cordaCoreReleaseGroup=net.corda +cordaVersion=4.10 +cordaCoreVersion=4.10 gradlePluginsVersion=5.0.12 kotlinVersion=1.2.71 junitVersion=4.12 quasarVersion=0.7.10 -log4jVersion =2.11.2 -platformVersion=8 +log4jVersion =2.17.1 +platformVersion=12 slf4jVersion=1.7.25 nettyVersion=4.1.22.Final guavaVersion=23.5-jre diff --git a/Tokens/dollartohousetoken/build.gradle b/Tokens/dollartohousetoken/build.gradle index b060a26d..99a572c9 100644 --- a/Tokens/dollartohousetoken/build.gradle +++ b/Tokens/dollartohousetoken/build.gradle @@ -22,8 +22,8 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -38,12 +38,12 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } //SDK lib - maven { url 'https://software.r3.com/artifactory/corda-lib' } + maven { url 'https://download.corda.net/maven/corda-lib' } //Gradle Plugins maven { url 'https://repo.gradle.org/gradle/libs-releases' } } @@ -92,6 +92,8 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" + // Token SDK dependencies. cordapp "$tokens_release_group:tokens-contracts:$tokens_release_version" diff --git a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/HouseSaleInitiatorFlow.java b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/HouseSaleInitiatorFlow.java index 97c47456..cee87cfa 100644 --- a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/HouseSaleInitiatorFlow.java +++ b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/HouseSaleInitiatorFlow.java @@ -2,6 +2,7 @@ import co.paralleluniverse.fibers.Suspendable; import com.r3.corda.lib.tokens.workflows.flows.move.MoveTokensUtilities; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.dollartohousetoken.states.HouseState; import com.google.common.collect.ImmutableList; import com.r3.corda.lib.tokens.contracts.states.FungibleToken; @@ -39,13 +40,8 @@ public HouseSaleInitiatorFlow(String houseId, Party buyer) { public String call() throws FlowException { // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); /* Get the UUID from the houseId parameter */ UUID uuid = UUID.fromString(houseId); diff --git a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/HouseTokenCreateAndIssueFlow.java b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/HouseTokenCreateAndIssueFlow.java index 5d4dc4a2..7c78e4f2 100644 --- a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/HouseTokenCreateAndIssueFlow.java +++ b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/HouseTokenCreateAndIssueFlow.java @@ -2,6 +2,7 @@ import co.paralleluniverse.fibers.Suspendable; import com.r3.corda.lib.tokens.workflows.utilities.NonFungibleTokenBuilder; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.dollartohousetoken.states.HouseState; import com.google.common.collect.ImmutableList; import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken; @@ -49,16 +50,9 @@ public HouseTokenCreateAndIssueFlow(Party owner, Amount valuation, @Suspendable public String call() throws FlowException { - /* Choose the notary for the transaction */ - // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); /* Get a reference of own identity */ Party issuer = getOurIdentity(); diff --git a/Tokens/dollartohousetoken/workflows/src/test/java/net/corda/samples/dollartohousetoken/FlowTests.java b/Tokens/dollartohousetoken/workflows/src/test/java/net/corda/samples/dollartohousetoken/FlowTests.java index 8f958c3f..9a66cb79 100644 --- a/Tokens/dollartohousetoken/workflows/src/test/java/net/corda/samples/dollartohousetoken/FlowTests.java +++ b/Tokens/dollartohousetoken/workflows/src/test/java/net/corda/samples/dollartohousetoken/FlowTests.java @@ -4,18 +4,16 @@ import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken; import net.corda.core.contracts.Amount; import net.corda.core.contracts.StateAndRef; +import net.corda.core.identity.CordaX500Name; import net.corda.core.node.NetworkParameters; import net.corda.core.node.services.Vault; import net.corda.core.node.services.vault.QueryCriteria; import net.corda.samples.dollartohousetoken.flows.HouseTokenCreateAndIssueFlow; import net.corda.samples.dollartohousetoken.states.HouseState; -import net.corda.testing.node.MockNetwork; -import net.corda.testing.node.MockNetworkParameters; -import net.corda.testing.node.TestCordapp; +import net.corda.testing.node.*; import org.junit.After; import org.junit.Before; import org.junit.Test; -import net.corda.testing.node.StartedMockNode; import java.time.Instant; import java.util.Arrays; @@ -42,7 +40,9 @@ public void setup() { TestCordapp.findCordapp("net.corda.samples.dollartohousetoken.contracts"), TestCordapp.findCordapp("net.corda.samples.dollartohousetoken.flows"), TestCordapp.findCordapp("com.r3.corda.lib.tokens.contracts"), - TestCordapp.findCordapp("com.r3.corda.lib.tokens.workflows"))).withNetworkParameters(testNetworkParameters)); + TestCordapp.findCordapp("com.r3.corda.lib.tokens.workflows"))).withNetworkParameters(testNetworkParameters) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); a = network.createPartyNode(null); b = network.createPartyNode(null); network.runNetwork(); diff --git a/Tokens/fungiblehousetoken/build.gradle b/Tokens/fungiblehousetoken/build.gradle index d3245ca5..93a1a840 100644 --- a/Tokens/fungiblehousetoken/build.gradle +++ b/Tokens/fungiblehousetoken/build.gradle @@ -22,8 +22,9 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda' } + + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -38,12 +39,13 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://download.corda.net/maven/corda-releases' } maven { url 'https://jitpack.io' } //SDK lib - maven { url 'https://software.r3.com/artifactory/corda-lib' } + maven { url 'https://download.corda.net/maven/corda-lib' } //Gradle Plugins maven { url 'https://repo.gradle.org/gradle/libs-releases' } } @@ -91,6 +93,7 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" // Token SDK dependencies. cordapp "$tokens_release_group:tokens-contracts:$tokens_release_version" diff --git a/Tokens/fungiblehousetoken/workflows/src/main/java/net/corda/samples/tokenizedhouse/flows/RealEstateEvolvableFungibleTokenFlow.java b/Tokens/fungiblehousetoken/workflows/src/main/java/net/corda/samples/tokenizedhouse/flows/RealEstateEvolvableFungibleTokenFlow.java index ddb28e5f..34ee15b0 100644 --- a/Tokens/fungiblehousetoken/workflows/src/main/java/net/corda/samples/tokenizedhouse/flows/RealEstateEvolvableFungibleTokenFlow.java +++ b/Tokens/fungiblehousetoken/workflows/src/main/java/net/corda/samples/tokenizedhouse/flows/RealEstateEvolvableFungibleTokenFlow.java @@ -6,6 +6,7 @@ import com.r3.corda.lib.tokens.contracts.types.TokenType; import com.r3.corda.lib.tokens.workflows.flows.rpc.*; import com.r3.corda.lib.tokens.workflows.utilities.FungibleTokenBuilder; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.tokenizedhouse.states.FungibleHouseTokenState; import kotlin.Unit; import net.corda.core.contracts.*; @@ -38,14 +39,8 @@ public CreateHouseTokenFlow(String symbol, int valuation) { @Suspendable public SignedTransaction call() throws FlowException { // Obtain a reference to a notary we wish to use. - /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 - // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 - + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); //create token type FungibleHouseTokenState evolvableTokenType = new FungibleHouseTokenState(valuation, getOurIdentity(), new UniqueIdentifier(), 0, this.symbol); diff --git a/Tokens/fungiblehousetoken/workflows/src/test/java/net/corda/samples/tokenizedhouse/FlowTests.java b/Tokens/fungiblehousetoken/workflows/src/test/java/net/corda/samples/tokenizedhouse/FlowTests.java index 0e5c875e..76776750 100644 --- a/Tokens/fungiblehousetoken/workflows/src/test/java/net/corda/samples/tokenizedhouse/FlowTests.java +++ b/Tokens/fungiblehousetoken/workflows/src/test/java/net/corda/samples/tokenizedhouse/FlowTests.java @@ -3,16 +3,14 @@ import com.google.common.collect.ImmutableList; import net.corda.core.contracts.Amount; import net.corda.core.contracts.StateAndRef; +import net.corda.core.identity.CordaX500Name; import net.corda.core.node.NetworkParameters; import net.corda.core.node.services.Vault; import net.corda.core.node.services.vault.QueryCriteria; import net.corda.core.transactions.SignedTransaction; import net.corda.samples.tokenizedhouse.flows.RealEstateEvolvableFungibleTokenFlow; import net.corda.samples.tokenizedhouse.states.FungibleHouseTokenState; -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 net.corda.testing.node.*; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -39,7 +37,9 @@ public void setup() { TestCordapp.findCordapp("net.corda.samples.tokenizedhouse.contracts"), TestCordapp.findCordapp("net.corda.samples.tokenizedhouse.flows"), TestCordapp.findCordapp("com.r3.corda.lib.tokens.contracts"), - TestCordapp.findCordapp("com.r3.corda.lib.tokens.workflows"))).withNetworkParameters(testNetworkParameters)); + TestCordapp.findCordapp("com.r3.corda.lib.tokens.workflows"))).withNetworkParameters(testNetworkParameters) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); a = network.createPartyNode(null); b = network.createPartyNode(null); network.runNetwork(); diff --git a/Tokens/spaceships-javaAPIs/build.gradle b/Tokens/spaceships-javaAPIs/build.gradle index df1f4f61..0db10ba0 100644 --- a/Tokens/spaceships-javaAPIs/build.gradle +++ b/Tokens/spaceships-javaAPIs/build.gradle @@ -22,8 +22,8 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -38,12 +38,12 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } //SDK lib - maven { url 'https://software.r3.com/artifactory/corda-lib' } + maven { url 'https://download.corda.net/maven/corda-lib' } //Gradle Plugins maven { url 'https://repo.gradle.org/gradle/libs-releases' } } @@ -90,6 +90,7 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" // Token SDK dependencies. cordapp "$tokens_release_group:tokens-contracts:$tokens_release_version" diff --git a/Tokens/spaceships-javaAPIs/workflows/src/main/java/net/corda/samples/spaceships/flows/BuySpaceshipFlows.java b/Tokens/spaceships-javaAPIs/workflows/src/main/java/net/corda/samples/spaceships/flows/BuySpaceshipFlows.java index 4ad10490..827ebb21 100644 --- a/Tokens/spaceships-javaAPIs/workflows/src/main/java/net/corda/samples/spaceships/flows/BuySpaceshipFlows.java +++ b/Tokens/spaceships-javaAPIs/workflows/src/main/java/net/corda/samples/spaceships/flows/BuySpaceshipFlows.java @@ -16,6 +16,7 @@ import net.corda.core.contracts.Amount; import net.corda.core.contracts.StateAndRef; import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; import net.corda.core.identity.Party; import net.corda.core.node.StatesToRecord; import net.corda.core.node.services.VaultService; @@ -58,7 +59,10 @@ public BuySpaceshipInitiator(String shipId, Party seller) { @Override @SuppressWarnings("unchecked") public SignedTransaction call() throws FlowException { - Party notary = NotaryUtilities.getPreferredNotary(getServiceHub()); + + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); FlowSession sellerSession = initiateFlow(seller); VaultService vaultService = getServiceHub().getVaultService(); boolean processSale = false; diff --git a/Tokens/spaceships-javaAPIs/workflows/src/test/java/net/corda/samples/spaceships/FlowTests.java b/Tokens/spaceships-javaAPIs/workflows/src/test/java/net/corda/samples/spaceships/FlowTests.java index 34edd029..7847ebc8 100644 --- a/Tokens/spaceships-javaAPIs/workflows/src/test/java/net/corda/samples/spaceships/FlowTests.java +++ b/Tokens/spaceships-javaAPIs/workflows/src/test/java/net/corda/samples/spaceships/FlowTests.java @@ -15,16 +15,14 @@ import net.corda.core.contracts.Amount; import net.corda.core.contracts.StateAndRef; import net.corda.core.contracts.TransactionResolutionException; +import net.corda.core.identity.CordaX500Name; import net.corda.core.identity.Party; import net.corda.core.node.services.VaultService; import net.corda.core.transactions.SignedTransaction; import net.corda.samples.spaceships.flows.*; import net.corda.samples.spaceships.states.SpaceshipTokenType; import net.corda.testing.common.internal.ParametersUtilitiesKt; -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 net.corda.testing.node.*; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -53,6 +51,7 @@ public void setup() { .withNetworkParameters(ParametersUtilitiesKt.testNetworkParameters( Collections.emptyList(), 4 )) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) ); a = network.createPartyNode(null); b = network.createPartyNode(null); diff --git a/Tokens/stockpaydividend/build.gradle b/Tokens/stockpaydividend/build.gradle index d71e502f..e39a029d 100755 --- a/Tokens/stockpaydividend/build.gradle +++ b/Tokens/stockpaydividend/build.gradle @@ -21,8 +21,9 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda' } + + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -37,12 +38,13 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://download.corda.net/maven/corda-releases' } maven { url 'https://jitpack.io' } //SDK lib - maven { url 'https://software.r3.com/artifactory/corda-lib' } + maven { url 'https://download.corda.net/maven/corda-lib' } //Gradle Plugins maven { url 'https://repo.gradle.org/gradle/libs-releases' } } @@ -87,6 +89,7 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" // Token SDK dependencies. cordapp "$tokens_release_group:tokens-contracts:$tokens_release_version" diff --git a/Tokens/stockpaydividend/contracts/src/test/java/net/corda/samples/stockpaydividend/contracts/ContractTests.java b/Tokens/stockpaydividend/contracts/src/test/java/net/corda/samples/stockpaydividend/contracts/ContractTests.java index 90fb32ff..912a63d1 100755 --- a/Tokens/stockpaydividend/contracts/src/test/java/net/corda/samples/stockpaydividend/contracts/ContractTests.java +++ b/Tokens/stockpaydividend/contracts/src/test/java/net/corda/samples/stockpaydividend/contracts/ContractTests.java @@ -20,7 +20,7 @@ public class ContractTests { @Test public void multipleOutputTests() { StockState tokenPass = new StockState(new UniqueIdentifier(),Operator.getParty(),"TT","Test Token", - "USD",BigDecimal.valueOf(2.7), BigDecimal.valueOf(0.2),new Date(),new Date()); + "USD",BigDecimal.valueOf(2.7), BigDecimal.valueOf(0),new Date(),new Date()); StockState tokenFail = new StockState(new UniqueIdentifier(),Operator.getParty(),"","Test Token", "USD",BigDecimal.valueOf(2.7), BigDecimal.valueOf(0.2),new Date(),new Date()); ledger(ledgerServices, l -> { diff --git a/Tokens/tokentofriend/README.md b/Tokens/tokentofriend/README.md index 470608eb..602827c9 100644 --- a/Tokens/tokentofriend/README.md +++ b/Tokens/tokentofriend/README.md @@ -26,7 +26,7 @@ We have built a front end interface to make the interaction easier. Clone the ap Go to that storage node terminal: ``` - flow start QueryToken uuid: xxx-xxxx-xxxx-xxxx-xx, recipientEmai: 2@gmail.com + flow start QueryToken uuid: xxx-xxxx-xxxx-xxxx-xx, recipientEmail: 2@gmail.com ``` You should discover the message that was attached in the token. diff --git a/Tokens/tokentofriend/build.gradle b/Tokens/tokentofriend/build.gradle index 38f6f307..befc943a 100644 --- a/Tokens/tokentofriend/build.gradle +++ b/Tokens/tokentofriend/build.gradle @@ -24,8 +24,8 @@ buildscript {//properties that you need to build the project repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://software.r3.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -42,12 +42,12 @@ allprojects {//Properties that you need to compile your project (The application repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } //SDK lib - maven { url 'https://software.r3.com/artifactory/corda-lib' } + maven { url 'https://download.corda.net/maven/corda-lib' } //Gradle Plugins maven { url 'https://repo.gradle.org/gradle/libs-releases' } } @@ -89,6 +89,8 @@ dependencies { 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" + cordaDriver "net.corda:corda-shell:4.10" + // Token SDK dependencies. cordapp "$tokens_release_group:tokens-contracts:$tokens_release_version" cordapp "$tokens_release_group:tokens-workflows:$tokens_release_version" diff --git a/Tokens/tokentofriend/repositories.gradle b/Tokens/tokentofriend/repositories.gradle index 206b9c68..8be7b630 100644 --- a/Tokens/tokentofriend/repositories.gradle +++ b/Tokens/tokentofriend/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Tokens/tokentofriend/workflows/src/main/java/net/corda/samples/tokentofriend/flows/CreateMyToken.java b/Tokens/tokentofriend/workflows/src/main/java/net/corda/samples/tokentofriend/flows/CreateMyToken.java index cbaadc56..64162063 100644 --- a/Tokens/tokentofriend/workflows/src/main/java/net/corda/samples/tokentofriend/flows/CreateMyToken.java +++ b/Tokens/tokentofriend/workflows/src/main/java/net/corda/samples/tokentofriend/flows/CreateMyToken.java @@ -2,6 +2,7 @@ import co.paralleluniverse.fibers.Suspendable; import com.r3.corda.lib.tokens.workflows.flows.rpc.CreateEvolvableTokens; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.tokentofriend.states.CustomTokenState; import net.corda.core.contracts.TransactionState; import net.corda.core.contracts.UniqueIdentifier; @@ -28,15 +29,9 @@ public CreateMyToken(String myEmail, String recipients, String msg) { @Suspendable public UniqueIdentifier call() throws FlowException { - // Obtain a reference from a notary we wish to use. - /** - * METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* - * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flow or parsed from config (Preferred) - * - * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. - */ - Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); - // val notary = serviceHub.networkMapCache.getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")) // METHOD 2 + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); UniqueIdentifier uuid = new UniqueIdentifier(); CustomTokenState tokenState = new CustomTokenState(myEmail,recipients,msg,getOurIdentity(),0,uuid); diff --git a/Tokens/tokentofriend/workflows/src/test/java/net/corda/samples/tokentofriend/FlowTests.java b/Tokens/tokentofriend/workflows/src/test/java/net/corda/samples/tokentofriend/FlowTests.java index f4ee96ed..24cbb673 100644 --- a/Tokens/tokentofriend/workflows/src/test/java/net/corda/samples/tokentofriend/FlowTests.java +++ b/Tokens/tokentofriend/workflows/src/test/java/net/corda/samples/tokentofriend/FlowTests.java @@ -2,6 +2,7 @@ import com.google.common.collect.ImmutableList; import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken; +import net.corda.core.identity.CordaX500Name; import net.corda.samples.tokentofriend.flows.IssueToken; import net.corda.samples.tokentofriend.states.CustomTokenState; import net.corda.samples.tokentofriend.flows.CreateMyToken; @@ -10,13 +11,11 @@ import net.corda.core.node.NetworkParameters; import net.corda.core.node.services.Vault; import net.corda.core.node.services.vault.QueryCriteria; -import net.corda.testing.node.MockNetwork; -import net.corda.testing.node.MockNetworkParameters; -import net.corda.testing.node.TestCordapp; +import net.corda.testing.node.*; import org.junit.After; import org.junit.Before; import org.junit.Test; -import net.corda.testing.node.StartedMockNode; + import java.util.*; import java.time.Instant; import java.util.concurrent.ExecutionException; @@ -41,7 +40,9 @@ public void setup() { TestCordapp.findCordapp("net.corda.samples.tokentofriend.contracts"), TestCordapp.findCordapp("net.corda.samples.tokentofriend.flows"), TestCordapp.findCordapp("com.r3.corda.lib.tokens.contracts"), - TestCordapp.findCordapp("com.r3.corda.lib.tokens.workflows"))).withNetworkParameters(testNetworkParameters)); + TestCordapp.findCordapp("com.r3.corda.lib.tokens.workflows"))).withNetworkParameters(testNetworkParameters) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); a = network.createPartyNode(null); b = network.createPartyNode(null); c = network.createPartyNode(null); @@ -62,8 +63,7 @@ public void CheckTheCorrectMessageIsStored() throws ExecutionException, Interrup Future future = a.startFlow(flow); network.runNetwork(); UniqueIdentifier tokenStateID = future.get(); - QueryCriteria inputCriteria = new QueryCriteria.LinearStateQueryCriteria().withUuid(Arrays.asList(tokenStateID.getId())).withStatus(Vault.StateStatus.UNCONSUMED); - CustomTokenState storedState = a.getServices().getVaultService().queryBy(CustomTokenState.class,inputCriteria).getStates().get(0).getState().getData(); + CustomTokenState storedState = a.getServices().getVaultService().queryBy(CustomTokenState.class).getStates().get(0).getState().getData(); assert (storedState.getMessage().equals(msg)); } @@ -82,11 +82,10 @@ public void CheckIfNonFungibleTokenCorrectlyCreated() throws ExecutionException, int subString = resultString.indexOf("Token Id is: "); String nonfungibleTokenId = resultString.substring(subString+13,resultString.indexOf("Storage Node is:")-1); System.out.println("-"+ nonfungibleTokenId+"-"); - QueryCriteria inputCriteria = new QueryCriteria.LinearStateQueryCriteria().withUuid(Arrays.asList(UUID.fromString(nonfungibleTokenId))).withStatus(Vault.StateStatus.UNCONSUMED); - List> storedNonFungibleTokenb = b.getServices().getVaultService().queryBy(NonFungibleToken.class,inputCriteria).getStates(); - List> storedNonFungibleTokenc = c.getServices().getVaultService().queryBy(NonFungibleToken.class,inputCriteria).getStates(); - List> storedNonFungibleTokend = d.getServices().getVaultService().queryBy(NonFungibleToken.class,inputCriteria).getStates(); - List> storedNonFungibleTokene = e.getServices().getVaultService().queryBy(NonFungibleToken.class,inputCriteria).getStates(); + List> storedNonFungibleTokenb = b.getServices().getVaultService().queryBy(NonFungibleToken.class).getStates(); + List> storedNonFungibleTokenc = c.getServices().getVaultService().queryBy(NonFungibleToken.class).getStates(); + List> storedNonFungibleTokend = d.getServices().getVaultService().queryBy(NonFungibleToken.class).getStates(); + List> storedNonFungibleTokene = e.getServices().getVaultService().queryBy(NonFungibleToken.class).getStates(); NonFungibleToken storedToken = Arrays.asList(storedNonFungibleTokenb,storedNonFungibleTokenc,storedNonFungibleTokend,storedNonFungibleTokene).stream().filter(it -> 0 != it.size() ).collect(Collectors.toList()).get(0).get(0).getState().getData();