Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] CORE-16353: Added flow tests using the Flow Testing Driver and fixed … #28

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 36 additions & 42 deletions java-samples/corda5-negotiation-cordapp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,43 @@ This CorDapp shows how multi-party negotiation is handled on the Corda ledger, i
interaction.

## Concepts

A flow is provided that allows a node to propose a trade to a counterparty. The counterparty has two options:

* Accepting the proposal, converting the `ProposalState` into a `TradeState` with identical attributes
* Modifying the proposal, consuming the existing `ProposalState` and replacing it with a new `ProposalState` for a new
amount

Only the recipient of the proposal has the ability to accept it or modify it. If the sender of the proposal tries to
accept or modify the proposal, this attempt will be rejected automatically at the flow level.
accept or modify the proposal, this attempt will be rejected automatically at the flow level. Similarly, the modifier
cannot accept the modified proposal.

### Flows
We start with the proposal flow implemented in `ProposalFlowRequest.java`.

We start with the proposal flow implemented in `ProposalFlow.java`.


The modification of the proposal is implemented in `ModificationFlow.java`.


In the `AcceptanceFlow.java`, we receive the modified ProposalState and it's converted into a TradeState.

The modification of the proposal is implemented in `ModifyFlowRequest.java`.

In the `AcceptFlowRequest.java`, we receive the modified or unmodified ProposalState and it is converted into a
TradeState.

### Setting up

1. We will begin our test deployment with clicking the `startCorda`. This task will load up the combined Corda workers in docker.
A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v1/swagger#. You can test out some
functions to check connectivity. (GET /cpi function call should return an empty list as for now.)
2. We will now deploy the cordapp with a click of `5-vNodeSetup` task. Upon successful deployment of the CPI, the GET /cpi function call should now return the meta data of the cpi you just upload.



### Running the app
1. We begin our test deployment with clicking the `startCorda`. This task loads up the combined Corda workers in Docker.
A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v1/swagger#. You can test
out some functions to check connectivity. (GET /cpi function call should return an empty list as for now.)
2. We now deploy the CorDapp with a click of `5-vNodeSetup` task. Upon successful deployment of the CPI, the GET /cpi
function call returns the metadata of the CPI you just upload.

In Corda 5, flows will be triggered via `POST /flow/{holdingidentityshorthash}` and flow result will need to be view at `GET /flow/{holdingidentityshorthash}/{clientrequestid}`
* holdingidentityshorthash: the id of the network participants, ie Bob, Alice, Charlie. You can view all the short hashes of the network member with another gradle task called `ListVNodes`
* clientrequestid: the id you specify in the flow requestBody when you trigger a flow.
### Running the CorDapp
In Corda 5, flows are triggered via `POST /flow/{holdingidentityshorthash}` and flow result has to be viewed at
`GET /flow/{holdingidentityshorthash}/{clientrequestid}`
* holdingidentityshorthash: the ID of the network participants, i.e. Bob, Alice, Charlie. You can view all the short
hashes of the network member with another Gradle task called `ListVNodes`
* clientrequestid: the ID you specify in the flow request body when you trigger a flow.

