mirror of
https://github.com/corda/corda.git
synced 2025-06-17 22:58:19 +00:00
Change CashIssueFlow to use anonymous identity
* Add functions for: * Retrieving nodes via their legal identity * Filtering a set of public keys down to those the node has corresponding private keys for * Modify contract upgrade flows to handle identifying participants after an anomymisation step * Correct terminology: "party who" -> "party which" * Modify CashIssueFlow and CashPaymentFlow to optionally use an anonymous identity for the recipient.
This commit is contained in:
@ -260,6 +260,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
rpcFlows = emptyList()
|
||||
}
|
||||
|
||||
// TODO: Investigate having class path scanning find this flow
|
||||
registerInitiatedFlow(TxKeyFlow.Provider::class.java)
|
||||
// TODO Remove this once the cash stuff is in its own CorDapp
|
||||
registerInitiatedFlow(IssuerFlow.Issuer::class.java)
|
||||
|
||||
@ -459,7 +461,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
val storageServices = initialiseStorageService(configuration.baseDirectory)
|
||||
storage = storageServices.first
|
||||
checkpointStorage = storageServices.second
|
||||
netMapCache = InMemoryNetworkMapCache()
|
||||
netMapCache = InMemoryNetworkMapCache(services)
|
||||
network = makeMessagingService()
|
||||
schemas = makeSchemaService()
|
||||
vault = makeVaultService(configuration.dataSourceProperties)
|
||||
|
@ -5,11 +5,11 @@ import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.crypto.keys
|
||||
import net.corda.core.crypto.sign
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.flows.AnonymisedIdentity
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
import java.security.KeyPair
|
||||
@ -58,7 +58,7 @@ class E2ETestKeyManagementService(val identityService: IdentityService,
|
||||
return keyPair.public
|
||||
}
|
||||
|
||||
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): Pair<X509CertificateHolder, CertPath> {
|
||||
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymisedIdentity {
|
||||
return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled)
|
||||
}
|
||||
|
||||
@ -71,6 +71,10 @@ class E2ETestKeyManagementService(val identityService: IdentityService,
|
||||
}
|
||||
}
|
||||
|
||||
override fun filterMyKeys(candidateKeys: Iterable<PublicKey>): Iterable<PublicKey> {
|
||||
return mutex.locked { candidateKeys.filter { it in this.keys } }
|
||||
}
|
||||
|
||||
override fun sign(bytes: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
|
||||
val keyPair = getSigningKeyPair(publicKey)
|
||||
val signature = keyPair.sign(bytes)
|
||||
|
@ -4,6 +4,7 @@ import net.corda.core.crypto.*
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.flows.AnonymisedIdentity
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
import java.security.KeyPair
|
||||
@ -30,7 +31,7 @@ fun freshCertificate(identityService: IdentityService,
|
||||
subjectPublicKey: PublicKey,
|
||||
issuer: PartyAndCertificate,
|
||||
issuerSigner: ContentSigner,
|
||||
revocationEnabled: Boolean = false): Pair<X509CertificateHolder, CertPath> {
|
||||
revocationEnabled: Boolean = false): AnonymisedIdentity {
|
||||
val issuerCertificate = issuer.certificate
|
||||
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, Duration.ofDays(10 * 365), issuerCertificate)
|
||||
val ourCertificate = Crypto.createCertificate(CertificateType.IDENTITY, issuerCertificate.subject, issuerSigner, issuer.name, subjectPublicKey, window)
|
||||
@ -39,7 +40,7 @@ fun freshCertificate(identityService: IdentityService,
|
||||
identityService.registerAnonymousIdentity(AnonymousParty(subjectPublicKey),
|
||||
issuer.party,
|
||||
ourCertPath)
|
||||
return Pair(issuerCertificate, ourCertPath)
|
||||
return AnonymisedIdentity(ourCertPath, issuerCertificate, subjectPublicKey)
|
||||
}
|
||||
|
||||
fun getSigner(issuerKeyPair: KeyPair): ContentSigner {
|
||||
|
@ -9,6 +9,7 @@ import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.flows.AnonymisedIdentity
|
||||
import net.corda.node.utilities.*
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
@ -60,6 +61,10 @@ class PersistentKeyManagementService(val identityService: IdentityService,
|
||||
|
||||
override val keys: Set<PublicKey> get() = mutex.locked { keys.keys }
|
||||
|
||||
override fun filterMyKeys(candidateKeys: Iterable<PublicKey>): Iterable<PublicKey> {
|
||||
return mutex.locked { candidateKeys.filter { it in this.keys } }
|
||||
}
|
||||
|
||||
override fun freshKey(): PublicKey {
|
||||
val keyPair = generateKeyPair()
|
||||
mutex.locked {
|
||||
@ -68,7 +73,7 @@ class PersistentKeyManagementService(val identityService: IdentityService,
|
||||
return keyPair.public
|
||||
}
|
||||
|
||||
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): Pair<X509CertificateHolder, CertPath> {
|
||||
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymisedIdentity {
|
||||
return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled)
|
||||
}
|
||||
|
||||
|
@ -4,12 +4,15 @@ import com.google.common.annotations.VisibleForTesting
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.google.common.util.concurrent.SettableFuture
|
||||
import net.corda.core.bufferUntilSubscribed
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.map
|
||||
import net.corda.core.messaging.DataFeed
|
||||
import net.corda.core.messaging.SingleMessageRecipient
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.services.DEFAULT_SESSION_ID
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
||||
import net.corda.core.node.services.PartyInfo
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
@ -35,9 +38,13 @@ import javax.annotation.concurrent.ThreadSafe
|
||||
|
||||
/**
|
||||
* Extremely simple in-memory cache of the network map.
|
||||
*
|
||||
* @param serviceHub an optional service hub from which we'll take the identity service. We take a service hub rather
|
||||
* than the identity service directly, as this avoids problems with service start sequence (network map cache
|
||||
* and identity services depend on each other). Should always be provided except for unit test cases.
|
||||
*/
|
||||
@ThreadSafe
|
||||
open class InMemoryNetworkMapCache : SingletonSerializeAsToken(), NetworkMapCacheInternal {
|
||||
open class InMemoryNetworkMapCache(private val serviceHub: ServiceHub?) : SingletonSerializeAsToken(), NetworkMapCacheInternal {
|
||||
companion object {
|
||||
val logger = loggerFor<InMemoryNetworkMapCache>()
|
||||
}
|
||||
@ -71,6 +78,17 @@ open class InMemoryNetworkMapCache : SingletonSerializeAsToken(), NetworkMapCach
|
||||
}
|
||||
|
||||
override fun getNodeByLegalIdentityKey(identityKey: PublicKey): NodeInfo? = registeredNodes[identityKey]
|
||||
override fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? {
|
||||
val wellKnownParty = if (serviceHub != null) {
|
||||
serviceHub.identityService.partyFromAnonymous(party)
|
||||
} else {
|
||||
party
|
||||
}
|
||||
|
||||
return wellKnownParty?.let {
|
||||
getNodeByLegalIdentityKey(it.owningKey)
|
||||
}
|
||||
}
|
||||
|
||||
override fun track(): DataFeed<List<NodeInfo>, MapChange> {
|
||||
synchronized(_changed) {
|
||||
|
@ -7,6 +7,7 @@ import net.corda.core.crypto.isFulfilledBy
|
||||
import net.corda.core.crypto.keys
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.getOrThrow
|
||||
import net.corda.core.messaging.*
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.node.services.Vault
|
||||
@ -86,18 +87,11 @@ class CordaRPCOpsImplTest {
|
||||
}
|
||||
|
||||
// Tell the monitoring service node to issue some cash
|
||||
val anonymous = false
|
||||
val recipient = aliceNode.info.legalIdentity
|
||||
rpc.startFlow(::CashIssueFlow, Amount(quantity, GBP), ref, recipient, notaryNode.info.notaryIdentity)
|
||||
val result = rpc.startFlow(::CashIssueFlow, Amount(quantity, GBP), ref, recipient, notaryNode.info.notaryIdentity, anonymous)
|
||||
mockNet.runNetwork()
|
||||
|
||||
val expectedState = Cash.State(Amount(quantity,
|
||||
Issued(aliceNode.info.legalIdentity.ref(ref), GBP)),
|
||||
recipient)
|
||||
|
||||
// Query vault via RPC
|
||||
val cash = rpc.vaultQueryBy<Cash.State>()
|
||||
assertEquals(expectedState, cash.states.first().state.data)
|
||||
|
||||
var issueSmId: StateMachineRunId? = null
|
||||
stateMachineUpdates.expectEvents {
|
||||
sequence(
|
||||
@ -111,11 +105,14 @@ class CordaRPCOpsImplTest {
|
||||
)
|
||||
}
|
||||
|
||||
transactions.expectEvents {
|
||||
expect { tx ->
|
||||
assertEquals(expectedState, tx.tx.outputs.single().data)
|
||||
}
|
||||
}
|
||||
val tx = result.returnValue.getOrThrow()
|
||||
val expectedState = Cash.State(Amount(quantity,
|
||||
Issued(aliceNode.info.legalIdentity.ref(ref), GBP)),
|
||||
recipient)
|
||||
|
||||
// Query vault via RPC
|
||||
val cash = rpc.vaultQueryBy<Cash.State>()
|
||||
assertEquals(expectedState, cash.states.first().state.data)
|
||||
|
||||
// TODO: deprecated
|
||||
vaultUpdates.expectEvents {
|
||||
@ -135,22 +132,24 @@ class CordaRPCOpsImplTest {
|
||||
|
||||
@Test
|
||||
fun `issue and move`() {
|
||||
rpc.startFlow(::CashIssueFlow,
|
||||
val anonymous = false
|
||||
val result = rpc.startFlow(::CashIssueFlow,
|
||||
Amount(100, USD),
|
||||
OpaqueBytes(ByteArray(1, { 1 })),
|
||||
aliceNode.info.legalIdentity,
|
||||
notaryNode.info.notaryIdentity
|
||||
notaryNode.info.notaryIdentity,
|
||||
false
|
||||
)
|
||||
|
||||
mockNet.runNetwork()
|
||||
|
||||
rpc.startFlow(::CashPaymentFlow, Amount(100, USD), aliceNode.info.legalIdentity)
|
||||
rpc.startFlow(::CashPaymentFlow, Amount(100, USD), aliceNode.info.legalIdentity, anonymous)
|
||||
|
||||
mockNet.runNetwork()
|
||||
|
||||
var issueSmId: StateMachineRunId? = null
|
||||
var moveSmId: StateMachineRunId? = null
|
||||
stateMachineUpdates.expectEvents {
|
||||
stateMachineUpdates.expectEvents() {
|
||||
sequence(
|
||||
// ISSUE
|
||||
expect { add: StateMachineUpdate.Added ->
|
||||
@ -169,6 +168,7 @@ class CordaRPCOpsImplTest {
|
||||
)
|
||||
}
|
||||
|
||||
val tx = result.returnValue.getOrThrow()
|
||||
transactions.expectEvents {
|
||||
sequence(
|
||||
// ISSUE
|
||||
@ -233,7 +233,8 @@ class CordaRPCOpsImplTest {
|
||||
Amount(100, USD),
|
||||
OpaqueBytes(ByteArray(1, { 1 })),
|
||||
aliceNode.info.legalIdentity,
|
||||
notaryNode.info.notaryIdentity
|
||||
notaryNode.info.notaryIdentity,
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ open class MockServiceHubInternal(
|
||||
val network: MessagingService? = null,
|
||||
val identity: IdentityService? = MOCK_IDENTITY_SERVICE,
|
||||
val storage: TxWritableStorageService? = MockStorageService(),
|
||||
val mapCache: NetworkMapCacheInternal? = MockNetworkMapCache(),
|
||||
val mapCache: NetworkMapCacheInternal? = null,
|
||||
val scheduler: SchedulerService? = null,
|
||||
val overrideClock: Clock? = NodeClock(),
|
||||
val schemas: SchemaService? = NodeSchemaService(),
|
||||
@ -46,7 +46,7 @@ open class MockServiceHubInternal(
|
||||
override val networkService: MessagingService
|
||||
get() = network ?: throw UnsupportedOperationException()
|
||||
override val networkMapCache: NetworkMapCacheInternal
|
||||
get() = mapCache ?: throw UnsupportedOperationException()
|
||||
get() = mapCache ?: MockNetworkMapCache(this)
|
||||
override val storageService: StorageService
|
||||
get() = storage ?: throw UnsupportedOperationException()
|
||||
override val schedulerService: SchedulerService
|
||||
|
@ -60,7 +60,8 @@ class ArtemisMessagingTests {
|
||||
var messagingClient: NodeMessagingClient? = null
|
||||
var messagingServer: ArtemisMessagingServer? = null
|
||||
|
||||
val networkMapCache = InMemoryNetworkMapCache()
|
||||
// TODO: We should have a dummy service hub rather than change behaviour in tests
|
||||
val networkMapCache = InMemoryNetworkMapCache(serviceHub = null)
|
||||
|
||||
val rpcOps = object : RPCOps {
|
||||
override val protocolVersion: Int get() = throw UnsupportedOperationException()
|
||||
|
@ -6,6 +6,7 @@ import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.flows.AnonymisedIdentity
|
||||
import net.corda.flows.TxKeyFlow
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.testing.ALICE_PUBKEY
|
||||
@ -136,14 +137,14 @@ class InMemoryIdentityServiceTests {
|
||||
}
|
||||
}
|
||||
|
||||
private fun createParty(x500Name: X500Name, ca: CertificateAndKeyPair): Pair<PartyAndCertificate, TxKeyFlow.AnonymousIdentity> {
|
||||
private fun createParty(x500Name: X500Name, ca: CertificateAndKeyPair): Pair<PartyAndCertificate, AnonymisedIdentity> {
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
val issuerKeyPair = generateKeyPair()
|
||||
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public, ca)
|
||||
val txKey = Crypto.generateKeyPair()
|
||||
val txCert = X509Utilities.createCertificate(CertificateType.IDENTITY, issuer.certificate, issuerKeyPair, x500Name, txKey.public)
|
||||
val txCertPath = certFactory.generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
||||
return Pair(issuer, TxKeyFlow.AnonymousIdentity(txCertPath, txCert, AnonymousParty(txKey.public)))
|
||||
return Pair(issuer, AnonymisedIdentity(txCertPath, txCert, AnonymousParty(txKey.public)))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,11 +1,13 @@
|
||||
package net.corda.node.services.network
|
||||
|
||||
import net.corda.core.getOrThrow
|
||||
import net.corda.core.node.services.NetworkMapCache
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.utilities.ALICE
|
||||
import net.corda.core.utilities.BOB
|
||||
import net.corda.node.utilities.transaction
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import org.junit.After
|
||||
import org.junit.Test
|
||||
import java.math.BigInteger
|
||||
import kotlin.test.assertEquals
|
||||
@ -13,6 +15,11 @@ import kotlin.test.assertEquals
|
||||
class InMemoryNetworkMapCacheTest {
|
||||
private val mockNet = MockNetwork()
|
||||
|
||||
@After
|
||||
fun teardown() {
|
||||
mockNet.stopNodes()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun registerWithNetwork() {
|
||||
val (n0, n1) = mockNet.createTwoNodes()
|
||||
@ -28,6 +35,8 @@ class InMemoryNetworkMapCacheTest {
|
||||
val nodeB = mockNet.createNode(null, -1, MockNetwork.DefaultFactory, true, BOB.name, null, entropy, ServiceInfo(NetworkMapService.type))
|
||||
assertEquals(nodeA.info.legalIdentity, nodeB.info.legalIdentity)
|
||||
|
||||
mockNet.runNetwork()
|
||||
|
||||
// Node A currently knows only about itself, so this returns node A
|
||||
assertEquals(nodeA.netMapCache.getNodeByLegalIdentityKey(nodeA.info.legalIdentity.owningKey), nodeA.info)
|
||||
|
||||
@ -37,4 +46,17 @@ class InMemoryNetworkMapCacheTest {
|
||||
// The details of node B write over those for node A
|
||||
assertEquals(nodeA.netMapCache.getNodeByLegalIdentityKey(nodeA.info.legalIdentity.owningKey), nodeB.info)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getNodeByLegalIdentity`() {
|
||||
val (n0, n1) = mockNet.createTwoNodes()
|
||||
val node0Cache: NetworkMapCache = n0.services.networkMapCache
|
||||
val expected = n1.info
|
||||
|
||||
mockNet.runNetwork()
|
||||
val actual = node0Cache.getNodeByLegalIdentity(n1.info.legalIdentity)
|
||||
assertEquals(expected, actual)
|
||||
|
||||
// TODO: Should have a test case with anonymous lookup
|
||||
}
|
||||
}
|
||||
|
@ -328,10 +328,11 @@ class FlowFrameworkTests {
|
||||
2000.DOLLARS,
|
||||
OpaqueBytes.of(0x01),
|
||||
node1.info.legalIdentity,
|
||||
notary1.info.notaryIdentity))
|
||||
notary1.info.notaryIdentity,
|
||||
anonymous = false))
|
||||
// We pay a couple of times, the notary picking should go round robin
|
||||
for (i in 1..3) {
|
||||
node1.services.startFlow(CashPaymentFlow(500.DOLLARS, node2.info.legalIdentity))
|
||||
node1.services.startFlow(CashPaymentFlow(500.DOLLARS, node2.info.legalIdentity, anonymous = false))
|
||||
mockNet.runNetwork()
|
||||
}
|
||||
val endpoint = mockNet.messagingNetwork.endpoint(notary1.network.myAddress as InMemoryMessagingNetwork.PeerHandle)!!
|
||||
|
Reference in New Issue
Block a user