From 5d05385c8bff83a3820a043ac4a4663c29182b17 Mon Sep 17 00:00:00 2001 From: ajithathayahar Date: Mon, 11 Mar 2019 22:06:54 +1100 Subject: [PATCH 1/3] Updated the code to create session only if the recipient is not the node running the flow --- .../sdk/token/workflow/flows/IssueToken.kt | 29 ++++++++----- .../sdk/token/workflow/flows/MoveToken.kt | 7 +++- .../workflow/flows/UpdateEvolvableToken.kt | 6 ++- .../sdk/token/workflow/TokenFlowTests.kt | 42 ++++++++++++++++++- 4 files changed, 70 insertions(+), 14 deletions(-) diff --git a/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/IssueToken.kt b/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/IssueToken.kt index 78b27a93..81a1be1c 100644 --- a/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/IssueToken.kt +++ b/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/IssueToken.kt @@ -72,18 +72,21 @@ object IssueToken { @Suspendable override fun call(): SignedTransaction { // This is the identity which will be used to issue tokens. - // We also need a session for the other side. val me: Party = ourIdentity - val ownerSession = initiateFlow(owner) - - // Notify the recipient that we'll be issuing them a tokens and advise them of anything they must do, e.g. - // generate a confidential identity for the issuer or sign up for updates for evolvable tokens. - ownerSession.send(TokenIssuanceNotification(anonymous = anonymous)) - + var ownerSession : FlowSession? = null // This is the recipient of the tokens identity. - val owningParty: AbstractParty = if (anonymous) { - subFlow(RequestConfidentialIdentity.Initiator(ownerSession)).party.anonymise() - } else owner + val owningParty: AbstractParty + + if(me != owner) { + // We also need a session for the other side. + ownerSession = initiateFlow(owner) + // Notify the recipient that we'll be issuing them a tokens and advise them of anything they must do, e.g. + // generate a confidential identity for the issuer or sign up for updates for evolvable tokens. + ownerSession.send(TokenIssuanceNotification(anonymous = anonymous)) + owningParty = if (anonymous) subFlow(RequestConfidentialIdentity.Initiator(ownerSession)).party.anonymise() else owner + } else { + owningParty = owner + } // Create the issued token. We add this to the commands for grouping. val issuedToken: IssuedTokenType = token issuedBy me @@ -118,7 +121,11 @@ object IssueToken { // Sign the transaction. Only Concrete Parties should be used here. val stx: SignedTransaction = serviceHub.signInitialTransaction(utx) // No need to pass in a session as there's no counterparty involved. - return subFlow(FinalityFlow(transaction = stx, sessions = listOf(ownerSession))) + return if(me != owner) { + subFlow(FinalityFlow(transaction = stx, sessions = listOf(ownerSession!!))) + } else { + subFlow(FinalityFlow(transaction = stx, sessions = emptyList())) + } } } diff --git a/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/MoveToken.kt b/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/MoveToken.kt index 2e97d38b..4cdcc5e3 100644 --- a/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/MoveToken.kt +++ b/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/MoveToken.kt @@ -2,6 +2,7 @@ package com.r3.corda.sdk.token.workflow.flows import co.paralleluniverse.fibers.Suspendable import com.r3.corda.sdk.token.contracts.commands.MoveTokenCommand +import com.r3.corda.sdk.token.contracts.types.TokenPointer import com.r3.corda.sdk.token.contracts.types.TokenType import com.r3.corda.sdk.token.contracts.utilities.withNotary import com.r3.corda.sdk.token.workflow.selection.TokenSelection @@ -40,7 +41,11 @@ object MoveToken { val owningParty: AbstractParty = if (anonymous) { subFlow(RequestConfidentialIdentity.Initiator(ownerSession)).party.anonymise() } else owner - + + if (ownedToken is TokenPointer<*>) { + subFlow(AddPartyToDistributionList(owner, ownedToken.pointer.pointer)) + } + val (builder, keys) = if (amount == null) { // The assumption here is that there is only one owned token of a particular type at any one time. // Double clarify this in the docs to ensure that it is used properly. Either way, this code likely diff --git a/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/UpdateEvolvableToken.kt b/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/UpdateEvolvableToken.kt index 810ae266..1efeaac0 100644 --- a/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/UpdateEvolvableToken.kt +++ b/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/UpdateEvolvableToken.kt @@ -33,7 +33,11 @@ class UpdateEvolvableToken( val stx: SignedTransaction = serviceHub.signInitialTransaction(utx) // Get the list of parties on the distribution list for this evolvable token. val updateSubscribers: List = getDistributionList(serviceHub, new.linearId) - val sessions = updateSubscribers.map { initiateFlow(it.party) } + // If the token is self-issued, subscribers list will contain ourIdentity. + // Remove ourIdentity to avoid initiating a session to itself. + val filteredList = updateSubscribers.filter { it.party != ourIdentity } + + val sessions = filteredList.map { initiateFlow(it.party) } // Send to all on the list. This could take a while if there are many subscribers! return subFlow(FinalityFlow(transaction = stx, sessions = sessions)) } diff --git a/workflow/src/test/kotlin/com/r3/corda/sdk/token/workflow/TokenFlowTests.kt b/workflow/src/test/kotlin/com/r3/corda/sdk/token/workflow/TokenFlowTests.kt index 2ff12e36..7b09d5d4 100644 --- a/workflow/src/test/kotlin/com/r3/corda/sdk/token/workflow/TokenFlowTests.kt +++ b/workflow/src/test/kotlin/com/r3/corda/sdk/token/workflow/TokenFlowTests.kt @@ -6,10 +6,12 @@ import com.r3.corda.sdk.token.money.GBP import com.r3.corda.sdk.token.workflow.statesAndContracts.House import com.r3.corda.sdk.token.workflow.utilities.getDistributionList import com.r3.corda.sdk.token.workflow.utilities.getLinearStateById +import com.r3.corda.sdk.token.workflow.utilities.tokenBalance import net.corda.core.contracts.LinearState import net.corda.core.contracts.StateAndRef import net.corda.core.node.services.queryBy import net.corda.core.utilities.getOrThrow +import net.corda.finance.AMOUNT import net.corda.testing.node.StartedMockNode import org.junit.Before import org.junit.Test @@ -140,5 +142,43 @@ class TokenFlowTests : MockNetworkTest(numberOfNodes = 3) { val issueTokenB = I.issueTokens(housePointer, A, NOTARY, 50 of housePointer).getOrThrow() A.watchForTransaction(issueTokenB.id).toCompletableFuture().getOrThrow() } - + + @Test + fun `create evolvable token, then self issue`() { + //This is to test the self issue flow, as the counter party session is not created + val house = House("24 Leinster Gardens, Bayswater, London", 900_000.GBP, listOf(I.legalIdentity())) + val createTokenTx = I.createEvolvableToken(house, NOTARY.legalIdentity()).getOrThrow() + val houseToken: StateAndRef = createTokenTx.singleOutput() + val housePointer: TokenPointer = house.toPointer() + I.issueTokens(housePointer, I, NOTARY, 1000 of housePointer).getOrThrow() + val houseTokenState = I.services.vaultService.queryBy().states.single() + assertEquals(houseToken, houseTokenState) + val moveTokenTx = I.moveTokens(housePointer, A, 250 of housePointer, anonymous = true).getOrThrow() + A.watchForTransaction(moveTokenTx.id).getOrThrow() + val issuerTokenBalance = I.services.vaultService.tokenBalance(housePointer) + assertEquals(issuerTokenBalance, AMOUNT(750, housePointer)) + val aPartyTokenBalance = A.services.vaultService.tokenBalance(housePointer) + assertEquals(aPartyTokenBalance, AMOUNT(250, housePointer)) + } + + @Test + fun `create evolvable token, then self issue, move and update`() { + //This is to test the self issue flow, as the counter party session is not created + val house = House("24 Leinster Gardens, Bayswater, London", 900_000.GBP, listOf(I.legalIdentity())) + val createTokenTx = I.createEvolvableToken(house, NOTARY.legalIdentity()).getOrThrow() + val houseToken: StateAndRef = createTokenTx.singleOutput() + val housePointer: TokenPointer = house.toPointer() + I.issueTokens(housePointer, I, NOTARY, 1000 of housePointer).getOrThrow() + val houseTokenState = I.services.vaultService.queryBy().states.single() + assertEquals(houseToken, houseTokenState) + val moveTokenTx = I.moveTokens(housePointer, A, 250 of housePointer, anonymous = true).getOrThrow() + A.watchForTransaction(moveTokenTx.id).getOrThrow() + val proposedToken = house.copy(valuation = 950_000.GBP) + val updateTx = I.updateEvolvableToken(houseToken, proposedToken).getOrThrow() + A.watchForTransaction(updateTx.id).getOrThrow() + val houseQuery= A.services.vaultService.queryBy().states + val updatedHouse = houseQuery.single().state.data + assertEquals(updatedHouse.valuation, proposedToken.valuation) + } + } \ No newline at end of file From 36decb4fd238165d1a9829d9f4ed406e6ed92958 Mon Sep 17 00:00:00 2001 From: ajithathayahar Date: Mon, 11 Mar 2019 22:19:15 +1100 Subject: [PATCH 2/3] Checked whether filteredList is not empty before initiating the flow in UpdateEvolvableToken.kt --- .../sdk/token/workflow/flows/UpdateEvolvableToken.kt | 9 ++++++--- .../com/r3/corda/sdk/token/workflow/TokenFlowTests.kt | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/UpdateEvolvableToken.kt b/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/UpdateEvolvableToken.kt index 1efeaac0..295c681f 100644 --- a/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/UpdateEvolvableToken.kt +++ b/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/UpdateEvolvableToken.kt @@ -37,8 +37,11 @@ class UpdateEvolvableToken( // Remove ourIdentity to avoid initiating a session to itself. val filteredList = updateSubscribers.filter { it.party != ourIdentity } - val sessions = filteredList.map { initiateFlow(it.party) } - // Send to all on the list. This could take a while if there are many subscribers! - return subFlow(FinalityFlow(transaction = stx, sessions = sessions)) + return if(filteredList.isNotEmpty()){ + val sessions = filteredList.map { initiateFlow(it.party) } + // Send to all on the list. This could take a while if there are many subscribers! + subFlow(FinalityFlow(transaction = stx, sessions = sessions)) + } else + subFlow(FinalityFlow(transaction = stx, sessions = emptyList())) } } \ No newline at end of file diff --git a/workflow/src/test/kotlin/com/r3/corda/sdk/token/workflow/TokenFlowTests.kt b/workflow/src/test/kotlin/com/r3/corda/sdk/token/workflow/TokenFlowTests.kt index 7b09d5d4..3b080130 100644 --- a/workflow/src/test/kotlin/com/r3/corda/sdk/token/workflow/TokenFlowTests.kt +++ b/workflow/src/test/kotlin/com/r3/corda/sdk/token/workflow/TokenFlowTests.kt @@ -180,5 +180,4 @@ class TokenFlowTests : MockNetworkTest(numberOfNodes = 3) { val updatedHouse = houseQuery.single().state.data assertEquals(updatedHouse.valuation, proposedToken.valuation) } - } \ No newline at end of file From ec303ddfd7c515775782ee57c9b9c9b0960ef4ae Mon Sep 17 00:00:00 2001 From: ajithathayahar Date: Mon, 25 Mar 2019 22:31:15 +1100 Subject: [PATCH 3/3] changed to conditional statement for owningParty in IssueToken flow as per comment --- .idea/codeStyles/Project.xml | 52 +++++++++++++++++++ .../sdk/token/workflow/flows/IssueToken.kt | 22 ++++---- 2 files changed, 63 insertions(+), 11 deletions(-) create mode 100644 .idea/codeStyles/Project.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..6d799a05 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/IssueToken.kt b/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/IssueToken.kt index 81a1be1c..c0319e4f 100644 --- a/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/IssueToken.kt +++ b/workflow/src/main/kotlin/com/r3/corda/sdk/token/workflow/flows/IssueToken.kt @@ -73,20 +73,20 @@ object IssueToken { override fun call(): SignedTransaction { // This is the identity which will be used to issue tokens. val me: Party = ourIdentity - var ownerSession : FlowSession? = null - // This is the recipient of the tokens identity. - val owningParty: AbstractParty - if(me != owner) { - // We also need a session for the other side. - ownerSession = initiateFlow(owner) + // We also need a session for the other side. + var ownerSession : FlowSession? = if(me != owner) { // Notify the recipient that we'll be issuing them a tokens and advise them of anything they must do, e.g. // generate a confidential identity for the issuer or sign up for updates for evolvable tokens. - ownerSession.send(TokenIssuanceNotification(anonymous = anonymous)) - owningParty = if (anonymous) subFlow(RequestConfidentialIdentity.Initiator(ownerSession)).party.anonymise() else owner - } else { - owningParty = owner - } + val session = initiateFlow(owner) + session.send(TokenIssuanceNotification(anonymous = anonymous)) + session + } else null + + // This is the recipient of the tokens identity. + val owningParty: AbstractParty = if(ownerSession != null ) + subFlow(RequestConfidentialIdentity.Initiator(ownerSession)).party.anonymise() + else owner // Create the issued token. We add this to the commands for grouping. val issuedToken: IssuedTokenType = token issuedBy me