Enable anonymisation (#1073)

* Enabled confidential identities in:
** IssuerFlow
** Trader demo
** Two party deal flows
** Two party trade flows
** Corda RPC tests
** Cash flows
** Integration testing tutorial
** Node monitor model tests
** CollectSignatureFlow
* Relay local node's confidential identities to counterparties when requesting transaction
signatures, so counterparties know where the inputs come from.
* Require all identities are known in finality flow
* Ensure all identities in FxTransactionBuildTutorial are known to each other

* Add flow for syncing identities to a number of counterparties

* Address PR comments

* Disable anonymisation in example code

* Revert unnecessary changes remaining from earlier rebases

* Corrections after rebase

* Remove unneeded identity registration

Remove unneeded identity registrations from tests, which sometimes cause duplicated entries in the database

* Revert accidental change in examples

* Revert unneeded imports

* Revert changes to CoreFlowHandlers.kt
This commit is contained in:
Ross Nicoll 2017-09-11 14:29:37 +01:00 committed by GitHub
parent 330db73c5d
commit 65dcfe4abe
18 changed files with 55 additions and 38 deletions

View File

@ -133,8 +133,8 @@ class NodeMonitorModelTest : DriverBasedTest() {
@Test
fun `cash issue and move`() {
val anonymous = false
rpc.startFlow(::CashIssueFlow, 100.DOLLARS, OpaqueBytes.of(1), notaryNode.notaryIdentity).returnValue.getOrThrow()
rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, bobNode.legalIdentity, anonymous).returnValue.getOrThrow()
val (_, issueIdentity) = rpc.startFlow(::CashIssueFlow, 100.DOLLARS, OpaqueBytes.of(1), notaryNode.notaryIdentity).returnValue.getOrThrow()
val (_, paymentIdentity) = rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, bobNode.legalIdentity).returnValue.getOrThrow()
var issueSmId: StateMachineRunId? = null
var moveSmId: StateMachineRunId? = null
@ -191,7 +191,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
require(stx.tx.outputs.size == 1)
val signaturePubKeys = stx.sigs.map { it.by }.toSet()
// Alice and Notary signed
require(aliceNode.legalIdentity.owningKey.isFulfilledBy(signaturePubKeys))
require(issueIdentity!!.owningKey.isFulfilledBy(signaturePubKeys))
require(notaryNode.notaryIdentity.owningKey.isFulfilledBy(signaturePubKeys))
moveTx = stx
}

View File

@ -6,6 +6,7 @@ import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionState
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.identity.AbstractParty
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.Party
import net.corda.core.internal.ResolveTransactionsFlow
import net.corda.core.node.ServiceHub
@ -126,6 +127,12 @@ open class FinalityFlow(val transactions: Iterable<SignedTransaction>,
// Calculate who is meant to see the results based on the participants involved.
return extractParticipants(ltx)
.map(this::partyFromAnonymous)
.map { participant ->
if (participant.wellKnown != null)
participant
else
throw IllegalArgumentException("Could not resolve well known identity of participant ${participant.participant.owningKey.toStringShort()}")
}
.toSet()
}

View File

@ -9,8 +9,9 @@ import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap
/**
* Very basic flow which exchanges transaction key and certificate paths between two parties in a transaction.
* This is intended for use as a subflow of another flow.
* Very basic flow which generates new confidential identities for parties in a transaction and exchanges the transaction
* key and certificate paths between the parties. This is intended for use as a subflow of another flow which builds a
* transaction.
*/
@StartableByRPC
@InitiatingFlow

View File