#### Step 1: Create ProposalState between two parties
Pick a VNode identity to initiate the Proposal creation, and get its short hash. (Let's pick Alice.).
#### Step 1: Create Proposal state between two parties
Pick a VNode identity to initiate the Proposal state creation and get its short hash. For example, let's pick Alice:

Go to `POST /flow/{holdingidentityshorthash}`, enter the identity short hash(Alice's hash) and request body:
Go to `POST /flow/{holdingidentityshorthash}`, enter Alice's identity short hash and the following request body:
```
{
"clientRequestId": "createProposal",
Expand All @@ -55,25 +51,26 @@ Go to `POST /flow/{holdingidentityshorthash}`, enter the identity short hash(Ali
}
}
```
After trigger the create-ProposalFlow, hop to `GET /flow/{holdingidentityshorthash}/{clientrequestid}` and enter the short hash(Alice's hash) and client request id ("createProposal" in the case above) to view the flow result.

After triggering the create-ProposalFlow, hop to `GET /flow/{holdingidentityshorthash}/{clientrequestid}` and enter
Alice's identity short hash and client request ID ("createProposal" in the case above) to view the flow result.

#### Step 2: List created Proposal state
In order to continue the app logics, we would need the Proposal ID. This step will bring out all the Proposal this entity (Alice) has.
Go to `POST /flow/{holdingidentityshorthash}`, enter the identity short hash(Alice's hash) and request body:
In order to continue the CorDapp's logics, we need the Proposal ID - the identity of the created Proposal state. This
step will bring out all the Proposal this entity (Alice) has.
Go to `POST /flow/{holdingidentityshorthash}`, enter the Alice's identity short hash and request body:
```
{
"clientRequestId": "list-1",
"flowClassName": "com.r3.developers.samples.negotiation.workflows.util.ListProposal",
"requestBody": {}
}
```
After trigger the List Proposal, hop to `GET /flow/{holdingidentityshorthash}/{clientrequestid}` and enter the short hash(Alice's hash) and client request id ("list-1" in the case above) to view the flow result.

After triggering the List Proposal, hop to `GET /flow/{holdingidentityshorthash}/{clientrequestid}` and enter Alice's
identity short hash and client request id ("list-1" in the case above) to view the flow result.

#### Step 3: Modify the proposal
In order to continue the app logics, we would need the Proposal ID. This step will bring out the Proposal entries this entity (Alice) has. Bob can edit the proposal if required by entering the new amount.
Go to `POST /flow/{holdingidentityshorthash}`, enter the identity short hash(Bob hash) and request body:
The responder, Bob, can edit the proposal if required by entering the new amount.
Go to `POST /flow/{holdingidentityshorthash}`, enter Bob's identity short hash and request body:
```
{
"clientRequestId": "ModifyFlow",
Expand All @@ -84,12 +81,13 @@ Go to `POST /flow/{holdingidentityshorthash}`, enter the identity short hash(Bob
}
}
```
After triggering the modify flow we need to hop to `GET /flow/{holdingidentityshorthash}/{clientrequestid}` and check the result. Enter bob's hash id and the modify flow id which is "ModifyFlow" in the case above.

After triggering the modify flow, we need to hop to `GET /flow/{holdingidentityshorthash}/{clientrequestid}` and
check the result. Enter Bob's hash id and the modify flow ID which is "ModifyFlow" in the case above.

#### Step 4: Accept the new proposal from bob `AcceptFlow`
In this step, alice will accept the new proposal of Bob.
Goto `POST /flow/{holdingidentityshorthash}`, enter the identity short hash (of Alice) and request body, we also need to provide the proposalId, which is same as the proposal ID used in modifyFlow body.
In this step, Alice will accept the new proposal from Bob.
Goto `POST /flow/{holdingidentityshorthash}`, enter Alice's identity short hash and request body, we also need to
provide the proposalId, which is same as the proposal ID used in ModifyFlow body.
```
{
"clientRequestId": "AcceptFlow",
Expand All @@ -99,7 +97,8 @@ Goto `POST /flow/{holdingidentityshorthash}`, enter the identity short hash (of
}
}
```
And as for the result of this flow, go to `GET /flow/{holdingidentityshorthash}/{clientrequestid}` and enter the required fields.
And as for the result of this flow, go to `GET /flow/{holdingidentityshorthash}/{clientrequestid}` and enter the
required fields.

Thus, we have concluded a full run through of the Negotiation app.

Expand All @@ -108,13 +107,8 @@ Below are the app diagrams which are useful for the visual understanding.

#### Dynamic Diagram


![img_2.png](negotiation-sequence-diagram.png)





#### Static Diagram

![img.png](negotiation-design-diagram.png)
Expand Down
35 changes: 32 additions & 3 deletions java-samples/corda5-negotiation-cordapp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ allprojects {
group 'net.corda.samples'
version '1.0-SNAPSHOT'

configurations.configureEach {
resolutionStrategy {
// FORCE Gradle to use latest SNAPSHOT versions.
cacheChangingModulesFor 0, 'seconds'
}
}

def javaVersion = VERSION_11

// Configure the CSDE
Expand Down Expand Up @@ -46,12 +53,36 @@ allprojects {
// All dependencies are held in Maven Central
mavenLocal()
mavenCentral()
// Remove once Corda 5.1 and Flow Testing Driver are full GA
maven {
url = "$artifactoryContextUrl/corda-dependencies-dev"
authentication {
basic(BasicAuthentication)
}
credentials {
username = findProperty('cordaArtifactoryUsername') ?: System.getenv('CORDA_ARTIFACTORY_USERNAME')
password = findProperty('cordaArtifactoryPassword') ?: System.getenv('CORDA_ARTIFACTORY_PASSWORD')
}
mavenContent {
snapshotsOnly()
}
}
// Remove once Corda 5.1 and Flow Testing Driver are full GA
maven {
url = "$artifactoryContextUrl/corda-os-maven"
authentication {
basic(BasicAuthentication)
}
credentials {
username = findProperty('cordaArtifactoryUsername') ?: System.getenv('CORDA_ARTIFACTORY_USERNAME')
password = findProperty('cordaArtifactoryPassword') ?: System.getenv('CORDA_ARTIFACTORY_PASSWORD')
}
}
}

tasks.withType(Test).configureEach {
useJUnitPlatform()
}

}

publishing {
Expand All @@ -61,7 +92,5 @@ publishing {
groupId project.group
artifact jar
}

}
}

Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,26 @@ public class Proposal implements ContractState {
private final Member proposer;
private final Member proposee;
private final UUID proposalID;
private final Member modifier;

@ConstructorForDeserialization
public Proposal(int amount, Member buyer, Member seller, Member proposer, Member proposee, UUID proposalID) {
public Proposal(int amount, Member buyer, Member seller, Member proposer, Member proposee, Member modifier, UUID proposalID) {
this.amount = amount;
this.buyer = buyer;
this.seller = seller;
this.proposee = proposee;
this.proposer = proposer;
this.modifier = modifier;
this.proposalID = proposalID;
}

public Proposal(int amount, Member buyer, Member seller, Member proposer, Member proposee) {
public Proposal(int amount, Member buyer, Member seller, Member proposer, Member proposee, Member modifier) {
this.amount = amount;
this.buyer = buyer;
this.seller = seller;
this.proposee = proposee;
this.proposer = proposer;
this.modifier = modifier;
this.proposalID = UUID.randomUUID();
}

Expand Down Expand Up @@ -63,6 +66,10 @@ public UUID getProposalID() {
return proposalID;
}

public Member getModifier() {
return modifier;
}

@NotNull
@Override
public List<PublicKey> getParticipants() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public static class Accept implements NegotiationCommands {
String sellerMsg = "The seller is unmodified in the output";
String proposerMsg = "The proposer is a required signer";
String proposeMsg = "The propose is a required signer";
String proposerCannotAcceptProposalMsg = "The proposer cannot accept their own proposal";

@Override
public void verify(UtxoLedgerTransaction transaction) {
Expand All @@ -112,14 +113,16 @@ public void verify(UtxoLedgerTransaction transaction) {
require(transaction.getOutputTransactionStates().size() == 1, oneOutputMsg);
require(transaction.getOutputStates(Trade.class).size() == 1, outputTypeMsg);
require(transaction.getCommands().size() == 1, oneCommandMsg);
require(proposalStateInputs.getModifier() == null ||
!proposalStateInputs.getModifier().getName().toString().equals(tradeStateOutput.getAcceptor().toString()),
proposerCannotAcceptProposalMsg);

require(tradeStateOutput.getAmount() == proposalStateInputs.getAmount(), amountMsg);
require(proposalStateInputs.getBuyer().toString().equals(tradeStateOutput.getBuyer().toString()), buyerMsg);
require(proposalStateInputs.getSeller().toString().equals(tradeStateOutput.getSeller().toString()), sellerMsg);

require(transaction.getSignatories().contains(proposalStateInputs.getProposer().getLedgerKey()), proposerMsg);
require(transaction.getSignatories().contains(proposalStateInputs.getProposee().getLedgerKey()), proposeMsg);

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.r3.developers.samples.negotiation.util.Member;
import net.corda.v5.base.annotations.ConstructorForDeserialization;
import net.corda.v5.base.types.MemberX500Name;
import net.corda.v5.ledger.utxo.BelongsToContract;
import net.corda.v5.ledger.utxo.ContractState;
import org.jetbrains.annotations.NotNull;
Expand All @@ -18,21 +19,25 @@ public class Trade implements ContractState {
private final Member buyer;
private final Member seller;
private final UUID proposalID;
private final MemberX500Name acceptor;
public List<PublicKey> participants;


@ConstructorForDeserialization
public Trade(int amount, Member buyer, Member seller, UUID proposalID, List<PublicKey> participants) {
public Trade(int amount, Member buyer, Member seller, UUID proposalID, MemberX500Name acceptor, List<PublicKey> participants) {
this.amount = amount;
this.buyer = buyer;
this.seller = seller;
this.proposalID = proposalID;
this.acceptor = acceptor;
this.participants = participants;
}

public Trade(int amount, Member buyer, Member seller, List<PublicKey> participants) {
public Trade(int amount, Member buyer, Member seller, MemberX500Name acceptor, List<PublicKey> participants) {
this.amount = amount;
this.buyer = buyer;
this.seller = seller;
this.acceptor = acceptor;
this.proposalID = UUID.randomUUID();
this.participants = participants;
}
Expand All @@ -53,6 +58,9 @@ public Member getBuyer() {
public UUID getProposalID() {
return proposalID;
}
public MemberX500Name getAcceptor() {
return acceptor;
}

@NotNull
@Override
Expand Down
11 changes: 7 additions & 4 deletions java-samples/corda5-negotiation-cordapp/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ kotlin.code.style=official

# Specify the version of the Corda-API to use.
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
cordaApiVersion=5.0.0.765
cordaApiVersion=5.1.0.16-beta-+

# Specify the version of the notary plugins to use.
# Currently packaged as part of corda-runtime-os, so should be set to a corda-runtime-os version.
cordaNotaryPluginsVersion=5.0.0.0
cordaNotaryPluginsVersion=5.1.0.0-+

# Specify the version of the Combined Worker to use
combinedWorkerJarVersion=5.0.0.0
combinedWorkerJarVersion=5.1.0.0-HC05
# Specify the version of the Flow Testing Driver to use (only works with Corda 5.1 artifacts)
cordaDriverVersion=5.1.0-DRIVER.0-+

# Specify the version of the cordapp-cpb and cordapp-cpk plugins
cordaPluginsVersion=7.0.3
Expand All @@ -35,7 +37,8 @@ junitVersion = 5.8.2
mockitoKotlinVersion=4.0.0
mockitoVersion=4.6.1
hamcrestVersion=2.2

assertjVersion = 3.24.1
jacksonVersion=2.15.2

# R3 internal repository
# Use this version when pointing to artefacts in artifactory that have not been published to S3
Expand Down
15 changes: 15 additions & 0 deletions java-samples/corda5-negotiation-cordapp/settings.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
pluginManagement {
// Declare the repositories where plugins are stored.
repositories {
// Remove once Corda 5.1 and Flow Testing Driver are full GA
maven {
url = "$artifactoryContextUrl/corda-os-maven"
content {
includeGroupByRegex 'net\\.corda\\.cordapp(\\..*)?'
}
authentication {
basic(BasicAuthentication)
}
credentials {
username = settings.ext.find('cordaArtifactoryUsername') ?: System.getenv('CORDA_ARTIFACTORY_USERNAME')
password = settings.ext.find('cordaArtifactoryPassword') ?: System.getenv('CORDA_ARTIFACTORY_PASSWORD')
}
}
//
gradlePluginPortal()
mavenCentral()
mavenLocal()
Expand Down
Loading