From 999486069751b283da888249e2dd401f07f653dc Mon Sep 17 00:00:00 2001 From: Nitesh Solanki Date: Thu, 16 Apr 2020 16:09:21 +0530 Subject: [PATCH] Add new flow to request for accountInfo by name from a particular host --- .../flows/RequestAccountInfoByNameFlows.kt | 74 ++++++++++ .../test/RequestAccountInfoByNameTest.kt | 136 ++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 workflows/src/main/kotlin/com/r3/corda/lib/accounts/workflows/flows/RequestAccountInfoByNameFlows.kt create mode 100644 workflows/src/test/kotlin/com/r3/corda/lib/accounts/workflows/test/RequestAccountInfoByNameTest.kt diff --git a/workflows/src/main/kotlin/com/r3/corda/lib/accounts/workflows/flows/RequestAccountInfoByNameFlows.kt b/workflows/src/main/kotlin/com/r3/corda/lib/accounts/workflows/flows/RequestAccountInfoByNameFlows.kt new file mode 100644 index 00000000..6ceb7ef3 --- /dev/null +++ b/workflows/src/main/kotlin/com/r3/corda/lib/accounts/workflows/flows/RequestAccountInfoByNameFlows.kt @@ -0,0 +1,74 @@ +package com.r3.corda.lib.accounts.workflows.flows + +import co.paralleluniverse.fibers.Suspendable +import com.r3.corda.lib.accounts.contracts.states.AccountInfo +import com.r3.corda.lib.accounts.workflows.internal.accountService +import net.corda.core.contracts.StateAndRef +import net.corda.core.flows.* +import net.corda.core.identity.Party +import net.corda.core.node.StatesToRecord +import net.corda.core.utilities.unwrap + +/** + * This flow can be used to check whether an account is hosted by a particular host by passing account name. If it is then the host will share + * the account info with the requester. This flow should be used in a situation where UUID is difficult to obtain for a given + * account. For e.g: a node can create accounts for its employees using SSN/NIN or employeeId + * + * @property accountName account name to request the [AccountInfo] for hosted at [host] node. + * @property host session to request the [AccountInfo] from. + */ +class RequestAccountInfoByNameFlow(private val accountName: String, val host: FlowSession) : FlowLogic() { + @Suspendable + override fun call(): AccountInfo? { + val hasAccount = host.sendAndReceive(accountName).unwrap { it } + return if (hasAccount) subFlow(ShareAccountInfoHandlerFlow(host)) else null + } +} + +/** + * Responder flow for [RequestAccountInfoByNameFlow]. + */ +class RequestAccountInfoByNameHandlerFlow(val otherSession: FlowSession) : FlowLogic() { + @Suspendable + override fun call() { + val accountName = otherSession.receive(String::class.java).unwrap {it } + val response = serviceHub.accountService.accountInfo(accountName).find { it.state.data.host == ourIdentity } + if (response == null) { + otherSession.send(false) + } else { + otherSession.send(true) + subFlow(ShareAccountInfoFlow(response, listOf(otherSession))) + } + } +} + +// Initiating versions of the above flows. + +/** + * Shares an [AccountInfo] [StateAndRef] with the supplied [Party]s. The [AccountInfo] is always stored using + * [StatesToRecord.ALL_VISIBLE]. + * + * @property accountName account name to request the [AccountInfo] for. + * @property host [Party] to request the [AccountInfo] from. + */ +@StartableByRPC +@StartableByService +@InitiatingFlow +class RequestAccountInfoByName(private val accountName: String, val host: Party) : FlowLogic() { + @Suspendable + override fun call(): AccountInfo? { + val session = initiateFlow(host) + return subFlow(RequestAccountInfoByNameFlow(accountName, session)) + } +} + +/** + * Responder flow for [RequestAccountInfoByName]. + */ +@InitiatedBy(RequestAccountInfoByName::class) +class RequestAccountInfoByNameHandler(val otherSession: FlowSession) : FlowLogic() { + @Suspendable + override fun call() { + subFlow(RequestAccountInfoByNameHandlerFlow(otherSession)) + } +} diff --git a/workflows/src/test/kotlin/com/r3/corda/lib/accounts/workflows/test/RequestAccountInfoByNameTest.kt b/workflows/src/test/kotlin/com/r3/corda/lib/accounts/workflows/test/RequestAccountInfoByNameTest.kt new file mode 100644 index 00000000..e4551032 --- /dev/null +++ b/workflows/src/test/kotlin/com/r3/corda/lib/accounts/workflows/test/RequestAccountInfoByNameTest.kt @@ -0,0 +1,136 @@ +package com.r3.corda.lib.accounts.workflows.test + +import com.r3.corda.lib.accounts.workflows.flows.* +import net.corda.testing.common.internal.testNetworkParameters +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.Assert +import org.junit.Before +import org.junit.Test +import java.lang.AssertionError +import kotlin.test.assertFailsWith + +class RequestAccountInfoByNameTest { + + lateinit var network: MockNetwork + lateinit var nodeA: StartedMockNode + lateinit var nodeB: StartedMockNode + lateinit var nodeC: StartedMockNode + + @Before + fun setup() { + network = MockNetwork( + MockNetworkParameters( + networkParameters = testNetworkParameters(minimumPlatformVersion = 4), + cordappsForAllNodes = listOf( + TestCordapp.findCordapp("com.r3.corda.lib.accounts.contracts"), + TestCordapp.findCordapp("com.r3.corda.lib.accounts.workflows") + ) + ) + ) + nodeA = network.createPartyNode() + nodeB = network.createPartyNode() + nodeC = network.createPartyNode() + + network.runNetwork() + } + + @After + fun tearDown() { + network.stopNodes() + } + + fun StartedMockNode.identity() = info.legalIdentities.first() + + /* + Should return the account info to the host which been requested + */ + + @Test + fun `should send account info to requester`() { + + //Create an account in host B + val accountB = nodeB.startFlow(CreateAccount("Test_AccountB")).runAndGet(network) + + //Call RequestAccountInfoByName from host A so that host A can request accountB's info + val accountBInfo = nodeA.startFlow(RequestAccountInfoByName("Test_AccountB", nodeB.info.legalIdentities.first())).runAndGet(network) + print(accountBInfo) + + //Checking if accountBInfo's name will be equal to the name with which the account is created + Assert.assertEquals(accountBInfo?.name, "Test_AccountB") + + //Checking if accountBInfo's name will be equal to the name of the account created + Assert.assertEquals(accountBInfo?.name, accountB.state.data.name) + } + + /* + Should throw error if the requested account is not of the host and compare expected account's name with + actual account's name + */ + + + @Test(expected = AssertionError::class) + fun `should throw error if the name of account passed is wrong and compare the expected account's and actual account's name`() { + + //Create an account in host B + val accountB = nodeB.startFlow(CreateAccount("Test_AccountB")).runAndGet(network) + + //Create an account in host C + val accountC = nodeC.startFlow(CreateAccount("Test_AccountC")).runAndGet(network) + + //To avail the account info of account B for node A, passing name of account C which is wrong name + val accountBInfo = nodeA.startFlow(RequestAccountInfoByName(accountC.state.data.name, nodeB.info.legalIdentities.first())).runAndGet(network) + + //Comparing actual account's name with expected account(account B)'s name + val resultOfAccountIdentifierComparison = Assert.assertEquals(accountBInfo?.name, accountB.state.data.name) + + //result will throw error since the name comparison do not match + assertFailsWith { resultOfAccountIdentifierComparison } + + } + + /* + Should throw error if the host passed is wrong and compare expected account's name with + actual account's name + */ + + + @Test(expected = AssertionError::class) + fun `should throw error if the account's host is wrong and compare expected account's and actual account's name`() { + + //Create an account in host A + val accountA = nodeA.startFlow(CreateAccount("Test_AccountA")).runAndGet(network) + + //To get the account info of accountA, passing host as C which is the wrong host + val accountAInfo = nodeB.startFlow(RequestAccountInfoByName(accountA.state.data.name, nodeC.info.legalIdentities.first())).runAndGet(network) + + //Comparing actual account's name with expected account(account A)'s name + val resultOfAccountIdentifierComparison = Assert.assertEquals(accountAInfo?.name, accountA.state.data.name) + + //result will throw error since the name comparison do not match + assertFailsWith { resultOfAccountIdentifierComparison } + } + + /* + This testcase check when pass wrong name of the account, the result will be null + */ + + @Test + fun `should return null if account is not found when searching by name`() { + + //Create an account in host C + val accountC = nodeC.startFlow(CreateAccount("Test_AccountC")).runAndGet(network) + + //To avail the account info of account B for node A, passing name of account C which will throw an error + val accountBInfo = nodeA.startFlow(RequestAccountInfoByName(accountC.state.data.name, nodeB.info.legalIdentities.first())).runAndGet(network) + print(accountBInfo) + + //accountBInfo will be null if the name of account entered is wrong + Assert.assertEquals(accountBInfo,null) + + } + +} \ No newline at end of file