@ -219,6 +219,7 @@ class ContractUpgradeFlowTest {
val result = a.services.startFlow(CashIssueFlow(Amount(1000, USD), OpaqueBytes.of(1), notary)).resultFuture
mockNet.runNetwork()
val stx = result.getOrThrow().stx
val anonymisedRecipient = result.get().recipient!!
val stateAndRef = stx.tx.outRef<Cash.State>(0)
val baseState = a.database.transaction { a.services.vaultQueryService.queryBy<ContractState>().states.single() }
assertTrue(baseState.state.data is Cash.State, "Contract state is old version.")
@ -230,7 +231,7 @@ class ContractUpgradeFlowTest {
val firstState = a.database.transaction { a.services.vaultQueryService.queryBy<ContractState>().states.single() }
assertTrue(firstState.state.data is CashV2.State, "Contract state is upgraded to the new version.")
assertEquals(Amount(1000000, USD).`issued by`(a.info.legalIdentity.ref(1)), (firstState.state.data as CashV2.State).amount, "Upgraded cash contain the correct amount.")
assertEquals<Collection<AbstractParty>>(listOf(a.info.legalIdentity), (firstState.state.data as CashV2.State).owners, "Upgraded cash belongs to the right owner.")
assertEquals<Collection<AbstractParty>>(listOf(anonymisedRecipient), (firstState.state.data as CashV2.State).owners, "Upgraded cash belongs to the right owner.")
}
class CashV2 : UpgradedContract<Cash.State, CashV2.State> {

View File

@ -7,12 +7,14 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.finance.GBP
import net.corda.finance.contracts.asset.Cash
import net.corda.testing.ALICE
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockServices
import org.junit.After
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class FinalityFlowTests {
lateinit var mockNet: MockNetwork
@ -53,4 +55,18 @@ class FinalityFlowTests {
}
assertEquals(notarisedTx, transactionSeenByB)
}
@Test
fun `reject a transaction with unknown parties`() {
val amount = Amount(1000, Issued(nodeA.info.legalIdentity.ref(0), GBP))
val fakeIdentity = ALICE // Alice isn't part of this network, so node A won't recognise them
val builder = TransactionBuilder(notary)
Cash().generateIssue(builder, amount, fakeIdentity, notary)
val stx = nodeA.services.signInitialTransaction(builder)
val flow = nodeA.services.startFlow(FinalityFlow(stx))
mockNet.runNetwork()
assertFailsWith<IllegalArgumentException> {
flow.resultFuture.getOrThrow()
}
}
}

View File

@ -116,10 +116,10 @@ class CordaRPCOpsImplTest {
)
}
result.returnValue.getOrThrow()
val anonymisedRecipient = result.returnValue.getOrThrow().recipient!!
val expectedState = Cash.State(Amount(quantity,
Issued(aliceNode.info.legalIdentity.ref(ref), GBP)),
recipient)
anonymisedRecipient)
// Query vault via RPC
val cash = rpc.vaultQueryBy<Cash.State>()
@ -141,7 +141,6 @@ class CordaRPCOpsImplTest {
vaultTrackCash = rpc.vaultTrackBy<Cash.State>().updates
}
val anonymous = false
val result = rpc.startFlow(::CashIssueFlow,
100.DOLLARS,
OpaqueBytes(ByteArray(1, { 1 })),
@ -150,7 +149,7 @@ class CordaRPCOpsImplTest {
mockNet.runNetwork()
rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, aliceNode.info.legalIdentity, anonymous)
rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, aliceNode.info.legalIdentity)
mockNet.runNetwork()
@ -183,7 +182,7 @@ class CordaRPCOpsImplTest {
require(stx.tx.inputs.isEmpty())
require(stx.tx.outputs.size == 1)
val signaturePubKeys = stx.sigs.map { it.by }.toSet()
// Only Alice signed
// Only Alice signed, as issuer
val aliceKey = aliceNode.info.legalIdentity.owningKey
require(signaturePubKeys.size <= aliceKey.keys.size)
require(aliceKey.isFulfilledBy(signaturePubKeys))
@ -194,7 +193,7 @@ class CordaRPCOpsImplTest {
require(stx.tx.outputs.size == 1)
val signaturePubKeys = stx.sigs.map { it.by }.toSet()
// Alice and Notary signed
require(aliceNode.info.legalIdentity.owningKey.isFulfilledBy(signaturePubKeys))
require(aliceNode.services.keyManagementService.filterMyKeys(signaturePubKeys).toList().isNotEmpty())
require(notaryNode.info.notaryIdentity.owningKey.isFulfilledBy(signaturePubKeys))
}
)

View File

@ -337,10 +337,10 @@ class FlowFrameworkTests {
node1.services.startFlow(CashIssueFlow(
2000.DOLLARS,
OpaqueBytes.of(0x01),
notary1.info.notaryIdentity))
notary1.info.notaryIdentity)).resultFuture.getOrThrow()
// We pay a couple of times, the notary picking should go round robin
for (i in 1..3) {
val flow = node1.services.startFlow(CashPaymentFlow(500.DOLLARS, node2.info.legalIdentity, anonymous = false))
val flow = node1.services.startFlow(CashPaymentFlow(500.DOLLARS, node2.info.legalIdentity))
mockNet.runNetwork()
flow.resultFuture.getOrThrow()
}

View File

