mirror of
https://github.com/corda/corda.git
synced 2024-12-29 09:18:58 +00:00
Merge commit '6edf95506bb2bd96ebffade9b9dd82d468cd4252' into enterprise
# Conflicts: # docs/source/changelog.rst # node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt
This commit is contained in:
commit
f3c5ae2ff6
@ -29,7 +29,7 @@ import net.corda.core.utilities.getOrThrow
|
|||||||
import net.corda.nodeapi.ArtemisConsumer
|
import net.corda.nodeapi.ArtemisConsumer
|
||||||
import net.corda.nodeapi.ArtemisProducer
|
import net.corda.nodeapi.ArtemisProducer
|
||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.RPCApi
|
||||||
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration
|
import org.apache.activemq.artemis.api.core.RoutingType
|
||||||
import org.apache.activemq.artemis.api.core.SimpleString
|
import org.apache.activemq.artemis.api.core.SimpleString
|
||||||
import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE
|
import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE
|
||||||
import org.apache.activemq.artemis.api.core.client.ClientMessage
|
import org.apache.activemq.artemis.api.core.client.ClientMessage
|
||||||
@ -194,7 +194,7 @@ class RPCClientProxyHandler(
|
|||||||
TimeUnit.MILLISECONDS
|
TimeUnit.MILLISECONDS
|
||||||
)
|
)
|
||||||
sessionAndProducerPool.run {
|
sessionAndProducerPool.run {
|
||||||
it.session.createTemporaryQueue(clientAddress, ActiveMQDefaultConfiguration.getDefaultRoutingType(), clientAddress)
|
it.session.createTemporaryQueue(clientAddress, RoutingType.ANYCAST, clientAddress)
|
||||||
}
|
}
|
||||||
val sessionFactory = serverLocator.createSessionFactory()
|
val sessionFactory = serverLocator.createSessionFactory()
|
||||||
val session = sessionFactory.createSession(rpcUsername, rpcPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
|
val session = sessionFactory.createSession(rpcUsername, rpcPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
|
||||||
|
@ -158,6 +158,13 @@ R3 Corda 3.0 Developer Preview
|
|||||||
* Peer-to-peer communications is now via AMQP 1.0 as default.
|
* Peer-to-peer communications is now via AMQP 1.0 as default.
|
||||||
Although the legacy Artemis CORE bridging can still be used by setting the ``useAMQPBridges`` configuration property to false.
|
Although the legacy Artemis CORE bridging can still be used by setting the ``useAMQPBridges`` configuration property to false.
|
||||||
|
|
||||||
|
* The Artemis topics used for peer-to-peer communication have been changed to be more consistent with future cryptographic
|
||||||
|
agility and to open up the future possibility of sharing brokers between nodes. This is a breaking wire level change
|
||||||
|
as it means that nodes after this change will not be able to communicate correctly with nodes running the previous version.
|
||||||
|
Also, any pending enqueued messages in the Artemis message store will not be delivered correctly to their original target.
|
||||||
|
However, assuming a clean reset of the artemis data and that the nodes are consistent versions,
|
||||||
|
data persisted via the AMQP serializer will be forward compatible.
|
||||||
|
|
||||||
* Enterprise Corda only: Compatibility with SQL Server 2017 and SQL Azure databases.
|
* Enterprise Corda only: Compatibility with SQL Server 2017 and SQL Azure databases.
|
||||||
|
|
||||||
* Enterprise Corda only: node configuration property ``database.schema`` and documented existing database properties.
|
* Enterprise Corda only: node configuration property ``database.schema`` and documented existing database properties.
|
||||||
|
@ -46,7 +46,7 @@ Message queues
|
|||||||
The node makes use of various queues for its operation. The more important ones are described below. Others are used
|
The node makes use of various queues for its operation. The more important ones are described below. Others are used
|
||||||
for maintenance and other minor purposes.
|
for maintenance and other minor purposes.
|
||||||
|
|
||||||
:``p2p.inbound``:
|
:``p2p.inbound.$identity``:
|
||||||
The node listens for messages sent from other peer nodes on this queue. Only clients who are authenticated to be
|
The node listens for messages sent from other peer nodes on this queue. Only clients who are authenticated to be
|
||||||
nodes on the same network are given permission to send. Messages which are routed internally are also sent to this
|
nodes on the same network are given permission to send. Messages which are routed internally are also sent to this
|
||||||
queue (e.g. two flows on the same node communicating with each other).
|
queue (e.g. two flows on the same node communicating with each other).
|
||||||
@ -54,7 +54,7 @@ for maintenance and other minor purposes.
|
|||||||
:``internal.peers.$identity``:
|
:``internal.peers.$identity``:
|
||||||
These are a set of private queues only available to the node which it uses to route messages destined to other peers.
|
These are a set of private queues only available to the node which it uses to route messages destined to other peers.
|
||||||
The queue name ends in the base 58 encoding of the peer's identity key. There is at most one queue per peer. The broker
|
The queue name ends in the base 58 encoding of the peer's identity key. There is at most one queue per peer. The broker
|
||||||
creates a bridge from this queue to the peer's ``p2p.inbound`` queue, using the network map service to lookup the
|
creates a bridge from this queue to the peer's ``p2p.inbound.$identity`` queue, using the network map service to lookup the
|
||||||
peer's network address.
|
peer's network address.
|
||||||
|
|
||||||
:``internal.services.$identity``:
|
:``internal.services.$identity``:
|
||||||
@ -86,7 +86,7 @@ Clients attempting to connect to the node's broker fall in one of four groups:
|
|||||||
#. Anyone connecting with the username ``SystemUsers/Peer`` is treated as a peer on the same Corda network as the node. Their
|
#. Anyone connecting with the username ``SystemUsers/Peer`` is treated as a peer on the same Corda network as the node. Their
|
||||||
TLS root CA must be the same as the node's root CA - the root CA is the doorman of the network and having the same root CA
|
TLS root CA must be the same as the node's root CA - the root CA is the doorman of the network and having the same root CA
|
||||||
implies we've been let in by the same doorman. If they are part of the same network then they are only given permission
|
implies we've been let in by the same doorman. If they are part of the same network then they are only given permission
|
||||||
to send to our ``p2p.inbound`` queue, otherwise they are rejected.
|
to send to our ``p2p.inbound.$identity`` queue, otherwise they are rejected.
|
||||||
|
|
||||||
#. Every other username is treated as a RPC user and authenticated against the node's list of valid RPC users. If that
|
#. Every other username is treated as a RPC user and authenticated against the node's list of valid RPC users. If that
|
||||||
is successful then they are only given sufficient permission to perform RPC, otherwise they are rejected.
|
is successful then they are only given sufficient permission to perform RPC, otherwise they are rejected.
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
.. highlight:: kotlin
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<script type="text/javascript" src="_static/jquery.js"></script>
|
||||||
|
<script type="text/javascript" src="_static/codesets.js"></script>
|
||||||
|
|
||||||
Upgrading a CorDapp (outside of platform version upgrades)
|
Upgrading a CorDapp (outside of platform version upgrades)
|
||||||
==========================================================
|
==========================================================
|
||||||
|
|
||||||
@ -127,18 +133,40 @@ The ``InitiatingFlow`` version number is included in the flow session handshake
|
|||||||
the flow running on the other side. In particular, it has a ``flowVersion`` property which can be used to
|
the flow running on the other side. In particular, it has a ``flowVersion`` property which can be used to
|
||||||
programmatically evolve flows across versions. For example:
|
programmatically evolve flows across versions. For example:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
val otherFlowVersion = otherSession.getCounterpartyFlowInfo().flowVersion
|
val otherFlowVersion = otherSession.getCounterpartyFlowInfo().flowVersion
|
||||||
val receivedString = if (otherFlowVersion == 1) {
|
val receivedString = if (otherFlowVersion == 1) {
|
||||||
receive<Int>(otherParty).unwrap { it.toString() }
|
otherSession.receive<Int>().unwrap { it.toString() }
|
||||||
} else {
|
} else {
|
||||||
receive<String>(otherParty).unwrap { it }
|
otherSession.receive<String>().unwrap { it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
@Override public Void call() throws FlowException {
|
||||||
|
int otherFlowVersion = otherSession.getCounterpartyFlowInfo().getFlowVersion();
|
||||||
|
String receivedString;
|
||||||
|
|
||||||
|
if (otherFlowVersion == 1) {
|
||||||
|
receivedString = otherSession.receive(Integer.class).unwrap(integer -> {
|
||||||
|
return integer.toString();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
receivedString = otherSession.receive(String.class).unwrap(string -> {
|
||||||
|
return string;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
This code shows a flow that in its first version expected to receive an Int, but in subsequent versions was modified to
|
This code shows a flow that in its first version expected to receive an Int, but in subsequent versions was modified to
|
||||||
expect a String. This flow is still able to communicate with parties that are running the older CorDapp containing
|
expect a String. This flow is still able to communicate with parties that are running the older CorDapp containing
|
||||||
the older flow.
|
the older flow.
|
||||||
@ -147,6 +175,8 @@ How do I deal with interface changes to inlined subflows?
|
|||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
Here is an example of an in-lined subflow:
|
Here is an example of an in-lined subflow:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
@ -172,6 +202,47 @@ Here is an example of an in-lined subflow:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
@StartableByRPC
|
||||||
|
@InitiatingFlow
|
||||||
|
class FlowA extends FlowLogic<Void> {
|
||||||
|
private final Party recipient;
|
||||||
|
|
||||||
|
public FlowA(Party recipient) {
|
||||||
|
this.recipient = recipient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
@Override public Void call() throws FlowException {
|
||||||
|
subFlow(new FlowB(recipient));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@InitiatedBy(FlowA.class)
|
||||||
|
class FlowC extends FlowLogic<Void> {
|
||||||
|
// Omitted.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: No annotations. This is used as an inlined subflow.
|
||||||
|
class FlowB extends FlowLogic<Void> {
|
||||||
|
private final Party recipient;
|
||||||
|
|
||||||
|
public FlowB(Party recipient) {
|
||||||
|
this.recipient = recipient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
@Override public Void call() {
|
||||||
|
String message = "I'm an inlined subflow, so I inherit the @InitiatingFlow's session ID and type.";
|
||||||
|
initiateFlow(recipient).send(message);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Inlined subflows are treated as being the flow that invoked them when initiating a new flow session with a counterparty.
|
Inlined subflows are treated as being the flow that invoked them when initiating a new flow session with a counterparty.
|
||||||
Suppose flow ``A`` calls inlined subflow B, which, in turn, initiates a session with a counterparty. The ``FlowLogic``
|
Suppose flow ``A`` calls inlined subflow B, which, in turn, initiates a session with a counterparty. The ``FlowLogic``
|
||||||
type used by the counterparty to determine which counter-flow to invoke is determined by ``A``, and not by ``B``. This
|
type used by the counterparty to determine which counter-flow to invoke is determined by ``A``, and not by ``B``. This
|
||||||
@ -361,6 +432,8 @@ for details.
|
|||||||
For backwards compatible changes such as adding columns, the procedure for upgrading a state schema is to extend the
|
For backwards compatible changes such as adding columns, the procedure for upgrading a state schema is to extend the
|
||||||
existing object relational mapper. For example, we can update:
|
existing object relational mapper. For example, we can update:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
object ObligationSchemaV1 : MappedSchema(Obligation::class.java, 1, listOf(ObligationEntity::class.java)) {
|
object ObligationSchemaV1 : MappedSchema(Obligation::class.java, 1, listOf(ObligationEntity::class.java)) {
|
||||||
@ -374,8 +447,57 @@ existing object relational mapper. For example, we can update:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
public class ObligationSchemaV1 extends MappedSchema {
|
||||||
|
public IOUSchemaV1() {
|
||||||
|
super(Obligation.class, 1, ImmutableList.of(ObligationEntity.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "obligations")
|
||||||
|
public static class ObligationEntity extends PersistentState {
|
||||||
|
@Column(name = "currency") private final String currency;
|
||||||
|
@Column(name = "amount") private final Long amount;
|
||||||
|
@Column(name = "lender") @Lob private final Byte[] lender;
|
||||||
|
@Column(name = "borrower") @Lob private final Byte[] borrower;
|
||||||
|
@Column(name = "linear_id") private final UUID linearId;
|
||||||
|
|
||||||
|
|
||||||
|
public ObligationEntity(String currency, Long amount, Byte[] lender, Byte[] borrower, UUID linearId) {
|
||||||
|
this.currency = currency;
|
||||||
|
this.amount = amount;
|
||||||
|
this.lender = lender;
|
||||||
|
this.borrower = borrower;
|
||||||
|
this.linearId = linearId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCurrency() {
|
||||||
|
return currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getAmount() {
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteArray getLender() {
|
||||||
|
return lender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteArray getBorrower() {
|
||||||
|
return borrower;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getId() {
|
||||||
|
return linearId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
To:
|
To:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
object ObligationSchemaV1 : MappedSchema(Obligation::class.java, 1, listOf(ObligationEntity::class.java)) {
|
object ObligationSchemaV1 : MappedSchema(Obligation::class.java, 1, listOf(ObligationEntity::class.java)) {
|
||||||
@ -386,7 +508,60 @@ To:
|
|||||||
@Column @Lob var lender: ByteArray = obligation.lender.owningKey.encoded
|
@Column @Lob var lender: ByteArray = obligation.lender.owningKey.encoded
|
||||||
@Column @Lob var borrower: ByteArray = obligation.borrower.owningKey.encoded
|
@Column @Lob var borrower: ByteArray = obligation.borrower.owningKey.encoded
|
||||||
@Column var linear_id: String = obligation.linearId.id.toString()
|
@Column var linear_id: String = obligation.linearId.id.toString()
|
||||||
@Column var defaulted: Bool = obligation.amount.inDefault // NEW COLUNM!
|
@Column var defaulted: Bool = obligation.amount.inDefault // NEW COLUMN!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
public class ObligationSchemaV1 extends MappedSchema {
|
||||||
|
public IOUSchemaV1() {
|
||||||
|
super(Obligation.class, 1, ImmutableList.of(ObligationEntity.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "obligations")
|
||||||
|
public static class ObligationEntity extends PersistentState {
|
||||||
|
@Column(name = "currency") private final String currency;
|
||||||
|
@Column(name = "amount") private final Long amount;
|
||||||
|
@Column(name = "lender") @Lob private final Byte[] lender;
|
||||||
|
@Column(name = "borrower") @Lob private final Byte[] borrower;
|
||||||
|
@Column(name = "linear_id") private final UUID linearId;
|
||||||
|
@Column(name = "defaulted") private final Boolean defaulted; // NEW COLUMN!
|
||||||
|
|
||||||
|
|
||||||
|
public ObligationEntity(String currency, Long amount, Byte[] lender, Byte[] borrower, UUID linearId, Boolean defaulted) {
|
||||||
|
this.currency = currency;
|
||||||
|
this.amount = amount;
|
||||||
|
this.lender = lender;
|
||||||
|
this.borrower = borrower;
|
||||||
|
this.linearId = linearId;
|
||||||
|
this.defaulted = defaulted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCurrency() {
|
||||||
|
return currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getAmount() {
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteArray getLender() {
|
||||||
|
return lender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteArray getBorrower() {
|
||||||
|
return borrower;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getId() {
|
||||||
|
return linearId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean isDefaulted() {
|
||||||
|
return defaulted;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -397,12 +572,22 @@ used, as changes to the state are required. To make a backwards-incompatible cha
|
|||||||
because a property was removed from a state object), the procedure is to define another object relational mapper, then
|
because a property was removed from a state object), the procedure is to define another object relational mapper, then
|
||||||
add it to the ``supportedSchemas`` property of your ``QueryableState``, like so:
|
add it to the ``supportedSchemas`` property of your ``QueryableState``, like so:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
override fun supportedSchemas(): Iterable<MappedSchema> = listOf(ExampleSchemaV1, ExampleSchemaV2)
|
override fun supportedSchemas(): Iterable<MappedSchema> = listOf(ExampleSchemaV1, ExampleSchemaV2)
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
@Override public Iterable<MappedSchema> supportedSchemas() {
|
||||||
|
return ImmutableList.of(new ExampleSchemaV1(), new ExampleSchemaV2());
|
||||||
|
}
|
||||||
|
|
||||||
Then, in ``generateMappedObject``, add support for the new schema:
|
Then, in ``generateMappedObject``, add support for the new schema:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
override fun generateMappedObject(schema: MappedSchema): PersistentState {
|
override fun generateMappedObject(schema: MappedSchema): PersistentState {
|
||||||
@ -413,5 +598,17 @@ Then, in ``generateMappedObject``, add support for the new schema:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
@Override public PersistentState generateMappedObject(MappedSchema schema) {
|
||||||
|
if (schema instanceof DummyLinearStateSchemaV1) {
|
||||||
|
// Omitted.
|
||||||
|
} else if (schema instanceof DummyLinearStateSchemaV2) {
|
||||||
|
// Omitted.
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unrecognised schema $schema");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
With this approach, whenever the state object is stored in the vault, a representation of it will be stored in two
|
With this approach, whenever the state object is stored in the vault, a representation of it will be stored in two
|
||||||
separate database tables where possible - one for each supported schema.
|
separate database tables where possible - one for each supported schema.
|
@ -1,11 +1,12 @@
|
|||||||
package net.corda.nodeapi.internal
|
package net.corda.nodeapi.internal
|
||||||
|
|
||||||
|
import net.corda.core.crypto.toStringShort
|
||||||
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.messaging.MessageRecipientGroup
|
import net.corda.core.messaging.MessageRecipientGroup
|
||||||
import net.corda.core.messaging.MessageRecipients
|
import net.corda.core.messaging.MessageRecipients
|
||||||
import net.corda.core.messaging.SingleMessageRecipient
|
import net.corda.core.messaging.SingleMessageRecipient
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.toBase58String
|
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,7 +24,7 @@ class ArtemisMessagingComponent {
|
|||||||
const val PEER_USER = "SystemUsers/Peer"
|
const val PEER_USER = "SystemUsers/Peer"
|
||||||
const val INTERNAL_PREFIX = "internal."
|
const val INTERNAL_PREFIX = "internal."
|
||||||
const val PEERS_PREFIX = "${INTERNAL_PREFIX}peers." //TODO Come up with better name for common peers/services queue
|
const val PEERS_PREFIX = "${INTERNAL_PREFIX}peers." //TODO Come up with better name for common peers/services queue
|
||||||
const val P2P_QUEUE = "p2p.inbound"
|
const val P2P_PREFIX = "p2p.inbound."
|
||||||
const val NOTIFICATIONS_ADDRESS = "${INTERNAL_PREFIX}activemq.notifications"
|
const val NOTIFICATIONS_ADDRESS = "${INTERNAL_PREFIX}activemq.notifications"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ class ArtemisMessagingComponent {
|
|||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class NodeAddress(override val queueName: String, override val hostAndPort: NetworkHostAndPort) : ArtemisPeerAddress {
|
data class NodeAddress(override val queueName: String, override val hostAndPort: NetworkHostAndPort) : ArtemisPeerAddress {
|
||||||
constructor(peerIdentity: PublicKey, hostAndPort: NetworkHostAndPort) :
|
constructor(peerIdentity: PublicKey, hostAndPort: NetworkHostAndPort) :
|
||||||
this("$PEERS_PREFIX${peerIdentity.toBase58String()}", hostAndPort)
|
this("$PEERS_PREFIX${peerIdentity.toStringShort()}", hostAndPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,6 +63,30 @@ class ArtemisMessagingComponent {
|
|||||||
* @param identity The service identity's owning key.
|
* @param identity The service identity's owning key.
|
||||||
*/
|
*/
|
||||||
data class ServiceAddress(val identity: PublicKey) : ArtemisAddress, MessageRecipientGroup {
|
data class ServiceAddress(val identity: PublicKey) : ArtemisAddress, MessageRecipientGroup {
|
||||||
override val queueName: String = "$PEERS_PREFIX${identity.toBase58String()}"
|
override val queueName: String = "$PEERS_PREFIX${identity.toStringShort()}"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [RemoteInboxAddress] implements [SingleMessageRecipient]. It represents the non-local address of a remote inbox.
|
||||||
|
* @param identity The Node public identity
|
||||||
|
*/
|
||||||
|
data class RemoteInboxAddress(val identity: PublicKey) : ArtemisAddress, SingleMessageRecipient {
|
||||||
|
constructor(party: Party) : this(party.owningKey)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* When transferring a message from the local holding queue to the remote inbox queue
|
||||||
|
* this method provides a simple translation of the address string.
|
||||||
|
* The topics are distinct so that proper segregation of internal
|
||||||
|
* and external access permissions can be made.
|
||||||
|
*/
|
||||||
|
fun translateLocalQueueToInboxAddress(address: String): String {
|
||||||
|
require(address.startsWith(PEERS_PREFIX)) { "Failed to map address: $address to a remote topic as it is not in the $PEERS_PREFIX namespace" }
|
||||||
|
return P2P_PREFIX + address.substring(PEERS_PREFIX.length)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val queueName: String = "$P2P_PREFIX${identity.toStringShort()}"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -3,18 +3,18 @@ package net.corda.node.amqp
|
|||||||
import com.nhaarman.mockito_kotlin.any
|
import com.nhaarman.mockito_kotlin.any
|
||||||
import com.nhaarman.mockito_kotlin.doReturn
|
import com.nhaarman.mockito_kotlin.doReturn
|
||||||
import com.nhaarman.mockito_kotlin.whenever
|
import com.nhaarman.mockito_kotlin.whenever
|
||||||
|
import net.corda.core.crypto.toStringShort
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.services.NetworkMapCache
|
import net.corda.core.node.services.NetworkMapCache
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.toBase58String
|
|
||||||
import net.corda.node.internal.protonwrapper.netty.AMQPServer
|
import net.corda.node.internal.protonwrapper.netty.AMQPServer
|
||||||
import net.corda.node.internal.security.RPCSecurityManager
|
import net.corda.node.internal.security.RPCSecurityManager
|
||||||
|
import net.corda.node.services.api.NetworkMapCacheInternal
|
||||||
import net.corda.node.services.config.*
|
import net.corda.node.services.config.*
|
||||||
import net.corda.node.services.messaging.ArtemisMessagingClient
|
import net.corda.node.services.messaging.ArtemisMessagingClient
|
||||||
import net.corda.node.services.messaging.ArtemisMessagingServer
|
import net.corda.node.services.messaging.ArtemisMessagingServer
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE
|
|
||||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
import net.corda.testing.internal.rigorousMock
|
import net.corda.testing.internal.rigorousMock
|
||||||
@ -53,7 +53,7 @@ class AMQPBridgeTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `test acked and nacked messages`() {
|
fun `test acked and nacked messages`() {
|
||||||
// Create local queue
|
// Create local queue
|
||||||
val sourceQueueName = "internal.peers." + BOB.publicKey.toBase58String()
|
val sourceQueueName = "internal.peers." + BOB.publicKey.toStringShort()
|
||||||
val (artemisServer, artemisClient) = createArtemis(sourceQueueName)
|
val (artemisServer, artemisClient) = createArtemis(sourceQueueName)
|
||||||
|
|
||||||
// Pre-populate local queue with 3 messages
|
// Pre-populate local queue with 3 messages
|
||||||
@ -133,11 +133,13 @@ class AMQPBridgeTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `Test legacy bridge still works`() {
|
fun `Test legacy bridge still works`() {
|
||||||
// Create local queue
|
// Create local queue
|
||||||
val sourceQueueName = "internal.peers." + ALICE.publicKey.toBase58String()
|
val sourceQueueName = "internal.peers." + BOB.publicKey.toStringShort()
|
||||||
val (artemisLegacyServer, artemisLegacyClient) = createLegacyArtemis(sourceQueueName)
|
val (artemisLegacyServer, artemisLegacyClient) = createLegacyArtemis(sourceQueueName)
|
||||||
|
|
||||||
|
|
||||||
val (artemisServer, artemisClient) = createArtemis(null)
|
val (artemisServer, artemisClient) = createArtemis(null)
|
||||||
|
val inbox = ArtemisMessagingComponent.RemoteInboxAddress(BOB.party).queueName
|
||||||
|
artemisClient.started!!.session.createQueue(inbox, RoutingType.ANYCAST, inbox, true)
|
||||||
|
|
||||||
val artemis = artemisLegacyClient.started!!
|
val artemis = artemisLegacyClient.started!!
|
||||||
for (i in 0 until 3) {
|
for (i in 0 until 3) {
|
||||||
@ -150,8 +152,7 @@ class AMQPBridgeTest {
|
|||||||
artemis.producer.send(sourceQueueName, artemisMessage)
|
artemis.producer.send(sourceQueueName, artemisMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val subs = artemisClient.started!!.session.createConsumer(inbox)
|
||||||
val subs = artemisClient.started!!.session.createConsumer(P2P_QUEUE)
|
|
||||||
for (i in 0 until 3) {
|
for (i in 0 until 3) {
|
||||||
val msg = subs.receive()
|
val msg = subs.receive()
|
||||||
val messageBody = ByteArray(msg.bodySize).apply { msg.bodyBuffer.readBytes(this) }
|
val messageBody = ByteArray(msg.bodySize).apply { msg.bodyBuffer.readBytes(this) }
|
||||||
@ -177,9 +178,9 @@ class AMQPBridgeTest {
|
|||||||
doReturn(true).whenever(it).useAMQPBridges
|
doReturn(true).whenever(it).useAMQPBridges
|
||||||
}
|
}
|
||||||
artemisConfig.configureWithDevSSLCertificate()
|
artemisConfig.configureWithDevSSLCertificate()
|
||||||
val networkMap = rigorousMock<NetworkMapCache>().also {
|
val networkMap = rigorousMock<NetworkMapCacheInternal>().also {
|
||||||
doReturn(Observable.never<NetworkMapCache.MapChange>()).whenever(it).changed
|
doReturn(Observable.never<NetworkMapCache.MapChange>()).whenever(it).changed
|
||||||
doReturn(listOf(NodeInfo(listOf(amqpAddress), listOf(BOB.identity), 1, 1L))).whenever(it).getNodesByLegalIdentityKey(any())
|
doReturn(listOf(NodeInfo(listOf(amqpAddress), listOf(BOB.identity), 1, 1L))).whenever(it).getNodesByOwningKeyIndex(any())
|
||||||
}
|
}
|
||||||
val userService = rigorousMock<RPCSecurityManager>()
|
val userService = rigorousMock<RPCSecurityManager>()
|
||||||
val artemisServer = ArtemisMessagingServer(artemisConfig, artemisPort, null, networkMap, userService, MAX_MESSAGE_SIZE)
|
val artemisServer = ArtemisMessagingServer(artemisConfig, artemisPort, null, networkMap, userService, MAX_MESSAGE_SIZE)
|
||||||
@ -189,7 +190,7 @@ class AMQPBridgeTest {
|
|||||||
val artemis = artemisClient.started!!
|
val artemis = artemisClient.started!!
|
||||||
if (sourceQueueName != null) {
|
if (sourceQueueName != null) {
|
||||||
// Local queue for outgoing messages
|
// Local queue for outgoing messages
|
||||||
artemis.session.createQueue(sourceQueueName, RoutingType.MULTICAST, sourceQueueName, true)
|
artemis.session.createQueue(sourceQueueName, RoutingType.ANYCAST, sourceQueueName, true)
|
||||||
}
|
}
|
||||||
return Pair(artemisServer, artemisClient)
|
return Pair(artemisServer, artemisClient)
|
||||||
}
|
}
|
||||||
@ -206,9 +207,9 @@ class AMQPBridgeTest {
|
|||||||
doReturn(ActiveMqServerConfiguration(BridgeConfiguration(0, 0, 0.0))).whenever(it).activeMQServer
|
doReturn(ActiveMqServerConfiguration(BridgeConfiguration(0, 0, 0.0))).whenever(it).activeMQServer
|
||||||
}
|
}
|
||||||
artemisConfig.configureWithDevSSLCertificate()
|
artemisConfig.configureWithDevSSLCertificate()
|
||||||
val networkMap = rigorousMock<NetworkMapCache>().also {
|
val networkMap = rigorousMock<NetworkMapCacheInternal>().also {
|
||||||
doReturn(Observable.never<NetworkMapCache.MapChange>()).whenever(it).changed
|
doReturn(Observable.never<NetworkMapCache.MapChange>()).whenever(it).changed
|
||||||
doReturn(listOf(NodeInfo(listOf(artemisAddress), listOf(ALICE.identity), 1, 1L))).whenever(it).getNodesByLegalIdentityKey(any())
|
doReturn(listOf(NodeInfo(listOf(artemisAddress), listOf(ALICE.identity), 1, 1L))).whenever(it).getNodesByOwningKeyIndex(any())
|
||||||
}
|
}
|
||||||
val userService = rigorousMock<RPCSecurityManager>()
|
val userService = rigorousMock<RPCSecurityManager>()
|
||||||
val artemisServer = ArtemisMessagingServer(artemisConfig, artemisPort2, null, networkMap, userService, MAX_MESSAGE_SIZE)
|
val artemisServer = ArtemisMessagingServer(artemisConfig, artemisPort2, null, networkMap, userService, MAX_MESSAGE_SIZE)
|
||||||
@ -217,7 +218,7 @@ class AMQPBridgeTest {
|
|||||||
artemisClient.start()
|
artemisClient.start()
|
||||||
val artemis = artemisClient.started!!
|
val artemis = artemisClient.started!!
|
||||||
// Local queue for outgoing messages
|
// Local queue for outgoing messages
|
||||||
artemis.session.createQueue(sourceQueueName, RoutingType.MULTICAST, sourceQueueName, true)
|
artemis.session.createQueue(sourceQueueName, RoutingType.ANYCAST, sourceQueueName, true)
|
||||||
return Pair(artemisServer, artemisClient)
|
return Pair(artemisServer, artemisClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,11 +13,13 @@ import net.corda.node.internal.protonwrapper.messages.MessageStatus
|
|||||||
import net.corda.node.internal.protonwrapper.netty.AMQPClient
|
import net.corda.node.internal.protonwrapper.netty.AMQPClient
|
||||||
import net.corda.node.internal.protonwrapper.netty.AMQPServer
|
import net.corda.node.internal.protonwrapper.netty.AMQPServer
|
||||||
import net.corda.node.internal.security.RPCSecurityManager
|
import net.corda.node.internal.security.RPCSecurityManager
|
||||||
|
import net.corda.node.services.api.NetworkMapCacheInternal
|
||||||
import net.corda.node.services.config.CertChainPolicyConfig
|
import net.corda.node.services.config.CertChainPolicyConfig
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
import net.corda.node.services.config.configureWithDevSSLCertificate
|
import net.corda.node.services.config.configureWithDevSSLCertificate
|
||||||
import net.corda.node.services.messaging.ArtemisMessagingClient
|
import net.corda.node.services.messaging.ArtemisMessagingClient
|
||||||
import net.corda.node.services.messaging.ArtemisMessagingServer
|
import net.corda.node.services.messaging.ArtemisMessagingServer
|
||||||
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
||||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
@ -48,7 +50,7 @@ class ProtonWrapperTests {
|
|||||||
amqpServer.start()
|
amqpServer.start()
|
||||||
val receiveSubs = amqpServer.onReceive.subscribe {
|
val receiveSubs = amqpServer.onReceive.subscribe {
|
||||||
assertEquals(BOB_NAME.toString(), it.sourceLegalName)
|
assertEquals(BOB_NAME.toString(), it.sourceLegalName)
|
||||||
assertEquals("p2p.inbound", it.topic)
|
assertEquals(P2P_PREFIX + "Test", it.topic)
|
||||||
assertEquals("Test", String(it.payload))
|
assertEquals("Test", String(it.payload))
|
||||||
it.complete(true)
|
it.complete(true)
|
||||||
}
|
}
|
||||||
@ -64,7 +66,7 @@ class ProtonWrapperTests {
|
|||||||
assertEquals(true, clientConnect.connected)
|
assertEquals(true, clientConnect.connected)
|
||||||
assertEquals(ALICE_NAME, CordaX500Name.build(clientConnect.remoteCert!!.subjectX500Principal))
|
assertEquals(ALICE_NAME, CordaX500Name.build(clientConnect.remoteCert!!.subjectX500Principal))
|
||||||
val msg = amqpClient.createMessage("Test".toByteArray(),
|
val msg = amqpClient.createMessage("Test".toByteArray(),
|
||||||
"p2p.inbound",
|
P2P_PREFIX + "Test",
|
||||||
ALICE_NAME.toString(),
|
ALICE_NAME.toString(),
|
||||||
emptyMap())
|
emptyMap())
|
||||||
amqpClient.write(msg)
|
amqpClient.write(msg)
|
||||||
@ -151,8 +153,8 @@ class ProtonWrapperTests {
|
|||||||
assertEquals(true, clientConnected.get().connected)
|
assertEquals(true, clientConnected.get().connected)
|
||||||
assertEquals(CHARLIE_NAME, CordaX500Name.build(clientConnected.get().remoteCert!!.subjectX500Principal))
|
assertEquals(CHARLIE_NAME, CordaX500Name.build(clientConnected.get().remoteCert!!.subjectX500Principal))
|
||||||
val artemis = artemisClient.started!!
|
val artemis = artemisClient.started!!
|
||||||
val sendAddress = "p2p.inbound"
|
val sendAddress = P2P_PREFIX + "Test"
|
||||||
artemis.session.createQueue(sendAddress, RoutingType.MULTICAST, "queue", true)
|
artemis.session.createQueue(sendAddress, RoutingType.ANYCAST, "queue", true)
|
||||||
val consumer = artemis.session.createConsumer("queue")
|
val consumer = artemis.session.createConsumer("queue")
|
||||||
val testData = "Test".toByteArray()
|
val testData = "Test".toByteArray()
|
||||||
val testProperty = mutableMapOf<Any?, Any?>()
|
val testProperty = mutableMapOf<Any?, Any?>()
|
||||||
@ -230,7 +232,7 @@ class ProtonWrapperTests {
|
|||||||
}
|
}
|
||||||
artemisConfig.configureWithDevSSLCertificate()
|
artemisConfig.configureWithDevSSLCertificate()
|
||||||
|
|
||||||
val networkMap = rigorousMock<NetworkMapCache>().also {
|
val networkMap = rigorousMock<NetworkMapCacheInternal>().also {
|
||||||
doReturn(never<NetworkMapCache.MapChange>()).whenever(it).changed
|
doReturn(never<NetworkMapCache.MapChange>()).whenever(it).changed
|
||||||
}
|
}
|
||||||
val userService = rigorousMock<RPCSecurityManager>()
|
val userService = rigorousMock<RPCSecurityManager>()
|
||||||
|
@ -5,6 +5,7 @@ import net.corda.client.rpc.CordaRPCClient
|
|||||||
import net.corda.client.rpc.CordaRPCConnection
|
import net.corda.client.rpc.CordaRPCConnection
|
||||||
import net.corda.core.crypto.generateKeyPair
|
import net.corda.core.crypto.generateKeyPair
|
||||||
import net.corda.core.crypto.random63BitValue
|
import net.corda.core.crypto.random63BitValue
|
||||||
|
import net.corda.core.crypto.toStringShort
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.FlowSession
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.flows.InitiatedBy
|
import net.corda.core.flows.InitiatedBy
|
||||||
@ -14,14 +15,13 @@ import net.corda.core.identity.Party
|
|||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.core.utilities.toBase58String
|
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
import net.corda.node.internal.Node
|
import net.corda.node.internal.Node
|
||||||
import net.corda.node.internal.StartedNode
|
import net.corda.node.internal.StartedNode
|
||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.RPCApi
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
import net.corda.testing.ALICE_NAME
|
import net.corda.testing.ALICE_NAME
|
||||||
@ -79,30 +79,30 @@ abstract class MQSecurityTest : NodeBasedTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `consume message from P2P queue`() {
|
fun `consume message from P2P queue`() {
|
||||||
assertConsumeAttackFails(P2P_QUEUE)
|
assertConsumeAttackFails("$P2P_PREFIX${alice.info.chooseIdentity().owningKey.toStringShort()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `consume message from peer queue`() {
|
fun `consume message from peer queue`() {
|
||||||
val bobParty = startBobAndCommunicateWithAlice()
|
val bobParty = startBobAndCommunicateWithAlice()
|
||||||
assertConsumeAttackFails("$PEERS_PREFIX${bobParty.owningKey.toBase58String()}")
|
assertConsumeAttackFails("$PEERS_PREFIX${bobParty.owningKey.toStringShort()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `send message to address of peer which has been communicated with`() {
|
fun `send message to address of peer which has been communicated with`() {
|
||||||
val bobParty = startBobAndCommunicateWithAlice()
|
val bobParty = startBobAndCommunicateWithAlice()
|
||||||
assertSendAttackFails("$PEERS_PREFIX${bobParty.owningKey.toBase58String()}")
|
assertSendAttackFails("$PEERS_PREFIX${bobParty.owningKey.toStringShort()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `create queue for peer which has not been communicated with`() {
|
fun `create queue for peer which has not been communicated with`() {
|
||||||
val bob = startNode(BOB_NAME)
|
val bob = startNode(BOB_NAME)
|
||||||
assertAllQueueCreationAttacksFail("$PEERS_PREFIX${bob.info.chooseIdentity().owningKey.toBase58String()}")
|
assertAllQueueCreationAttacksFail("$PEERS_PREFIX${bob.info.chooseIdentity().owningKey.toStringShort()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `create queue for unknown peer`() {
|
fun `create queue for unknown peer`() {
|
||||||
val invalidPeerQueue = "$PEERS_PREFIX${generateKeyPair().public.toBase58String()}"
|
val invalidPeerQueue = "$PEERS_PREFIX${generateKeyPair().public.toStringShort()}"
|
||||||
assertAllQueueCreationAttacksFail(invalidPeerQueue)
|
assertAllQueueCreationAttacksFail(invalidPeerQueue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ import org.slf4j.Logger
|
|||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import rx.Scheduler
|
import rx.Scheduler
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
|
import java.security.PublicKey
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import javax.management.ObjectName
|
import javax.management.ObjectName
|
||||||
@ -166,11 +167,14 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
VerifierType.OutOfProcess -> VerifierMessagingClient(configuration, serverAddress, services.monitoringService.metrics, networkParameters.maxMessageSize)
|
VerifierType.OutOfProcess -> VerifierMessagingClient(configuration, serverAddress, services.monitoringService.metrics, networkParameters.maxMessageSize)
|
||||||
VerifierType.InMemory -> null
|
VerifierType.InMemory -> null
|
||||||
}
|
}
|
||||||
|
require(info.legalIdentities.size in 1..2) { "Currently nodes must have a primary address and optionally one serviced address" }
|
||||||
|
val serviceIdentity: PublicKey? = if (info.legalIdentities.size == 1) null else info.legalIdentities[1].owningKey
|
||||||
return P2PMessagingClient(
|
return P2PMessagingClient(
|
||||||
configuration,
|
configuration,
|
||||||
versionInfo,
|
versionInfo,
|
||||||
serverAddress,
|
serverAddress,
|
||||||
info.legalIdentities[0].owningKey,
|
info.legalIdentities[0].owningKey,
|
||||||
|
serviceIdentity,
|
||||||
serverThread,
|
serverThread,
|
||||||
database,
|
database,
|
||||||
advertisedAddress,
|
advertisedAddress,
|
||||||
|
@ -29,6 +29,10 @@ interface NetworkMapCacheBaseInternal : NetworkMapCacheBase {
|
|||||||
|
|
||||||
fun getNodeByHash(nodeHash: SecureHash): NodeInfo?
|
fun getNodeByHash(nodeHash: SecureHash): NodeInfo?
|
||||||
|
|
||||||
|
/** Find nodes from the [PublicKey] toShortString representation.
|
||||||
|
* This is used for Artemis bridge lookup process. */
|
||||||
|
fun getNodesByOwningKeyIndex(identityKeyIndex: String): List<NodeInfo>
|
||||||
|
|
||||||
/** Adds a node to the local cache (generally only used for adding ourselves). */
|
/** Adds a node to the local cache (generally only used for adding ourselves). */
|
||||||
fun addNode(node: NodeInfo)
|
fun addNode(node: NodeInfo)
|
||||||
|
|
||||||
|
@ -12,8 +12,8 @@ import net.corda.node.services.config.NodeConfiguration
|
|||||||
import net.corda.node.services.messaging.AMQPBridgeManager.AMQPBridge.Companion.getBridgeName
|
import net.corda.node.services.messaging.AMQPBridgeManager.AMQPBridge.Companion.getBridgeName
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE
|
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
||||||
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.RemoteInboxAddress.Companion.translateLocalQueueToInboxAddress
|
||||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||||
import org.apache.activemq.artemis.api.core.SimpleString
|
import org.apache.activemq.artemis.api.core.SimpleString
|
||||||
import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE
|
import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE
|
||||||
@ -132,7 +132,8 @@ internal class AMQPBridgeManager(val config: NodeConfiguration, val p2pAddress:
|
|||||||
properties[key.toString()] = value
|
properties[key.toString()] = value
|
||||||
}
|
}
|
||||||
log.debug { "Bridged Send to ${legalNames.first()} uuid: ${artemisMessage.getObjectProperty("_AMQ_DUPL_ID")}" }
|
log.debug { "Bridged Send to ${legalNames.first()} uuid: ${artemisMessage.getObjectProperty("_AMQ_DUPL_ID")}" }
|
||||||
val sendableMessage = amqpClient.createMessage(data, P2P_QUEUE,
|
val peerInbox = translateLocalQueueToInboxAddress(queueName)
|
||||||
|
val sendableMessage = amqpClient.createMessage(data, peerInbox,
|
||||||
legalNames.first().toString(),
|
legalNames.first().toString(),
|
||||||
properties)
|
properties)
|
||||||
sendableMessage.onComplete.then {
|
sendableMessage.onComplete.then {
|
||||||
|
@ -8,16 +8,15 @@ import net.corda.core.internal.div
|
|||||||
import net.corda.core.internal.noneOrSingle
|
import net.corda.core.internal.noneOrSingle
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.services.NetworkMapCache
|
|
||||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
import net.corda.core.node.services.NetworkMapCache.MapChange
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.core.utilities.parsePublicKeyBase58
|
|
||||||
import net.corda.node.internal.Node
|
import net.corda.node.internal.Node
|
||||||
import net.corda.node.internal.security.Password
|
import net.corda.node.internal.security.Password
|
||||||
import net.corda.node.internal.security.RPCSecurityManager
|
import net.corda.node.internal.security.RPCSecurityManager
|
||||||
|
import net.corda.node.services.api.NetworkMapCacheInternal
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
import net.corda.node.services.messaging.NodeLoginModule.Companion.NODE_ROLE
|
import net.corda.node.services.messaging.NodeLoginModule.Companion.NODE_ROLE
|
||||||
import net.corda.node.services.messaging.NodeLoginModule.Companion.PEER_ROLE
|
import net.corda.node.services.messaging.NodeLoginModule.Companion.PEER_ROLE
|
||||||
@ -31,7 +30,7 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.ArtemisPeerAddress
|
|||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.NodeAddress
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.NodeAddress
|
||||||
@ -94,7 +93,7 @@ import javax.security.cert.CertificateException
|
|||||||
class ArtemisMessagingServer(private val config: NodeConfiguration,
|
class ArtemisMessagingServer(private val config: NodeConfiguration,
|
||||||
private val p2pPort: Int,
|
private val p2pPort: Int,
|
||||||
val rpcPort: Int?,
|
val rpcPort: Int?,
|
||||||
val networkMapCache: NetworkMapCache,
|
val networkMapCache: NetworkMapCacheInternal,
|
||||||
val securityManager: RPCSecurityManager,
|
val securityManager: RPCSecurityManager,
|
||||||
val maxMessageSize: Int) : SingletonSerializeAsToken() {
|
val maxMessageSize: Int) : SingletonSerializeAsToken() {
|
||||||
companion object {
|
companion object {
|
||||||
@ -191,7 +190,6 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
|||||||
// by having its password be an unknown securely random 128-bit value.
|
// by having its password be an unknown securely random 128-bit value.
|
||||||
clusterPassword = BigInteger(128, newSecureRandom()).toString(16)
|
clusterPassword = BigInteger(128, newSecureRandom()).toString(16)
|
||||||
queueConfigurations = listOf(
|
queueConfigurations = listOf(
|
||||||
queueConfig(P2P_QUEUE, durable = true),
|
|
||||||
// Create an RPC queue: this will service locally connected clients only (not via a bridge) and those
|
// Create an RPC queue: this will service locally connected clients only (not via a bridge) and those
|
||||||
// clients must have authenticated. We could use a single consumer for everything and perhaps we should,
|
// clients must have authenticated. We could use a single consumer for everything and perhaps we should,
|
||||||
// but these queues are not worth persisting.
|
// but these queues are not worth persisting.
|
||||||
@ -243,7 +241,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
|||||||
private fun ConfigurationImpl.configureAddressSecurity(): Pair<Configuration, LoginListener> {
|
private fun ConfigurationImpl.configureAddressSecurity(): Pair<Configuration, LoginListener> {
|
||||||
val nodeInternalRole = Role(NODE_ROLE, true, true, true, true, true, true, true, true)
|
val nodeInternalRole = Role(NODE_ROLE, true, true, true, true, true, true, true, true)
|
||||||
securityRoles["$INTERNAL_PREFIX#"] = setOf(nodeInternalRole) // Do not add any other roles here as it's only for the node
|
securityRoles["$INTERNAL_PREFIX#"] = setOf(nodeInternalRole) // Do not add any other roles here as it's only for the node
|
||||||
securityRoles[P2P_QUEUE] = setOf(nodeInternalRole, restrictedRole(PEER_ROLE, send = true))
|
securityRoles["$P2P_PREFIX#"] = setOf(nodeInternalRole, restrictedRole(PEER_ROLE, send = true))
|
||||||
securityRoles[RPCApi.RPC_SERVER_QUEUE_NAME] = setOf(nodeInternalRole, restrictedRole(RPC_ROLE, send = true))
|
securityRoles[RPCApi.RPC_SERVER_QUEUE_NAME] = setOf(nodeInternalRole, restrictedRole(RPC_ROLE, send = true))
|
||||||
// Each RPC user must have its own role and its own queue. This prevents users accessing each other's queues
|
// Each RPC user must have its own role and its own queue. This prevents users accessing each other's queues
|
||||||
// and stealing RPC responses.
|
// and stealing RPC responses.
|
||||||
@ -309,8 +307,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
|||||||
|
|
||||||
if (queueName.startsWith(PEERS_PREFIX)) {
|
if (queueName.startsWith(PEERS_PREFIX)) {
|
||||||
try {
|
try {
|
||||||
val identity = parsePublicKeyBase58(queueName.substring(PEERS_PREFIX.length))
|
val nodeInfos = networkMapCache.getNodesByOwningKeyIndex(queueName.substring(PEERS_PREFIX.length))
|
||||||
val nodeInfos = networkMapCache.getNodesByLegalIdentityKey(identity)
|
|
||||||
if (nodeInfos.isNotEmpty()) {
|
if (nodeInfos.isNotEmpty()) {
|
||||||
nodeInfos.forEach { deployBridgeToPeer(it) }
|
nodeInfos.forEach { deployBridgeToPeer(it) }
|
||||||
} else {
|
} else {
|
||||||
|
@ -10,15 +10,17 @@ import net.corda.node.services.config.NodeConfiguration
|
|||||||
import net.corda.nodeapi.ArtemisTcpTransport
|
import net.corda.nodeapi.ArtemisTcpTransport
|
||||||
import net.corda.nodeapi.ConnectionDirection
|
import net.corda.nodeapi.ConnectionDirection
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE
|
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
||||||
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.RemoteInboxAddress.Companion.translateLocalQueueToInboxAddress
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
|
import org.apache.activemq.artemis.api.core.Message
|
||||||
import org.apache.activemq.artemis.core.config.BridgeConfiguration
|
import org.apache.activemq.artemis.core.config.BridgeConfiguration
|
||||||
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnection
|
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnection
|
||||||
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnector
|
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnector
|
||||||
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory
|
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory
|
||||||
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants
|
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants
|
||||||
import org.apache.activemq.artemis.core.server.ActiveMQServer
|
import org.apache.activemq.artemis.core.server.ActiveMQServer
|
||||||
|
import org.apache.activemq.artemis.core.server.cluster.Transformer
|
||||||
import org.apache.activemq.artemis.spi.core.remoting.*
|
import org.apache.activemq.artemis.spi.core.remoting.*
|
||||||
import org.apache.activemq.artemis.utils.ConfigurationHelper
|
import org.apache.activemq.artemis.utils.ConfigurationHelper
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
@ -46,7 +48,7 @@ internal class CoreBridgeManager(val config: NodeConfiguration, val activeMQServ
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All nodes are expected to have a public facing address called [ArtemisMessagingComponent.P2P_QUEUE] for receiving
|
* All nodes are expected to have a public facing address called p2p.inbound.$identity for receiving
|
||||||
* messages from other nodes. When we want to send a message to a node we send it to our internal address/queue for it,
|
* messages from other nodes. When we want to send a message to a node we send it to our internal address/queue for it,
|
||||||
* as defined by ArtemisAddress.queueName. A bridge is then created to forward messages from this queue to the node's
|
* as defined by ArtemisAddress.queueName. A bridge is then created to forward messages from this queue to the node's
|
||||||
* P2P address.
|
* P2P address.
|
||||||
@ -64,7 +66,6 @@ internal class CoreBridgeManager(val config: NodeConfiguration, val activeMQServ
|
|||||||
activeMQServer.deployBridge(BridgeConfiguration().apply {
|
activeMQServer.deployBridge(BridgeConfiguration().apply {
|
||||||
name = getBridgeName(queueName, target)
|
name = getBridgeName(queueName, target)
|
||||||
this.queueName = queueName
|
this.queueName = queueName
|
||||||
forwardingAddress = P2P_QUEUE
|
|
||||||
staticConnectors = listOf(target.toString())
|
staticConnectors = listOf(target.toString())
|
||||||
confirmationWindowSize = 100000 // a guess
|
confirmationWindowSize = 100000 // a guess
|
||||||
isUseDuplicateDetection = true // Enable the bridge's automatic deduplication logic
|
isUseDuplicateDetection = true // Enable the bridge's automatic deduplication logic
|
||||||
@ -77,6 +78,7 @@ internal class CoreBridgeManager(val config: NodeConfiguration, val activeMQServ
|
|||||||
// our TLS certificate.
|
// our TLS certificate.
|
||||||
user = PEER_USER
|
user = PEER_USER
|
||||||
password = PEER_USER
|
password = PEER_USER
|
||||||
|
transformerClassName = InboxTopicTransformer::class.java.name
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,6 +101,13 @@ internal class CoreBridgeManager(val config: NodeConfiguration, val activeMQServ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class InboxTopicTransformer : Transformer {
|
||||||
|
override fun transform(message: Message): Message {
|
||||||
|
message.address = translateLocalQueueToInboxAddress(message.address)
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class VerifyingNettyConnectorFactory : NettyConnectorFactory() {
|
class VerifyingNettyConnectorFactory : NettyConnectorFactory() {
|
||||||
override fun createConnector(configuration: MutableMap<String, Any>,
|
override fun createConnector(configuration: MutableMap<String, Any>,
|
||||||
handler: BufferHandler?,
|
handler: BufferHandler?,
|
||||||
|
@ -19,7 +19,6 @@ import net.corda.node.utilities.AffinityExecutor
|
|||||||
import net.corda.node.utilities.AppendOnlyPersistentMap
|
import net.corda.node.utilities.AppendOnlyPersistentMap
|
||||||
import net.corda.node.utilities.PersistentMap
|
import net.corda.node.utilities.PersistentMap
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.*
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.*
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE
|
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||||
import org.apache.activemq.artemis.api.core.ActiveMQObjectClosedException
|
import org.apache.activemq.artemis.api.core.ActiveMQObjectClosedException
|
||||||
@ -54,21 +53,28 @@ import javax.persistence.Lob
|
|||||||
* invoke methods on the provided implementation. There is more documentation on this in the docsite and the
|
* invoke methods on the provided implementation. There is more documentation on this in the docsite and the
|
||||||
* CordaRPCClient class.
|
* CordaRPCClient class.
|
||||||
*
|
*
|
||||||
* @param serverAddress The address of the broker instance to connect to (might be running in the same process).
|
* @param config The configuration of the node, which is used for controlling the message redelivery options.
|
||||||
* @param myIdentity The public key to be used as the ArtemisMQ address and queue name for the node.
|
* @param versionInfo All messages from the node carry the version info and received messages are checked against this for compatibility.
|
||||||
* @param nodeExecutor An executor to run received message tasks upon.
|
* @param serverAddress The host and port of the Artemis broker.
|
||||||
* @param advertisedAddress The node address for inbound connections, advertised to the network map service and peers.
|
* @param myIdentity The primary identity of the node, which defines the messaging address for externally received messages.
|
||||||
* If not provided, will default to [serverAddress].
|
* It is also used to construct the myAddress field, which is ultimately advertised in the network map.
|
||||||
|
* @param serviceIdentity An optional second identity if the node is also part of a group address, for example a notary.
|
||||||
|
* @param nodeExecutor The received messages are marshalled onto the server executor to prevent Netty buffers leaking during fiber suspends.
|
||||||
|
* @param database The nodes database, which is used to deduplicate messages.
|
||||||
|
* @param advertisedAddress The externally advertised version of the Artemis broker address used to construct myAddress and included
|
||||||
|
* in the network map data.
|
||||||
|
* @param maxMessageSize A bound applied to the message size.
|
||||||
*/
|
*/
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
class P2PMessagingClient(config: NodeConfiguration,
|
class P2PMessagingClient(config: NodeConfiguration,
|
||||||
private val versionInfo: VersionInfo,
|
private val versionInfo: VersionInfo,
|
||||||
serverAddress: NetworkHostAndPort,
|
serverAddress: NetworkHostAndPort,
|
||||||
private val myIdentity: PublicKey,
|
private val myIdentity: PublicKey,
|
||||||
|
private val serviceIdentity: PublicKey?,
|
||||||
private val nodeExecutor: AffinityExecutor.ServiceAffinityExecutor,
|
private val nodeExecutor: AffinityExecutor.ServiceAffinityExecutor,
|
||||||
private val database: CordaPersistence,
|
private val database: CordaPersistence,
|
||||||
advertisedAddress: NetworkHostAndPort = serverAddress,
|
advertisedAddress: NetworkHostAndPort = serverAddress,
|
||||||
private val maxMessageSize: Int
|
maxMessageSize: Int
|
||||||
) : SingletonSerializeAsToken(), MessagingService {
|
) : SingletonSerializeAsToken(), MessagingService {
|
||||||
companion object {
|
companion object {
|
||||||
private val log = contextLogger()
|
private val log = contextLogger()
|
||||||
@ -126,6 +132,7 @@ class P2PMessagingClient(config: NodeConfiguration,
|
|||||||
private class InnerState {
|
private class InnerState {
|
||||||
var running = false
|
var running = false
|
||||||
var p2pConsumer: ClientConsumer? = null
|
var p2pConsumer: ClientConsumer? = null
|
||||||
|
var serviceConsumer: ClientConsumer? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private val messagesToRedeliver = database.transaction {
|
private val messagesToRedeliver = database.transaction {
|
||||||
@ -181,8 +188,20 @@ class P2PMessagingClient(config: NodeConfiguration,
|
|||||||
fun start() {
|
fun start() {
|
||||||
state.locked {
|
state.locked {
|
||||||
val session = artemis.start().session
|
val session = artemis.start().session
|
||||||
|
val inbox = RemoteInboxAddress(myIdentity).queueName
|
||||||
// Create a queue, consumer and producer for handling P2P network messages.
|
// Create a queue, consumer and producer for handling P2P network messages.
|
||||||
p2pConsumer = session.createConsumer(P2P_QUEUE)
|
createQueueIfAbsent(inbox)
|
||||||
|
p2pConsumer = session.createConsumer(inbox)
|
||||||
|
if (serviceIdentity != null) {
|
||||||
|
val serviceAddress = RemoteInboxAddress(serviceIdentity).queueName
|
||||||
|
createQueueIfAbsent(serviceAddress)
|
||||||
|
val serviceHandler = session.createConsumer(serviceAddress)
|
||||||
|
serviceHandler.setMessageHandler { msg ->
|
||||||
|
val message: ReceivedMessage? = artemisToCordaMessage(msg)
|
||||||
|
if (message != null)
|
||||||
|
deliver(msg, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resumeMessageRedelivery()
|
resumeMessageRedelivery()
|
||||||
@ -331,6 +350,13 @@ class P2PMessagingClient(config: NodeConfiguration,
|
|||||||
// Ignore it: this can happen if the server has gone away before we do.
|
// Ignore it: this can happen if the server has gone away before we do.
|
||||||
}
|
}
|
||||||
p2pConsumer = null
|
p2pConsumer = null
|
||||||
|
val s = serviceConsumer
|
||||||
|
try {
|
||||||
|
s?.close()
|
||||||
|
} catch (e: ActiveMQObjectClosedException) {
|
||||||
|
// Ignore it: this can happen if the server has gone away before we do.
|
||||||
|
}
|
||||||
|
serviceConsumer = null
|
||||||
prevRunning
|
prevRunning
|
||||||
}
|
}
|
||||||
if (running && !nodeExecutor.isOnThread) {
|
if (running && !nodeExecutor.isOnThread) {
|
||||||
@ -430,7 +456,7 @@ class P2PMessagingClient(config: NodeConfiguration,
|
|||||||
private fun getMQAddress(target: MessageRecipients): String {
|
private fun getMQAddress(target: MessageRecipients): String {
|
||||||
return if (target == myAddress) {
|
return if (target == myAddress) {
|
||||||
// If we are sending to ourselves then route the message directly to our P2P queue.
|
// If we are sending to ourselves then route the message directly to our P2P queue.
|
||||||
P2P_QUEUE
|
RemoteInboxAddress(myIdentity).queueName
|
||||||
} else {
|
} else {
|
||||||
// Otherwise we send the message to an internal queue for the target residing on our broker. It's then the
|
// Otherwise we send the message to an internal queue for the target residing on our broker. It's then the
|
||||||
// broker's job to route the message to the target's P2P queue.
|
// broker's job to route the message to the target's P2P queue.
|
||||||
@ -447,7 +473,7 @@ class P2PMessagingClient(config: NodeConfiguration,
|
|||||||
val queueQuery = session.queueQuery(SimpleString(queueName))
|
val queueQuery = session.queueQuery(SimpleString(queueName))
|
||||||
if (!queueQuery.isExists) {
|
if (!queueQuery.isExists) {
|
||||||
log.info("Create fresh queue $queueName bound on same address")
|
log.info("Create fresh queue $queueName bound on same address")
|
||||||
session.createQueue(queueName, RoutingType.MULTICAST, queueName, true)
|
session.createQueue(queueName, RoutingType.ANYCAST, queueName, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,15 +7,15 @@ import net.corda.core.transactions.LedgerTransaction
|
|||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
import net.corda.node.services.transactions.OutOfProcessTransactionVerifierService
|
import net.corda.node.services.transactions.OutOfProcessTransactionVerifierService
|
||||||
import net.corda.node.utilities.*
|
import net.corda.node.utilities.AffinityExecutor
|
||||||
import net.corda.nodeapi.VerifierApi
|
import net.corda.nodeapi.VerifierApi
|
||||||
import net.corda.nodeapi.VerifierApi.VERIFICATION_REQUESTS_QUEUE_NAME
|
import net.corda.nodeapi.VerifierApi.VERIFICATION_REQUESTS_QUEUE_NAME
|
||||||
import net.corda.nodeapi.VerifierApi.VERIFICATION_RESPONSES_QUEUE_NAME_PREFIX
|
import net.corda.nodeapi.VerifierApi.VERIFICATION_RESPONSES_QUEUE_NAME_PREFIX
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
import org.apache.activemq.artemis.api.core.RoutingType
|
import org.apache.activemq.artemis.api.core.RoutingType
|
||||||
import org.apache.activemq.artemis.api.core.SimpleString
|
import org.apache.activemq.artemis.api.core.SimpleString
|
||||||
import org.apache.activemq.artemis.api.core.client.*
|
import org.apache.activemq.artemis.api.core.client.ClientConsumer
|
||||||
import java.util.concurrent.*
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class VerifierMessagingClient(config: SSLConfiguration, serverAddress: NetworkHostAndPort, metrics: MetricRegistry, private val maxMessageSize: Int) : SingletonSerializeAsToken() {
|
class VerifierMessagingClient(config: SSLConfiguration, serverAddress: NetworkHostAndPort, metrics: MetricRegistry, private val maxMessageSize: Int) : SingletonSerializeAsToken() {
|
||||||
companion object {
|
companion object {
|
||||||
@ -40,7 +40,7 @@ class VerifierMessagingClient(config: SSLConfiguration, serverAddress: NetworkHo
|
|||||||
val queueQuery = session.queueQuery(SimpleString(queueName))
|
val queueQuery = session.queueQuery(SimpleString(queueName))
|
||||||
if (!queueQuery.isExists) {
|
if (!queueQuery.isExists) {
|
||||||
log.info("Create fresh queue $queueName bound on same address")
|
log.info("Create fresh queue $queueName bound on same address")
|
||||||
session.createQueue(queueName, RoutingType.MULTICAST, queueName, true)
|
session.createQueue(queueName, RoutingType.ANYCAST, queueName, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
createQueueIfAbsent(VERIFICATION_REQUESTS_QUEUE_NAME)
|
createQueueIfAbsent(VERIFICATION_REQUESTS_QUEUE_NAME)
|
||||||
|
@ -160,6 +160,12 @@ open class PersistentNetworkMapCache(
|
|||||||
|
|
||||||
private val nodesByKeyCache = NonInvalidatingCache<PublicKey, List<NodeInfo>>(1024, 8, { key -> database.transaction { queryByIdentityKey(session, key) } })
|
private val nodesByKeyCache = NonInvalidatingCache<PublicKey, List<NodeInfo>>(1024, 8, { key -> database.transaction { queryByIdentityKey(session, key) } })
|
||||||
|
|
||||||
|
override fun getNodesByOwningKeyIndex(identityKeyIndex: String): List<NodeInfo> {
|
||||||
|
return database.transaction {
|
||||||
|
queryByIdentityKeyIndex(session, identityKeyIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getNodeByAddress(address: NetworkHostAndPort): NodeInfo? = database.transaction { queryByAddress(session, address) }
|
override fun getNodeByAddress(address: NetworkHostAndPort): NodeInfo? = database.transaction { queryByAddress(session, address) }
|
||||||
|
|
||||||
override fun getPeerCertificateByLegalName(name: CordaX500Name): PartyAndCertificate? = identityByLegalNameCache.get(name).orElse(null)
|
override fun getPeerCertificateByLegalName(name: CordaX500Name): PartyAndCertificate? = identityByLegalNameCache.get(name).orElse(null)
|
||||||
@ -245,15 +251,23 @@ open class PersistentNetworkMapCache(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun findByIdentityKey(session: Session, identityKey: PublicKey): List<NodeInfoSchemaV1.PersistentNodeInfo> {
|
private fun findByIdentityKey(session: Session, identityKey: PublicKey): List<NodeInfoSchemaV1.PersistentNodeInfo> {
|
||||||
|
return findByIdentityKeyIndex(session, identityKey.toStringShort())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findByIdentityKeyIndex(session: Session, identityKeyIndex: String): List<NodeInfoSchemaV1.PersistentNodeInfo> {
|
||||||
val query = session.createQuery(
|
val query = session.createQuery(
|
||||||
"SELECT n FROM ${NodeInfoSchemaV1.PersistentNodeInfo::class.java.name} n JOIN n.legalIdentitiesAndCerts l WHERE l.owningKeyHash = :owningKeyHash",
|
"SELECT n FROM ${NodeInfoSchemaV1.PersistentNodeInfo::class.java.name} n JOIN n.legalIdentitiesAndCerts l WHERE l.owningKeyHash = :owningKeyHash",
|
||||||
NodeInfoSchemaV1.PersistentNodeInfo::class.java)
|
NodeInfoSchemaV1.PersistentNodeInfo::class.java)
|
||||||
query.setParameter("owningKeyHash", identityKey.toStringShort())
|
query.setParameter("owningKeyHash", identityKeyIndex)
|
||||||
return query.resultList
|
return query.resultList
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun queryByIdentityKey(session: Session, identityKey: PublicKey): List<NodeInfo> {
|
private fun queryByIdentityKey(session: Session, identityKey: PublicKey): List<NodeInfo> {
|
||||||
val result = findByIdentityKey(session, identityKey)
|
return queryByIdentityKeyIndex(session, identityKey.toStringShort())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun queryByIdentityKeyIndex(session: Session, identityKeyIndex: String): List<NodeInfo> {
|
||||||
|
val result = findByIdentityKeyIndex(session, identityKeyIndex)
|
||||||
return result.map { it.toNodeInfo() }
|
return result.map { it.toNodeInfo() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,6 +178,7 @@ class ArtemisMessagingTests {
|
|||||||
MOCK_VERSION_INFO.copy(platformVersion = platformVersion),
|
MOCK_VERSION_INFO.copy(platformVersion = platformVersion),
|
||||||
server,
|
server,
|
||||||
identity.public,
|
identity.public,
|
||||||
|
null,
|
||||||
ServiceAffinityExecutor("ArtemisMessagingTests", 1),
|
ServiceAffinityExecutor("ArtemisMessagingTests", 1),
|
||||||
database,
|
database,
|
||||||
maxMessageSize = maxMessageSize).apply {
|
maxMessageSize = maxMessageSize).apply {
|
||||||
|
Loading…
Reference in New Issue
Block a user