Skip to content

Commit

Permalink
fixed pathing error with kotlin customStateJSONandQuery app.
Browse files Browse the repository at this point in the history
  • Loading branch information
parisyup committed May 12, 2024
1 parent 96a5af8 commit 2fa0b42
Show file tree
Hide file tree
Showing 15 changed files with 1,276 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@ This user guide provides step-by-step instructions on using the Corda 5 flow man
## Prerequisites
* Install and run Python and Flask framework. link.

* Prepare your local Corda 5 environment. (By default, the Flow Management Tool is looking to connect to https://localhost:8888/api/v5_2/swagger#/ with Login: Admin and Password: Admin.)
* Prepare your local Corda 5 environment. (By default, the Flow Management Tool is looking to connect to https://localhost:8888/api/v1/swagger#/ with Login: Admin and Password: Admin.)

* Clong the Flow Management Tool repository. FlowManagementUI: main

## Set Up

1. Assuming your local Corda 5 environment is populated and the swagger endpoint is at: https://localhost:8888/api/v5_2/swagger#/
1. Assuming your local Corda 5 environment is populated and the swagger endpoint is at: https://localhost:8888/api/v1/swagger#/

2. Navigate to where you downloaded the Corda 5 Flow Management Tool

3. To run the framework
* Navigate to the file name using cd command.
* use the python app.py command to run it.
![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/f0c3bf59-8180-48a0-91cc-80f2d260e530)
* Navigate to the file name using cd command.
* use the python app.py command to run it.
![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/f0c3bf59-8180-48a0-91cc-80f2d260e530)

* Later on, click on the IP Address which will open the Interface:
* Later on, click on the IP Address which will open the Interface:

![image(4)](https://github.com/parisyup/FlowManagementUI/assets/66366646/8d88e37c-edbb-4d6d-8bcd-d773e818a106)

Expand Down Expand Up @@ -52,7 +52,7 @@ the `your-image-name` at the end of the command can be whatever you like but mak

### Selecting the Flow Initiator

As the first step of using the Flow Management Tool, you would need to select the Flow Initiator. The Flow Initiator indicates which vNode will be triggering the flow. If you wish to have Alice to run a transaction to Bob, select the X500Name of Alice. The selected vNode’s shortHash (Corda 5 Network participant identifier) will also be shown below the dropdown list to signify your selection.
As the first step of using the Flow Management Tool, you would need to select the Flow Initiator. The Flow Initiator indicates which vNode will be triggering the flow. If you wish to have Alice to run a transaction to Bob, select the X500Name of Alice. The selected vNode’s shortHash (Corda 5 Network participant identifier) will also be shown below the dropdown list to signify your selection.

### Function 1: To Make a Flow Call

Expand Down
86 changes: 5 additions & 81 deletions kotlin-samples/customeStateJSONandQuery/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

- The MyFirstFlow code which forms the basis of this getting started documentation, this is located in package com.r3.developers.cordapptemplate.flowexample

- A UTXO example in package com.r3.developers.cordapptemplate.customeStateJSONandQuery packages
- A UTXO example in package com.r3.developers.cordapptemplate.customStateJSONandQuery packages

- Ability to configure the Members of the Local Corda Network.

Expand Down Expand Up @@ -63,7 +63,7 @@ Go to `POST /flow/{holdingidentityshorthash}`, enter the identity short hash(Ali
```
{
"clientRequestId": "create-1",
"flowClassName": "com.r3.developers.cordapptemplate.customeStateJSONandQuery.workflows.CreateNewChatFlow",
"flowClassName": "com.r3.developers.cordapptemplate.customStateJSONandQuery.workflows.CreateNewChatFlow",
"requestBody": {
"chatName":"Chat with Bob",
"otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB",
Expand All @@ -80,7 +80,7 @@ Go to `POST /flow/{holdingidentityshorthash}`, enter the identity short hash(Ali
```
{
"clientRequestId": "list-1",
"flowClassName": "com.r3.developers.cordapptemplate.customeStateJSONandQuery.workflows.ListChatsFlow",
"flowClassName": "com.r3.developers.cordapptemplate.customStateJSONandQuery.workflows.ListChatsFlow",
"requestBody": {}
}
```
Expand All @@ -95,7 +95,7 @@ this message will be recorded as a message from Alice, vice versa. And the id fi
```
{
"clientRequestId": "update-1",
"flowClassName": "com.r3.developers.cordapptemplate.customeStateJSONandQuery.workflows.UpdateChatFlow",
"flowClassName": "com.r3.developers.cordapptemplate.customStateJSONandQuery.workflows.UpdateChatFlow",
"requestBody": {
"id":" ** fill in id **",
"message": "How are you today?"
Expand All @@ -110,7 +110,7 @@ After a few back and forth of the messaging, you can view entire chat history by
```
{
"clientRequestId": "get-1",
"flowClassName": "com.r3.developers.cordapptemplate.customeStateJSONandQuery.workflows.GetChatFlow",
"flowClassName": "com.r3.developers.cordapptemplate.customStateJSONandQuery.workflows.GetChatFlow",
"requestBody": {
"id":" ** fill in id **",
"numberOfRecords":"4"
Expand All @@ -120,79 +120,3 @@ After a few back and forth of the messaging, you can view entire chat history by
And as for the result, you need to go to the Get API again and enter the short hash and client request ID.

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

## Saving a run as JSON

#### Description
The ChatJsonFactory is a custom JSON factory designed to facilitate the serialization and deserialization of ChatState objects to and from JSON format within a Corda application.

#### Usage
This factory is utilized within Corda applications to convert ChatState objects to JSON format after each use.
It is integrated into the Corda framework, ensuring seamless interoperability with other components of the application.

#### Functionality
getStateType(): Specifies the type of state the factory is responsible for, in this case, ChatState.
create(ChatState state, JsonMarshallingService jsonMarshallingService): Generates a JSON representation of a ChatState object by constructing a map and serializing it to JSON format using the provided JsonMarshallingService constants

ID: Key name for the identifier of the chat.

CHATNAME: Key name for the name of the chat.

MESSAGE: Key name for the content of the message.

MESSAGEFROM: Key name for the sender of the message.

#### Integration
The app is integrated automatically by corda when implementing ContractStateVaultJsonFactory for the class.
It will run whenever a flow runs for the chatState

## Running the query

#### Description
The CustomChatQuery class provides custom named queries for querying Vault states related to a chat application within a Corda application.
These queries are designed to facilitate specific data retrieval operations from the Corda vault which were stored
using the JSON function previously mentioned.

#### Usage
This component is integrated into Corda applications to execute custom named queries for retrieving chat-related states from the Corda vault.
It enables developers to perform targeted data retrieval operations efficiently.

#### Functionality
create(VaultNamedQueryBuilderFactory vaultNamedQueryBuilderFactory): Defines custom named queries for querying the Corda vault.
GET_ALL_MSG: Retrieves all messages from the vault.
GET_MSG_FROM: Retrieves messages from a specific sender.

### Integration
#### Process
The integration process began with the instantiation of the ListChatByCustomQueryFlow class within the application codebase.
This class provides the necessary functionality to execute custom named queries against the Corda vault from the saved JSONs.

Subsequently, the custom query GET_MSG_FROM was executed using the query method provided by VaultService.
This involved setting parameters for the query, such as specifying the name of the sender whose messages were to be retrieved in this
application it is hard coded to alice.
Additionally, optional parameters like timestamp limits and result limits were configured as per the application's requirements.

Once the query execution was completed, the results were processed to extract the relevant ChatState objects.
This step involved mapping the query results to extract the desired states from the Corda vault.

Following the retrieval of ChatState objects, they were converted into a human-readable format or DTOs suitable for presentation purposes.
This ensured that the retrieved chat messages were formatted in a manner understandable by users or other components of the application.

Finally, the results were serialized to JSON format using a JsonMarshallingService.
This step prepared the results for transmission as a response,
enabling other components or external systems to consume the data in a standardized format.

#### Calling a query

After creating the query it can be run using the following REST call using Bob's id:

```
{
"clientRequestId": "customlist-1",
"flowClassName": "com.r3.developers.cordapptemplate.utxoexample.workflows.ListChatsByCustomQueryFlow",
"requestBody": {}
}
```

Which will display all messages sent by just Alice.
So even if Bob received messages from someone else it will only display the messages sent by Alice
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.r3.developers.cordapptemplate.customStateJSONandQuery.contracts

import com.r3.developers.cordapptemplate.customStateJSONandQuery.states.ChatState
import net.corda.v5.base.exceptions.CordaRuntimeException
import net.corda.v5.ledger.utxo.Command
import net.corda.v5.ledger.utxo.Contract
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction

class ChatContract: Contract {

// Use an internal scoped constant to hold the error messages
// This allows the tests to use them, meaning if they are updated you won't need to fix tests just because the wording was updated
internal companion object {

const val REQUIRE_SINGLE_COMMAND = "Requires a single command."
const val UNKNOWN_COMMAND = "Command not allowed."
const val OUTPUT_STATE_SHOULD_ONLY_HAVE_TWO_PARTICIPANTS = "The output state should have two and only two participants."
const val TRANSACTION_SHOULD_BE_SIGNED_BY_ALL_PARTICIPANTS = "The transaction should have been signed by both participants."

const val CREATE_COMMAND_SHOULD_HAVE_NO_INPUT_STATES = "When command is Create there should be no input states."
const val CREATE_COMMAND_SHOULD_HAVE_ONLY_ONE_OUTPUT_STATE = "When command is Create there should be one and only one output state."

const val UPDATE_COMMAND_SHOULD_HAVE_ONLY_ONE_INPUT_STATE = "When command is Update there should be one and only one input state."
const val UPDATE_COMMAND_SHOULD_HAVE_ONLY_ONE_OUTPUT_STATE = "When command is Update there should be one and only one output state."
const val UPDATE_COMMAND_ID_SHOULD_NOT_CHANGE = "When command is Update id must not change."
const val UPDATE_COMMAND_CHATNAME_SHOULD_NOT_CHANGE = "When command is Update chatName must not change."
const val UPDATE_COMMAND_PARTICIPANTS_SHOULD_NOT_CHANGE = "When command is Update participants must not change."
}

// Command Class used to indicate that the transaction should start a new chat.
class Create: Command
// Command Class used to indicate that the transaction should append a new ChatState to an existing chat.
class Update: Command

// verify() function is used to apply contract rules to the transaction.
override fun verify(transaction: UtxoLedgerTransaction) {

// Ensures that there is only one command in the transaction
val command = transaction.commands.singleOrNull() ?: throw CordaRuntimeException(REQUIRE_SINGLE_COMMAND)

// Applies a universal constraint (applies to all transactions irrespective of command)
OUTPUT_STATE_SHOULD_ONLY_HAVE_TWO_PARTICIPANTS using {
val output = transaction.outputContractStates.first() as ChatState
output.participants.size== 2
}

TRANSACTION_SHOULD_BE_SIGNED_BY_ALL_PARTICIPANTS using {
val output = transaction.outputContractStates.first() as ChatState
transaction.signatories.containsAll(output.participants)
}

// Switches case based on the command
when(command) {
// Rules applied only to transactions with the Create Command.
is Create -> {
CREATE_COMMAND_SHOULD_HAVE_NO_INPUT_STATES using (transaction.inputContractStates.isEmpty())
CREATE_COMMAND_SHOULD_HAVE_ONLY_ONE_OUTPUT_STATE using (transaction.outputContractStates.size == 1)
}
// Rules applied only to transactions with the Update Command.
is Update -> {
UPDATE_COMMAND_SHOULD_HAVE_ONLY_ONE_INPUT_STATE using (transaction.inputContractStates.size == 1)
UPDATE_COMMAND_SHOULD_HAVE_ONLY_ONE_OUTPUT_STATE using (transaction.outputContractStates.size == 1)

val input = transaction.inputContractStates.single() as ChatState
val output = transaction.outputContractStates.single() as ChatState
UPDATE_COMMAND_ID_SHOULD_NOT_CHANGE using (input.id == output.id)
UPDATE_COMMAND_CHATNAME_SHOULD_NOT_CHANGE using (input.chatName == output.chatName)
UPDATE_COMMAND_PARTICIPANTS_SHOULD_NOT_CHANGE using (
input.participants.toSet().intersect(output.participants.toSet()).size == 2)
}
else -> {
throw CordaRuntimeException(UNKNOWN_COMMAND)
}
}
}

// Helper function to allow writing constraints in the Corda 4 '"text" using (boolean)' style
private infix fun String.using(expr: Boolean) {
if (!expr) throw CordaRuntimeException("Failed requirement: $this")
}

// Helper function to allow writing constraints in '"text" using {lambda}' style where the last expression
// in the lambda is a boolean.
private infix fun String.using(expr: () -> Boolean) {
if (!expr.invoke()) throw CordaRuntimeException("Failed requirement: $this")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.r3.developers.cordapptemplate.customStateJSONandQuery.states

import net.corda.v5.application.marshalling.JsonMarshallingService
import net.corda.v5.ledger.utxo.query.json.ContractStateVaultJsonFactory

// This class represents a custom JSON factory for serializing/deserializing ChatState objects to JSON format after each use.
class ChatJsonFactory : ContractStateVaultJsonFactory<ChatState> {

// This function specifies the type of state this factory is responsible for.
override fun getStateType(): Class<ChatState> = ChatState::class.java

// Companion object containing constants used for JSON key names.
companion object {
const val ID = "Id"
const val CHATNAME = "chatName"
const val MESSAGE = "messageContent"
const val MESSAGEFROM = "messageContentFrom"
}

// This function creates a JSON representation of a ChatState object.
override fun create(state: ChatState, jsonMarshallingService: JsonMarshallingService): String {

// Constructs a map representing the ChatState object using the provided constants.
val jsonMap = mapOf(
Pair(ID, state.id),
Pair(CHATNAME, state.chatName),
Pair(MESSAGE, state.message),
Pair(MESSAGEFROM, state.messageFrom)
)

// Uses the JsonMarshallingService's format() function to serialize the map to JSON.
return jsonMarshallingService.format(jsonMap)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.r3.developers.cordapptemplate.customStateJSONandQuery.states

import com.r3.developers.cordapptemplate.customStateJSONandQuery.contracts.ChatContract
import net.corda.v5.base.types.MemberX500Name
import net.corda.v5.ledger.utxo.BelongsToContract
import net.corda.v5.ledger.utxo.ContractState
import java.security.PublicKey
import java.util.*


// The ChatState represents data stored on ledger. A chat consists of a linear series of messages between two
// participants and is represented by a UUID. Any given pair of participants can have multiple chats
// Each ChatState stores one message between the two participants in the chat. The backchain of ChatStates
// represents the history of the chat.

@BelongsToContract(ChatContract::class)
data class ChatState(
// Unique identifier for the chat.
val id : UUID = UUID.randomUUID(),
// Non-unique name for the chat.
val chatName: String,
// The MemberX500Name of the participant who sent the message.
val messageFrom: MemberX500Name,
// The message
val message: String,
// The participants to the chat, represented by their public key.
private val participants: List<PublicKey>) : ContractState {

override fun getParticipants(): List<PublicKey> {
return participants
}

// Helper function to create a new ChatState from the previous (input) ChatState.
fun updateMessage(messageFrom: MemberX500Name, message: String) =
copy(messageFrom = messageFrom, message = message)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.r3.developers.cordapptemplate.customStateJSONandQuery.states

import net.corda.v5.ledger.utxo.query.VaultNamedQueryFactory
import net.corda.v5.ledger.utxo.query.registration.VaultNamedQueryBuilderFactory

class ChatCustomQueryFactory : VaultNamedQueryFactory {
override fun create(vaultNamedQueryBuilderFactory: VaultNamedQueryBuilderFactory) {
vaultNamedQueryBuilderFactory.create("GET_ALL_MSG")
.whereJson(
"WHERE visible_states.custom_representation ? 'com.r3.developers.cordapptemplate.customStateJSONandQuery.states.ChatState' "
)
.register()

vaultNamedQueryBuilderFactory.create("GET_MSG_FROM")
.whereJson(
"WHERE visible_states.custom_representation -> 'com.r3.developers.cordapptemplate.customStateJSONandQuery.states.ChatState' ->> 'messageContentFrom' = :nameOfSender"
)
.register()
}
}
2 changes: 1 addition & 1 deletion kotlin-samples/customeStateJSONandQuery/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pluginManagement {
}

// Root project name, used in naming the project as a whole and used in naming objects built by the project.
rootProject.name = 'customeStateJSONandQuery'
rootProject.name = 'Custom-State-JSON-and-Query'
include ':workflows'
include ':contracts'

Loading

0 comments on commit 2fa0b42

Please sign in to comment.