mirror of
https://github.com/corda/corda.git
synced 2025-01-29 15:43:55 +00:00
Nodes part of a group identity (e.g. notary) now advertise the whole group Party on the network. When sending a message to a group, a representative node advertising the group identity is first chosen (at random), and its legal identity is used for communication. Currently we assume that a single legal identity can't be advertised by more than one node (the PublicKeyTree of an identity is used for Artemis queue names and we need to do more work to properly map a single queue to multiple nodes)
This commit is contained in:
parent
c33c55eb20
commit
d855b10817
@ -7,8 +7,8 @@ import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.google.common.util.concurrent.MoreExecutors
|
||||
import com.google.common.util.concurrent.SettableFuture
|
||||
import net.corda.core.crypto.newSecureRandom
|
||||
import kotlinx.support.jdk7.use
|
||||
import net.corda.core.crypto.newSecureRandom
|
||||
import org.slf4j.Logger
|
||||
import rx.Observable
|
||||
import rx.subjects.UnicastSubject
|
||||
@ -155,6 +155,16 @@ fun <T> Iterable<T>.noneOrSingle(): T? {
|
||||
return single
|
||||
}
|
||||
|
||||
/** Returns a random element in the list, or null if empty */
|
||||
fun <T> List<T>.randomOrNull(): T? {
|
||||
if (size <= 1) return firstOrNull()
|
||||
val randomIndex = (Math.random() * size).toInt()
|
||||
return get(randomIndex)
|
||||
}
|
||||
|
||||
/** Returns a random element in the list matching the given predicate, or null if none found */
|
||||
fun <T> List<T>.randomOrNull(predicate: (T) -> Boolean) = filter(predicate).randomOrNull()
|
||||
|
||||
// An alias that can sometimes make code clearer to read.
|
||||
val RunOnCallerThread = MoreExecutors.directExecutor()
|
||||
|
||||
|
@ -72,9 +72,38 @@ interface NetworkMapCache {
|
||||
fun getNodeByLegalName(name: String): NodeInfo?
|
||||
|
||||
/**
|
||||
* Look up the node info for a public key.
|
||||
* Look up the node info for a public key tree.
|
||||
*/
|
||||
fun getNodeByPublicKey(publicKey: PublicKeyTree): NodeInfo?
|
||||
fun getNodeByPublicKeyTree(publicKeyTree: PublicKeyTree): NodeInfo?
|
||||
|
||||
/**
|
||||
* Given a [party], returns a node advertising it as an identity. If more than one node found the result
|
||||
* is chosen at random.
|
||||
*
|
||||
* In general, nodes can advertise multiple identities: a legal identity, and separate identities for each of
|
||||
* the services it provides. In case of a distributed service – run by multiple nodes – each participant advertises
|
||||
* the identity of the *whole group*. If the provided [party] is a group identity, multiple nodes advertising it
|
||||
* will be found, and this method will return a randomly chosen one. If [party] is an individual (legal) identity,
|
||||
* we currently assume that it will be advertised by one node only, which will be returned as the result.
|
||||
*/
|
||||
fun getRepresentativeNode(party: Party): NodeInfo?
|
||||
|
||||
/**
|
||||
* Gets a notary identity by the given name.
|
||||
*/
|
||||
fun getNotary(name: String): Party?
|
||||
|
||||
/**
|
||||
* Returns a notary identity advertised by any of the nodes on the network (chosen at random)
|
||||
*
|
||||
* @param type Limits the result to notaries of the specified type (optional)
|
||||
*/
|
||||
fun getAnyNotary(type: ServiceType? = null): Party?
|
||||
|
||||
/**
|
||||
* Checks whether a given party is an advertised notary identity
|
||||
*/
|
||||
fun isNotary(party: Party): Boolean
|
||||
|
||||
/**
|
||||
* Add a network map service; fetches a copy of the latest map from the service and subscribes to any further
|
||||
|
@ -20,10 +20,12 @@ abstract class BaseTransaction(
|
||||
*/
|
||||
val notary: Party?,
|
||||
/**
|
||||
* Keys that are required to have signed the wrapping [SignedTransaction], ordered to match the list of
|
||||
* signatures. There is nothing that forces the list to be the _correct_ list of signers for this
|
||||
* transaction until the transaction is verified by using [LedgerTransaction.verify]. It includes the
|
||||
* notary key, if the notary field is set.
|
||||
* Public key trees that need to be fulfilled by signatures in order for the transaction to be valid.
|
||||
* In a [SignedTransaction] this list is used to check whether there are any missing signatures. Note that
|
||||
* there is nothing that forces the list to be the _correct_ list of signers for this transaction until
|
||||
* the transaction is verified by using [LedgerTransaction.verify].
|
||||
*
|
||||
* It includes the notary key, if the notary field is set.
|
||||
*/
|
||||
val mustSign: List<PublicKeyTree>,
|
||||
/**
|
||||
|
@ -72,7 +72,7 @@ abstract class AbstractStateReplacementProtocol<T> {
|
||||
@Suspendable
|
||||
private fun collectSignatures(participants: List<PublicKeyTree>, stx: SignedTransaction): List<DigitalSignature.WithKey> {
|
||||
val parties = participants.map {
|
||||
val participantNode = serviceHub.networkMapCache.getNodeByPublicKey(it) ?:
|
||||
val participantNode = serviceHub.networkMapCache.getNodeByPublicKeyTree(it) ?:
|
||||
throw IllegalStateException("Participant $it to state $originalState not found on the network")
|
||||
participantNode.legalIdentity
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
|
||||
if (queueName.startsWith(PEERS_PREFIX) && queueName != NETWORK_MAP_ADDRESS) {
|
||||
try {
|
||||
val identity = parseKeyFromQueueName(queueName.toString())
|
||||
val nodeInfo = networkMapCache.getNodeByPublicKey(identity)
|
||||
val nodeInfo = networkMapCache.getNodeByPublicKeyTree(identity)
|
||||
if (nodeInfo != null) {
|
||||
maybeDeployBridgeForAddress(queueName, nodeInfo.address)
|
||||
} else {
|
||||
|
@ -18,6 +18,7 @@ import net.corda.core.node.services.NetworkMapCache
|
||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
||||
import net.corda.core.node.services.NetworkMapCache.MapChangeType
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.randomOrNull
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
@ -35,6 +36,8 @@ import javax.annotation.concurrent.ThreadSafe
|
||||
|
||||
/**
|
||||
* Extremely simple in-memory cache of the network map.
|
||||
*
|
||||
* TODO: some method implementations can be moved up to [NetworkMapCache]
|
||||
*/
|
||||
@ThreadSafe
|
||||
open class InMemoryNetworkMapCache : SingletonSerializeAsToken(), NetworkMapCache {
|
||||
@ -65,18 +68,40 @@ open class InMemoryNetworkMapCache : SingletonSerializeAsToken(), NetworkMapCach
|
||||
override fun get(serviceType: ServiceType) = registeredNodes.filterValues { it.advertisedServices.any { it.info.type.isSubTypeOf(serviceType) } }.map { it.value }
|
||||
override fun getRecommended(type: ServiceType, contract: Contract, vararg party: Party): NodeInfo? = get(type).firstOrNull()
|
||||
override fun getNodeByLegalName(name: String) = get().singleOrNull { it.legalIdentity.name == name }
|
||||
override fun getNodeByPublicKey(publicKey: PublicKeyTree): NodeInfo? {
|
||||
override fun getNodeByPublicKeyTree(publicKeyTree: PublicKeyTree): NodeInfo? {
|
||||
// Although we should never have more than one match, it is theoretically possible. Report an error if it happens.
|
||||
val candidates = get().filter {
|
||||
(it.legalIdentity.owningKey == publicKey)
|
||||
|| it.advertisedServices.any { it.identity.owningKey == publicKey }
|
||||
(it.legalIdentity.owningKey == publicKeyTree)
|
||||
|| it.advertisedServices.any { it.identity.owningKey == publicKeyTree }
|
||||
}
|
||||
if (candidates.size > 1) {
|
||||
throw IllegalStateException("Found more than one match for key $publicKey")
|
||||
throw IllegalStateException("Found more than one match for key $publicKeyTree")
|
||||
}
|
||||
return candidates.singleOrNull()
|
||||
}
|
||||
|
||||
override fun getRepresentativeNode(party: Party): NodeInfo? {
|
||||
return partyNodes.randomOrNull { it.legalIdentity == party || it.advertisedServices.any { it.identity == party } }
|
||||
}
|
||||
|
||||
override fun getNotary(name: String): Party? {
|
||||
val notaryNode = notaryNodes.randomOrNull { it.advertisedServices.any { it.info.type.isSubTypeOf(ServiceType.notary) && it.info.name == name } }
|
||||
return notaryNode?.notaryIdentity
|
||||
}
|
||||
|
||||
override fun getAnyNotary(type: ServiceType?): Party? {
|
||||
val nodes = if (type == null) {
|
||||
notaryNodes
|
||||
} else {
|
||||
require(type != ServiceType.notary && type.isSubTypeOf(ServiceType.notary)) { "The provided type must be a specific notary sub-type" }
|
||||
notaryNodes.filter { it.advertisedServices.any { it.info.type == type } }
|
||||
}
|
||||
|
||||
return nodes.randomOrNull()?.notaryIdentity
|
||||
}
|
||||
|
||||
override fun isNotary(party: Party) = notaryNodes.any { it.notaryIdentity == party }
|
||||
|
||||
override fun addMapService(net: MessagingService, networkMapAddress: SingleMessageRecipient, subscribe: Boolean,
|
||||
ifChangedSinceVer: Int?): ListenableFuture<Unit> {
|
||||
if (subscribe && !registeredForPush) {
|
||||
|
@ -182,11 +182,20 @@ class ProtocolStateMachineImpl<R>(override val id: StateMachineRunId,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new session. The provided [otherParty] can be an identity of any advertised service on the network,
|
||||
* and might be advertised by more than one node. Therefore we first choose a single node that advertises it
|
||||
* and use its *legal identity* for communication. At the moment a single node can compose its legal identity out of
|
||||
* multiple public keys, but we **don't support multiple nodes advertising the same legal identity**.
|
||||
*/
|
||||
@Suspendable
|
||||
private fun startNewSession(otherParty: Party, sessionProtocol: ProtocolLogic<*>, firstPayload: Any?) : ProtocolSession {
|
||||
val session = ProtocolSession(sessionProtocol, otherParty, random63BitValue(), null)
|
||||
openSessions[Pair(sessionProtocol, otherParty)] = session
|
||||
val counterpartyProtocol = sessionProtocol.getCounterpartyMarker(otherParty).name
|
||||
val node = serviceHub.networkMapCache.getRepresentativeNode(otherParty) ?: throw IllegalArgumentException("Don't know about party $otherParty")
|
||||
val nodeIdentity = node.legalIdentity
|
||||
logger.trace { "Initiating a new session with $nodeIdentity (representative of $otherParty)" }
|
||||
val session = ProtocolSession(sessionProtocol, nodeIdentity, random63BitValue(), null)
|
||||
openSessions[Pair(sessionProtocol, nodeIdentity)] = session
|
||||
val counterpartyProtocol = sessionProtocol.getCounterpartyMarker(nodeIdentity).name
|
||||
val sessionInit = SessionInit(session.ourSessionId, serviceHub.myInfo.legalIdentity, counterpartyProtocol, firstPayload)
|
||||
val sessionInitResponse = sendAndReceiveInternal<SessionInitResponse>(session, sessionInit)
|
||||
if (sessionInitResponse is SessionConfirm) {
|
||||
@ -194,7 +203,7 @@ class ProtocolStateMachineImpl<R>(override val id: StateMachineRunId,
|
||||
return session
|
||||
} else {
|
||||
sessionInitResponse as SessionReject
|
||||
throw ProtocolSessionException("Party $otherParty rejected session attempt: ${sessionInitResponse.errorMessage}")
|
||||
throw ProtocolSessionException("Party $nodeIdentity rejected session attempt: ${sessionInitResponse.errorMessage}")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ import co.paralleluniverse.strands.Strand
|
||||
import com.codahale.metrics.Gauge
|
||||
import com.esotericsoftware.kryo.Kryo
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import kotlinx.support.jdk8.collections.removeIf
|
||||
import net.corda.core.ThreadBox
|
||||
import net.corda.core.abbreviate
|
||||
import net.corda.core.crypto.Party
|
||||
@ -28,7 +29,6 @@ import net.corda.node.services.api.ServiceHubInternal
|
||||
import net.corda.node.utilities.AddOrRemove
|
||||
import net.corda.node.utilities.AffinityExecutor
|
||||
import net.corda.node.utilities.isolatedTransaction
|
||||
import kotlinx.support.jdk8.collections.removeIf
|
||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import rx.Observable
|
||||
@ -432,7 +432,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
}
|
||||
|
||||
private fun sendSessionMessage(party: Party, message: SessionMessage, psm: ProtocolStateMachineImpl<*>?) {
|
||||
val node = serviceHub.networkMapCache.getNodeByPublicKey(party.owningKey)
|
||||
val node = serviceHub.networkMapCache.getNodeByPublicKeyTree(party.owningKey)
|
||||
?: throw IllegalArgumentException("Don't know about party $party")
|
||||
val logger = psm?.logger ?: logger
|
||||
logger.trace { "Sending $message to party $party" }
|
||||
|
@ -34,12 +34,12 @@ class InMemoryNetworkMapCacheTest {
|
||||
val nodeB = network.createNode(null, -1, MockNetwork.DefaultFactory, true, "Node B", keyPair, ServiceInfo(NetworkMapService.type))
|
||||
|
||||
// Node A currently knows only about itself, so this returns node A
|
||||
assertEquals(nodeA.netMapCache.getNodeByPublicKey(keyPair.public.tree), nodeA.info)
|
||||
assertEquals(nodeA.netMapCache.getNodeByPublicKeyTree(keyPair.public.tree), nodeA.info)
|
||||
|
||||
nodeA.netMapCache.addNode(nodeB.info)
|
||||
// Now both nodes match, so it throws an error
|
||||
expect<IllegalStateException> {
|
||||
nodeA.netMapCache.getNodeByPublicKey(keyPair.public.tree)
|
||||
nodeA.netMapCache.getNodeByPublicKeyTree(keyPair.public.tree)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user