@ -17,9 +17,8 @@ class BankOfCordaHttpAPITest {
val bigCorpNodeFuture = startNode(providedName = BIGCORP_LEGAL_NAME)
val nodeBankOfCordaFuture = startNode(providedName = BOC.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
val (nodeBankOfCorda) = listOf(nodeBankOfCordaFuture, bigCorpNodeFuture).map { it.getOrThrow() }
val anonymous = false
val nodeBankOfCordaApiAddr = startWebserver(nodeBankOfCorda).getOrThrow().listenAddress
assertTrue(BankOfCordaClientApi(nodeBankOfCordaApiAddr).requestWebIssue(IssueRequestParams(1000, "USD", BIGCORP_LEGAL_NAME, "1", BOC.name, BOC.name, anonymous)))
assertTrue(BankOfCordaClientApi(nodeBankOfCordaApiAddr).requestWebIssue(IssueRequestParams(1000, "USD", BIGCORP_LEGAL_NAME, "1", BOC.name, BOC.name)))
}, isDebug = true)
}
}

View File

@ -47,7 +47,7 @@ class BankOfCordaRPCClientTest {
1000.DOLLARS, BIG_CORP_PARTY_REF,
nodeBigCorporation.nodeInfo.legalIdentity,
anonymous,
nodeBankOfCorda.nodeInfo.notaryIdentity)
nodeBankOfCorda.nodeInfo.notaryIdentity).returnValue.getOrThrow()
// Check Bank of Corda Vault Updates
vaultUpdatesBoc.expectEvents {

View File

@ -55,8 +55,7 @@ private class BankOfCordaDriver {
// The ISSUE_CASH will request some Cash from the ISSUER on behalf of Big Corporation node
val role = options.valueOf(roleArg)!!
val anonymous = true
val requestParams = IssueRequestParams(options.valueOf(quantity), options.valueOf(currency), BIGCORP_LEGAL_NAME, "1", BOC.name, DUMMY_NOTARY.name, anonymous)
val requestParams = IssueRequestParams(options.valueOf(quantity), options.valueOf(currency), BIGCORP_LEGAL_NAME, "1", BOC.name, DUMMY_NOTARY.name)
try {
when (role) {

View File

@ -46,9 +46,10 @@ class BankOfCordaClientApi(val hostAndPort: NetworkHostAndPort) {
?: throw IllegalStateException("Unable to locate notary node in network map cache")
val amount = Amount(params.amount, Currency.getInstance(params.currency))
val anonymous = true
val issuerBankPartyRef = OpaqueBytes.of(params.issuerBankPartyRef.toByte())
return rpc.startFlow(::CashIssueAndPaymentFlow, amount, issuerBankPartyRef, issueToParty, params.anonymous, notaryNode.notaryIdentity)
return rpc.startFlow(::CashIssueAndPaymentFlow, amount, issuerBankPartyRef, issueToParty, anonymous, notaryNode.notaryIdentity)
.returnValue.getOrThrow().stx
}
}

View File

@ -20,8 +20,7 @@ class BankOfCordaWebApi(val rpc: CordaRPCOps) {
data class IssueRequestParams(val amount: Long, val currency: String,
val issueToPartyName: X500Name, val issuerBankPartyRef: String,
val issuerBankName: X500Name,
val notaryName: X500Name,
val anonymous: Boolean)
val notaryName: X500Name)
private companion object {
val logger = loggerFor<BankOfCordaWebApi>()
@ -51,12 +50,13 @@ class BankOfCordaWebApi(val rpc: CordaRPCOps) {
?: return Response.status(Response.Status.FORBIDDEN).entity("Unable to locate $notaryParty in network map service").build()
val amount = Amount(params.amount, Currency.getInstance(params.currency))
val anonymous = true
val issuerBankPartyRef = OpaqueBytes.of(params.issuerBankPartyRef.toByte())
// invoke client side of Issuer Flow: IssuanceRequester
// The line below blocks and waits for the future to resolve.
return try {
rpc.startFlow(::CashIssueAndPaymentFlow, amount, issuerBankPartyRef, issueToParty, params.anonymous, notaryNode.notaryIdentity).returnValue.getOrThrow()
rpc.startFlow(::CashIssueAndPaymentFlow, amount, issuerBankPartyRef, issueToParty, anonymous, notaryNode.notaryIdentity).returnValue.getOrThrow()
logger.info("Issue and payment request completed successfully: $params")
Response.status(Response.Status.CREATED).build()
} catch (e: Exception) {

View File

@ -56,7 +56,6 @@ class TraderDemoTest : NodeBasedTest() {
val expectedBCash = clientB.cashCount + 1
val expectedPaper = listOf(clientA.commercialPaperCount + 1, clientB.commercialPaperCount)
// TODO: Enable anonymisation
clientBank.runIssuer(amount = 100.DOLLARS, buyerName = nodeA.info.legalIdentity.name, sellerName = nodeB.info.legalIdentity.name)
clientB.runSeller(buyerName = nodeA.info.legalIdentity.name, amount = 5.DOLLARS)

View File

@ -51,12 +51,11 @@ class TraderDemoClientApi(val rpc: CordaRPCOps) {
val notaryNode = rpc.nodeIdentityFromParty(notaryLegalIdentity)
?: throw IllegalStateException("Unable to locate notary node in network map cache")
val amounts = calculateRandomlySizedAmounts(amount, 3, 10, Random())
val anonymous = false
rpc.startFlow(::CashIssueFlow, amount, OpaqueBytes.of(1), notaryNode.notaryIdentity).returnValue.getOrThrow()
// Pay random amounts of currency up to the requested amount
amounts.forEach { pennies ->
// TODO This can't be done in parallel, perhaps due to soft-locking issues?
rpc.startFlow(::CashPaymentFlow, amount.copy(quantity = pennies), buyer, anonymous).returnValue.getOrThrow()
rpc.startFlow(::CashPaymentFlow, amount.copy(quantity = pennies), buyer).returnValue.getOrThrow()
}
println("Cash issued to buyer")

View File

@ -149,8 +149,7 @@ class NewTransaction : Fragment() {
dialogPane = root
initOwner(window)
setResultConverter {
// TODO: Enable confidential identities
val anonymous = false
val anonymous = true
val defaultRef = OpaqueBytes.of(1)
val issueRef = if (issueRef.value != null) OpaqueBytes.of(issueRef.value) else defaultRef
when (it) {

View File

@ -123,14 +123,13 @@ val crossCashTest = LoadTest<CrossCashCommand, CrossCashState>(
generate = { (nodeVaults), parallelism ->
val nodeMap = simpleNodes.associateBy { it.info.legalIdentity }
val anonymous = true
Generator.pickN(parallelism, simpleNodes).flatMap { nodes ->
Generator.sequence(
nodes.map { node ->
val quantities = nodeVaults[node.info.legalIdentity] ?: mapOf()
val possibleRecipients = nodeMap.keys.toList()
val moves = quantities.map {
it.value.toDouble() / 1000 to generateMove(it.value, USD, node.info.legalIdentity, possibleRecipients, anonymous)
it.value.toDouble() / 1000 to generateMove(it.value, USD, node.info.legalIdentity, possibleRecipients)
}
val exits = quantities.mapNotNull {
if (it.key == node.info.legalIdentity) {
@ -140,7 +139,7 @@ val crossCashTest = LoadTest<CrossCashCommand, CrossCashState>(
}
}
val command = Generator.frequency(
listOf(1.0 to generateIssue(10000, USD, notary.info.notaryIdentity, possibleRecipients, anonymous)) + moves + exits
listOf(1.0 to generateIssue(10000, USD, notary.info.notaryIdentity, possibleRecipients)) + moves + exits
)
command.map { CrossCashCommand(it, nodeMap[node.info.legalIdentity]!!) }
}

View File

@ -16,14 +16,13 @@ fun generateIssue(
max: Long,
currency: Currency,
notary: Party,
possibleRecipients: List<Party>,
anonymous: Boolean
possibleRecipients: List<Party>
): Generator<IssueAndPaymentRequest> {
return generateAmount(1, max, Generator.pure(currency)).combine(
Generator.pure(OpaqueBytes.of(0)),
Generator.pickOne(possibleRecipients)
) { amount, ref, recipient ->
IssueAndPaymentRequest(amount, ref, recipient, notary, anonymous)
IssueAndPaymentRequest(amount, ref, recipient, notary, true)
}
}
@ -31,13 +30,12 @@ fun generateMove(
max: Long,
currency: Currency,
issuer: Party,
possibleRecipients: List<Party>,
anonymous: Boolean
possibleRecipients: List<Party>
): Generator<PaymentRequest> {
return generateAmount(1, max, Generator.pure(Issued(PartyAndReference(issuer, OpaqueBytes.of(0)), currency))).combine(
Generator.pickOne(possibleRecipients)
) { amount, recipient ->
PaymentRequest(amount.withoutIssuer(), recipient, anonymous, setOf(issuer))
PaymentRequest(amount.withoutIssuer(), recipient, true, setOf(issuer))
}
}

View File

@ -38,7 +38,7 @@ val selfIssueTest = LoadTest<SelfIssueCommand, SelfIssueState>(
generate = { _, parallelism ->
val generateIssue = Generator.pickOne(simpleNodes).flatMap { node ->
generateIssue(1000, USD, notary.info.notaryIdentity, listOf(node.info.legalIdentity), anonymous = true).map {
generateIssue(1000, USD, notary.info.notaryIdentity, listOf(node.info.legalIdentity)).map {
SelfIssueCommand(it, node)
}
}