Merge pull request #1073 from corda/mkit-os-ent-merge

OS -> ENT Merge e00c7706c3f52d0e338f83d169e60c99d623f1c5
This commit is contained in:
Michele Sollecito 2018-06-26 11:49:06 +01:00 committed by GitHub
commit 2497b0b90a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 184 additions and 342 deletions

View File

@ -16,7 +16,6 @@ import javafx.beans.property.SimpleObjectProperty
import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCClientConfiguration import net.corda.client.rpc.CordaRPCClientConfiguration
import net.corda.client.rpc.CordaRPCConnection import net.corda.client.rpc.CordaRPCConnection
import net.corda.client.rpc.RPCException
import net.corda.core.contracts.ContractState import net.corda.core.contracts.ContractState
import net.corda.core.flows.StateMachineRunId import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.Party import net.corda.core.identity.Party
@ -32,7 +31,6 @@ import net.corda.core.transactions.SignedTransaction
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.seconds import net.corda.core.utilities.seconds
import org.apache.activemq.artemis.api.core.ActiveMQException
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
@ -135,7 +133,7 @@ class NodeMonitorModel {
} }
} }
val stateMachines = performRpcReconnect(nodeHostAndPort, username, password) val stateMachines = performRpcReconnect(nodeHostAndPort, username, password, shouldRetry = false)
// Extract the flow tracking stream // Extract the flow tracking stream
// TODO is there a nicer way of doing this? Stream of streams in general results in code like this... // TODO is there a nicer way of doing this? Stream of streams in general results in code like this...
@ -156,9 +154,9 @@ class NodeMonitorModel {
futureProgressTrackerUpdates.startWith(currentProgressTrackerUpdates).flatMap { it }.retry().subscribe(progressTrackingSubject) futureProgressTrackerUpdates.startWith(currentProgressTrackerUpdates).flatMap { it }.retry().subscribe(progressTrackingSubject)
} }
private fun performRpcReconnect(nodeHostAndPort: NetworkHostAndPort, username: String, password: String): List<StateMachineInfo> { private fun performRpcReconnect(nodeHostAndPort: NetworkHostAndPort, username: String, password: String, shouldRetry: Boolean): List<StateMachineInfo> {
val connection = establishConnectionWithRetry(nodeHostAndPort, username, password) val connection = establishConnectionWithRetry(nodeHostAndPort, username, password, shouldRetry)
val proxy = connection.proxy val proxy = connection.proxy
val (stateMachineInfos, stateMachineUpdatesRaw) = proxy.stateMachinesFeed() val (stateMachineInfos, stateMachineUpdatesRaw) = proxy.stateMachinesFeed()
@ -176,7 +174,7 @@ class NodeMonitorModel {
// force closing the connection to avoid propagation of notification to the server side. // force closing the connection to avoid propagation of notification to the server side.
connection.forceClose() connection.forceClose()
// Perform re-connect. // Perform re-connect.
performRpcReconnect(nodeHostAndPort, username, password) performRpcReconnect(nodeHostAndPort, username, password, shouldRetry = true)
}) })
retryableStateMachineUpdatesSubscription.set(subscription) retryableStateMachineUpdatesSubscription.set(subscription)
@ -186,7 +184,7 @@ class NodeMonitorModel {
return stateMachineInfos return stateMachineInfos
} }
private fun establishConnectionWithRetry(nodeHostAndPort: NetworkHostAndPort, username: String, password: String): CordaRPCConnection { private fun establishConnectionWithRetry(nodeHostAndPort: NetworkHostAndPort, username: String, password: String, shouldRetry: Boolean): CordaRPCConnection {
val retryInterval = 5.seconds val retryInterval = 5.seconds
@ -205,21 +203,15 @@ class NodeMonitorModel {
require(nodeInfo.legalIdentitiesAndCerts.isNotEmpty()) require(nodeInfo.legalIdentitiesAndCerts.isNotEmpty())
_connection _connection
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
when (throwable) { if (shouldRetry) {
is ActiveMQException, is RPCException -> { // Deliberately not logging full stack trace as it will be full of internal stacktraces.
// Happens when: logger.info("Exception upon establishing connection: " + throwable.message)
// * incorrect credentials provided; null
// * incorrect endpoint specified; } else {
// - no point to retry connecting. throw throwable
throw throwable
}
else -> {
// Deliberately not logging full stack trace as it will be full of internal stacktraces.
logger.info("Exception upon establishing connection: " + throwable.message)
null
}
} }
} }
if (connection != null) { if (connection != null) {
logger.info("Connection successfully established with: $nodeHostAndPort") logger.info("Connection successfully established with: $nodeHostAndPort")
return connection return connection

View File

@ -6,6 +6,7 @@ release, see :doc:`upgrade-notes`.
Unreleased Unreleased
---------- ----------
* Remove all references to the out-of-process transaction verification.
* Introduced a hierarchy of ``DatabaseMigrationException``s, allowing ``NodeStartup`` to gracefully inform users of problems related to database migrations before exiting with a non-zero code. * Introduced a hierarchy of ``DatabaseMigrationException``s, allowing ``NodeStartup`` to gracefully inform users of problems related to database migrations before exiting with a non-zero code.

View File

@ -90,7 +90,6 @@ The following modules are available but we do not commit to their stability or c
* **net.corda.tools.explorer**: a GUI front-end for Corda * **net.corda.tools.explorer**: a GUI front-end for Corda
* **net.corda.tools.graphs**: utilities to infer project dependencies * **net.corda.tools.graphs**: utilities to infer project dependencies
* **net.corda.tools.loadtest**: Corda load tests * **net.corda.tools.loadtest**: Corda load tests
* **net.corda.verifier**: allows out-of-node transaction verification, allowing verification to scale horizontally
* **net.corda.webserver**: is a servlet container for CorDapps that export HTTP endpoints. This server is an RPC client of the node * **net.corda.webserver**: is a servlet container for CorDapps that export HTTP endpoints. This server is an RPC client of the node
* **net.corda.sandbox-creator**: sandbox utilities * **net.corda.sandbox-creator**: sandbox utilities
* **net.corda.quasar.hook**: agent to hook into Quasar and provide types exclusion lists * **net.corda.quasar.hook**: agent to hook into Quasar and provide types exclusion lists

View File

@ -23,5 +23,4 @@ The Corda repository comprises the following folders:
mock network) implementation mock network) implementation
* **tools** contains the explorer which is a GUI front-end for Corda, and also the DemoBench which is a GUI tool that * **tools** contains the explorer which is a GUI front-end for Corda, and also the DemoBench which is a GUI tool that
allows you to run Corda nodes locally for demonstrations allows you to run Corda nodes locally for demonstrations
* **verifier** allows out-of-node transaction verification, allowing verification to scale horizontally
* **webserver** is a servlet container for CorDapps that export HTTP endpoints. This server is an RPC client of the node * **webserver** is a servlet container for CorDapps that export HTTP endpoints. This server is an RPC client of the node

View File

@ -232,7 +232,7 @@ In general, the requirements outlined in this design are cross-cutting concerns
* `FlowErrorAuditEvent` (unused) * `FlowErrorAuditEvent` (unused)
* `SystemAuditEvent` (unused) * `SystemAuditEvent` (unused)
* Modules impacted * Modules impacted
* All modules packaged and shipped as part of a Corda distribution (as published to Artifactory / Maven): *core, node, node-api, node-driver, finance, confidential-identities, test-common, test-utils, verifier, webserver, jackson, jfx, mock, rpc* * All modules packaged and shipped as part of a Corda distribution (as published to Artifactory / Maven): *core, node, node-api, node-driver, finance, confidential-identities, test-common, test-utils, webserver, jackson, jfx, mock, rpc*
### Functional ### Functional
@ -458,7 +458,6 @@ Corda subsystem components:
| NotaryService | RaftNonValidatingNotaryService | as above | | NotaryService | RaftNonValidatingNotaryService | as above |
| NotaryService | BFTNonValidatingNotaryService | Logging coverage (info, debug) | | NotaryService | BFTNonValidatingNotaryService | Logging coverage (info, debug) |
| Doorman | DoormanServer (Enterprise only) | Some logging (info, warn, error), and use of `println` | | Doorman | DoormanServer (Enterprise only) | Some logging (info, warn, error), and use of `println` |
| TransactionVerifierService | OutOfProcessTransactionVerifierService (Enterprise only) | some logging (info) |
| | | | | | | |
Corda core flows: Corda core flows:

View File

@ -1,26 +0,0 @@
Out-of-process verification
===========================
A Corda node does transaction verification through ``ServiceHub.transactionVerifierService``. This is by default an
``InMemoryTransactionVerifierService`` which just verifies transactions in-process.
Corda may be configured to use out of process verification. Any number of verifiers may be started connecting to a node
through the node's exposed artemis SSL port. The messaging layer takes care of load balancing.
.. note:: We plan to introduce kernel level sandboxing around the out of process verifiers as an additional line of
defence in case of inner sandbox escapes.
To configure a node to use out of process verification specify the ``verifierType`` option in your node.conf:
.. literalinclude:: example-code/src/main/resources/example-out-of-process-verifier-node.conf
:language: cfg
You can build a verifier jar using ``./gradlew verifier:standaloneJar``.
And run it with ``java -jar verifier/build/libs/corda-verifier.jar <PATH_TO_VERIFIER_BASE_DIR>``.
``PATH_TO_VERIFIER_BASE_DIR`` should contain a ``certificates`` folder akin to the one in a node directory, and a
``verifier.conf`` containing the following:
.. literalinclude:: example-code/src/main/resources/example-verifier.conf
:language: cfg

View File

@ -1,72 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package net.corda.nodeapi
import net.corda.core.serialization.*
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.sequence
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.client.ClientMessage
import org.apache.activemq.artemis.reader.MessageUtil
object VerifierApi {
const val VERIFIER_USERNAME = "SystemUsers/Verifier"
const val VERIFICATION_REQUESTS_QUEUE_NAME = "verifier.requests"
const val VERIFICATION_RESPONSES_QUEUE_NAME_PREFIX = "verifier.responses"
private const val VERIFICATION_ID_FIELD_NAME = "id"
private const val RESULT_EXCEPTION_FIELD_NAME = "result-exception"
data class VerificationRequest(
val verificationId: Long,
val transaction: LedgerTransaction,
val responseAddress: SimpleString
) {
companion object {
fun fromClientMessage(message: ClientMessage): ObjectWithCompatibleContext<VerificationRequest> {
val bytes = ByteArray(message.bodySize).apply { message.bodyBuffer.readBytes(this) }
val bytesSequence = bytes.sequence()
val (transaction, context) = bytesSequence.deserializeWithCompatibleContext<LedgerTransaction>()
val request = VerificationRequest(
message.getLongProperty(VERIFICATION_ID_FIELD_NAME),
transaction,
MessageUtil.getJMSReplyTo(message))
return ObjectWithCompatibleContext(request, context)
}
}
fun writeToClientMessage(message: ClientMessage) {
message.putLongProperty(VERIFICATION_ID_FIELD_NAME, verificationId)
message.writeBodyBufferBytes(transaction.serialize().bytes)
MessageUtil.setJMSReplyTo(message, responseAddress)
}
}
data class VerificationResponse(
val verificationId: Long,
val exception: Throwable?
) {
companion object {
fun fromClientMessage(message: ClientMessage): VerificationResponse {
return VerificationResponse(
message.getLongProperty(VERIFICATION_ID_FIELD_NAME),
message.getBytesProperty(RESULT_EXCEPTION_FIELD_NAME)?.deserialize()
)
}
}
fun writeToClientMessage(message: ClientMessage, context: SerializationContext) {
message.putLongProperty(VERIFICATION_ID_FIELD_NAME, verificationId)
if (exception != null) {
message.putBytesProperty(RESULT_EXCEPTION_FIELD_NAME, exception.serialize(context = context).bytes)
}
}
}
}

View File

@ -0,0 +1,41 @@
package net.corda.nodeapi.internal.crypto
import net.corda.core.internal.validate
import net.corda.nodeapi.internal.DEV_CA_TRUST_STORE_FILE
import net.corda.nodeapi.internal.DEV_CA_TRUST_STORE_PASS
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.security.cert.TrustAnchor
import java.security.cert.X509Certificate
class DevCertificatesTest {
private companion object {
const val OLD_DEV_KEYSTORE_PASS = "password"
const val OLD_NODE_DEV_KEYSTORE_FILE_NAME = "nodekeystore.jks"
}
@Rule
@JvmField
val tempFolder = TemporaryFolder()
@Test
fun `create server certificate in keystore for SSL`() {
// given
val newTrustStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/$DEV_CA_TRUST_STORE_FILE"), DEV_CA_TRUST_STORE_PASS)
val newTrustRoot = newTrustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
val newTrustAnchor = TrustAnchor(newTrustRoot, null)
val oldNodeCaKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("regression-test/$OLD_NODE_DEV_KEYSTORE_FILE_NAME"), OLD_DEV_KEYSTORE_PASS)
val oldX509Certificates = oldNodeCaKeyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA).map {
it as X509Certificate
}.toTypedArray()
val certPath = X509Utilities.buildCertPath(*oldX509Certificates)
// when
certPath.validate(newTrustAnchor)
// then no exception is thrown
}
}

View File

@ -94,7 +94,7 @@ class CertificateRevocationListNodeTests {
} }
@Test @Test
fun `Simple AMPQ Client to Server connection works`() { fun `Simple AMPQ Client to Server connection works and soft fail is enabled`() {
val crlCheckSoftFail = true val crlCheckSoftFail = true
val (amqpServer, _) = createServer(serverPort, crlCheckSoftFail = crlCheckSoftFail) val (amqpServer, _) = createServer(serverPort, crlCheckSoftFail = crlCheckSoftFail)
amqpServer.use { amqpServer.use {
@ -126,7 +126,39 @@ class CertificateRevocationListNodeTests {
} }
@Test @Test
fun `AMPQ Client to Server connection fails when client's certificate is revoked`() { fun `Simple AMPQ Client to Server connection works and soft fail is disabled`() {
val crlCheckSoftFail = false
val (amqpServer, _) = createServer(serverPort, crlCheckSoftFail = crlCheckSoftFail)
amqpServer.use {
amqpServer.start()
val receiveSubs = amqpServer.onReceive.subscribe {
assertEquals(BOB_NAME.toString(), it.sourceLegalName)
assertEquals(P2P_PREFIX + "Test", it.topic)
assertEquals("Test", String(it.payload))
it.complete(true)
}
val (amqpClient, _) = createClient(serverPort, crlCheckSoftFail)
amqpClient.use {
val serverConnected = amqpServer.onConnection.toFuture()
val clientConnected = amqpClient.onConnection.toFuture()
amqpClient.start()
val serverConnect = serverConnected.get()
assertEquals(true, serverConnect.connected)
val clientConnect = clientConnected.get()
assertEquals(true, clientConnect.connected)
val msg = amqpClient.createMessage("Test".toByteArray(),
P2P_PREFIX + "Test",
ALICE_NAME.toString(),
emptyMap())
amqpClient.write(msg)
assertEquals(MessageStatus.Acknowledged, msg.onComplete.get())
receiveSubs.unsubscribe()
}
}
}
@Test
fun `AMPQ Client to Server connection fails when client's certificate is revoked and soft fail is enabled`() {
val crlCheckSoftFail = true val crlCheckSoftFail = true
val (amqpServer, _) = createServer(serverPort, crlCheckSoftFail = crlCheckSoftFail) val (amqpServer, _) = createServer(serverPort, crlCheckSoftFail = crlCheckSoftFail)
amqpServer.use { amqpServer.use {
@ -146,6 +178,27 @@ class CertificateRevocationListNodeTests {
} }
} }
@Test
fun `AMPQ Client to Server connection fails when client's certificate is revoked and soft fail is disabled`() {
val crlCheckSoftFail = false
val (amqpServer, _) = createServer(serverPort, crlCheckSoftFail = crlCheckSoftFail)
amqpServer.use {
amqpServer.start()
amqpServer.onReceive.subscribe {
it.complete(true)
}
val (amqpClient, clientCert) = createClient(serverPort, crlCheckSoftFail)
revokedNodeCerts.add(clientCert.serialNumber)
amqpClient.use {
val serverConnected = amqpServer.onConnection.toFuture()
amqpClient.onConnection.toFuture()
amqpClient.start()
val serverConnect = serverConnected.get()
assertEquals(false, serverConnect.connected)
}
}
}
@Test @Test
fun `AMPQ Client to Server connection fails when servers's certificate is revoked`() { fun `AMPQ Client to Server connection fails when servers's certificate is revoked`() {
val crlCheckSoftFail = true val crlCheckSoftFail = true

View File

@ -54,7 +54,6 @@ import net.corda.node.services.messaging.InternalRPCMessagingClient
import net.corda.node.services.messaging.MessagingService import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.messaging.P2PMessagingClient import net.corda.node.services.messaging.P2PMessagingClient
import net.corda.node.services.messaging.RPCServerConfiguration import net.corda.node.services.messaging.RPCServerConfiguration
import net.corda.node.services.messaging.VerifierMessagingClient
import net.corda.node.services.rpc.ArtemisRpcBroker import net.corda.node.services.rpc.ArtemisRpcBroker
import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.utilities.AddressUtils import net.corda.node.utilities.AddressUtils
@ -138,10 +137,7 @@ open class Node(configuration: NodeConfiguration,
} }
override val log: Logger get() = staticLog override val log: Logger get() = staticLog
override fun makeTransactionVerifierService(): TransactionVerifierService = when (configuration.verifierType) { override fun makeTransactionVerifierService(): TransactionVerifierService = InMemoryTransactionVerifierService(numberOfWorkers = 4)
VerifierType.OutOfProcess -> throw IllegalArgumentException("OutOfProcess verifier not supported") //verifierMessagingClient!!.verifierService
VerifierType.InMemory -> InMemoryTransactionVerifierService(numberOfWorkers = 4)
}
private val sameVmNodeNumber = sameVmNodeCounter.incrementAndGet() // Under normal (non-test execution) it will always be "1" private val sameVmNodeNumber = sameVmNodeCounter.incrementAndGet() // Under normal (non-test execution) it will always be "1"
@ -232,10 +228,6 @@ open class Node(configuration: NodeConfiguration,
printBasicNodeInfo("RPC connection address", it.primary.toString()) printBasicNodeInfo("RPC connection address", it.primary.toString())
printBasicNodeInfo("RPC admin connection address", it.admin.toString()) printBasicNodeInfo("RPC admin connection address", it.admin.toString())
} }
verifierMessagingClient = when (configuration.verifierType) {
VerifierType.OutOfProcess -> throw IllegalArgumentException("OutOfProcess verifier not supported") //VerifierMessagingClient(configuration, serverAddress, services.monitoringService.metrics, /*networkParameters.maxMessageSize*/MAX_FILE_SIZE)
VerifierType.InMemory -> null
}
require(info.legalIdentities.size in 1..2) { "Currently nodes must have a primary address and optionally one serviced address" } 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 val serviceIdentity: PublicKey? = if (info.legalIdentities.size == 1) null else info.legalIdentities[1].owningKey
return P2PMessagingClient( return P2PMessagingClient(
@ -337,10 +329,6 @@ open class Node(configuration: NodeConfiguration,
runOnStop += this::close runOnStop += this::close
init(rpcOps, securityManager) init(rpcOps, securityManager)
} }
verifierMessagingClient?.run {
runOnStop += this::stop
start()
}
(network as P2PMessagingClient).apply { (network as P2PMessagingClient).apply {
runOnStop += this::stop runOnStop += this::stop
start() start()
@ -443,12 +431,10 @@ open class Node(configuration: NodeConfiguration,
} }
private var internalRpcMessagingClient: InternalRPCMessagingClient? = null private var internalRpcMessagingClient: InternalRPCMessagingClient? = null
private var verifierMessagingClient: VerifierMessagingClient? = null
/** Starts a blocking event loop for message dispatch. */ /** Starts a blocking event loop for message dispatch. */
fun run() { fun run() {
internalRpcMessagingClient?.start(rpcBroker!!.serverControl) internalRpcMessagingClient?.start(rpcBroker!!.serverControl)
verifierMessagingClient?.start2()
(network as P2PMessagingClient).run() (network as P2PMessagingClient).run()
} }

View File

@ -184,7 +184,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
.asSequence() .asSequence()
// This is to only scan classes from test folders. // This is to only scan classes from test folders.
.filter { url -> .filter { url ->
listOf("main", "production/classes").none { url.toString().contains("$it/$resource") } || listOf("net.corda.core", "net.corda.node", "net.corda.finance").none { scanPackage.startsWith(it) } !url.toString().contains("main/$resource") || listOf("net.corda.core", "net.corda.node", "net.corda.finance").none { scanPackage.startsWith(it) }
} }
.map { url -> .map { url ->
if (url.protocol == "jar") { if (url.protocol == "jar") {

View File

@ -455,8 +455,7 @@ data class NodeH2Settings(
) )
enum class VerifierType { enum class VerifierType {
InMemory, InMemory
OutOfProcess
} }
enum class CertChainPolicyType { enum class CertChainPolicyType {

View File

@ -1,84 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package net.corda.node.services.messaging
import com.codahale.metrics.MetricRegistry
import net.corda.core.crypto.random63BitValue
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor
import net.corda.node.services.transactions.OutOfProcessTransactionVerifierService
import net.corda.node.utilities.AffinityExecutor
import net.corda.nodeapi.VerifierApi
import net.corda.nodeapi.VerifierApi.VERIFICATION_REQUESTS_QUEUE_NAME
import net.corda.nodeapi.VerifierApi.VERIFICATION_RESPONSES_QUEUE_NAME_PREFIX
import net.corda.nodeapi.internal.ArtemisMessagingClient
import net.corda.nodeapi.internal.config.SSLConfiguration
import org.apache.activemq.artemis.api.core.RoutingType
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.client.ClientConsumer
import java.util.concurrent.TimeUnit
class VerifierMessagingClient(config: SSLConfiguration, serverAddress: NetworkHostAndPort, metrics: MetricRegistry, private val maxMessageSize: Int) : SingletonSerializeAsToken() {
companion object {
private val log = loggerFor<VerifierMessagingClient>()
private val verifierResponseAddress = "$VERIFICATION_RESPONSES_QUEUE_NAME_PREFIX.${random63BitValue()}"
}
private val artemis = ArtemisMessagingClient(config, serverAddress, maxMessageSize)
/** An executor for sending messages */
private val messagingExecutor = AffinityExecutor.ServiceAffinityExecutor("Messaging", 1)
private var verificationResponseConsumer: ClientConsumer? = null
fun start(): Unit = synchronized(this) {
val session = artemis.start().session
fun checkVerifierCount() {
if (session.queueQuery(SimpleString(VERIFICATION_REQUESTS_QUEUE_NAME)).consumerCount == 0) {
log.warn("No connected verifier listening on $VERIFICATION_REQUESTS_QUEUE_NAME!")
}
}
// Attempts to create a durable queue on the broker which is bound to an address of the same name.
fun createQueueIfAbsent(queueName: String) {
val queueQuery = session.queueQuery(SimpleString(queueName))
if (!queueQuery.isExists) {
log.info("Create fresh queue $queueName bound on same address")
session.createQueue(queueName, RoutingType.ANYCAST, queueName, true)
}
}
createQueueIfAbsent(VERIFICATION_REQUESTS_QUEUE_NAME)
createQueueIfAbsent(verifierResponseAddress)
verificationResponseConsumer = session.createConsumer(verifierResponseAddress)
messagingExecutor.scheduleAtFixedRate(::checkVerifierCount, 0, 10, TimeUnit.SECONDS)
}
fun start2() = synchronized(this) {
verifierService.start(verificationResponseConsumer!!)
}
fun stop() = synchronized(this) {
artemis.stop()
}
internal val verifierService = OutOfProcessTransactionVerifierService(metrics) { nonce, transaction ->
messagingExecutor.fetchFrom {
sendRequest(nonce, transaction)
}
}
private fun sendRequest(nonce: Long, transaction: LedgerTransaction) = synchronized(this) {
val started = artemis.started!!
val message = started.session.createMessage(false)
val request = VerifierApi.VerificationRequest(nonce, transaction, SimpleString(verifierResponseAddress))
request.writeToClientMessage(message)
started.producer.send(VERIFICATION_REQUESTS_QUEUE_NAME, message)
}
}

View File

@ -1,81 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package net.corda.node.services.transactions
import com.codahale.metrics.Gauge
import com.codahale.metrics.MetricRegistry
import com.codahale.metrics.Timer
import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.SecureHash
import net.corda.core.node.services.TransactionVerifierService
import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.concurrent.OpenFuture
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.VerifierApi
import org.apache.activemq.artemis.api.core.client.ClientConsumer
import java.util.concurrent.ConcurrentHashMap
class OutOfProcessTransactionVerifierService(
private val metrics: MetricRegistry,
private val sendRequest: (Long, LedgerTransaction) -> Unit
) : SingletonSerializeAsToken(), TransactionVerifierService {
companion object {
private val log = contextLogger()
}
private data class VerificationHandle(
val transactionId: SecureHash,
val resultFuture: OpenFuture<Unit>,
val durationTimerContext: Timer.Context
)
private val verificationHandles = ConcurrentHashMap<Long, VerificationHandle>()
// Metrics
private fun metric(name: String) = "OutOfProcessTransactionVerifierService.$name"
private val durationTimer = metrics.timer(metric("Verification.Duration"))
private val successMeter = metrics.meter(metric("Verification.Success"))
private val failureMeter = metrics.meter(metric("Verification.Failure"))
class VerificationResultForUnknownTransaction(nonce: Long) :
Exception("Verification result arrived for unknown transaction nonce $nonce")
fun start(responseConsumer: ClientConsumer) {
log.info("Starting out of process verification service")
metrics.register(metric("VerificationsInFlight"), Gauge { verificationHandles.size })
responseConsumer.setMessageHandler { message ->
val response = VerifierApi.VerificationResponse.fromClientMessage(message)
val handle = verificationHandles.remove(response.verificationId) ?: throw VerificationResultForUnknownTransaction(response.verificationId)
handle.durationTimerContext.stop()
val exception = response.exception
if (exception == null) {
successMeter.mark()
handle.resultFuture.set(Unit)
} else {
failureMeter.mark()
handle.resultFuture.setException(exception)
}
}
}
override fun verify(transaction: LedgerTransaction): CordaFuture<*> {
log.info("Verifying ${transaction.id}")
val future = openFuture<Unit>()
val nonce = random63BitValue()
verificationHandles[nonce] = VerificationHandle(transaction.id, future, durationTimer.time())
sendRequest(nonce, transaction)
return future
}
}

View File

@ -12,21 +12,50 @@ package net.corda.node.services.vault
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import co.paralleluniverse.strands.Strand import co.paralleluniverse.strands.Strand
import net.corda.core.contracts.* import net.corda.core.contracts.Amount
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.FungibleAsset
import net.corda.core.contracts.OwnableState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.core.messaging.DataFeed import net.corda.core.messaging.DataFeed
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.StatesToRecord import net.corda.core.node.StatesToRecord
import net.corda.core.node.services.* import net.corda.core.node.services.KeyManagementService
import net.corda.core.node.services.vault.* import net.corda.core.node.services.StatesNotAvailableException
import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultQueryException
import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.DEFAULT_PAGE_NUM
import net.corda.core.node.services.vault.DEFAULT_PAGE_SIZE
import net.corda.core.node.services.vault.MAX_PAGE_SIZE
import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.Sort
import net.corda.core.node.services.vault.SortAttribute
import net.corda.core.node.services.vault.builder
import net.corda.core.schemas.PersistentStateRef import net.corda.core.schemas.PersistentStateRef
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.* import net.corda.core.transactions.ContractUpgradeWireTransaction
import net.corda.core.utilities.* import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.FullTransaction
import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.NonEmptySet
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.core.utilities.toHexString
import net.corda.core.utilities.toNonEmptySet
import net.corda.core.utilities.trace
import net.corda.node.services.api.VaultServiceInternal import net.corda.node.services.api.VaultServiceInternal
import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.statemachine.FlowStateMachineImpl
import net.corda.nodeapi.internal.persistence.* import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.HibernateConfiguration
import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit
import net.corda.nodeapi.internal.persistence.currentDBSession
import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
import org.hibernate.Session import org.hibernate.Session
import rx.Observable import rx.Observable
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
@ -142,7 +171,8 @@ class NodeVaultService(
val ourNewStates = when (statesToRecord) { val ourNewStates = when (statesToRecord) {
StatesToRecord.NONE -> throw AssertionError("Should not reach here") StatesToRecord.NONE -> throw AssertionError("Should not reach here")
StatesToRecord.ONLY_RELEVANT -> tx.outputs.withIndex().filter { StatesToRecord.ONLY_RELEVANT -> tx.outputs.withIndex().filter {
isRelevant(it.value.data, keyManagementService.filterMyKeys(tx.outputs.flatMap { it.data.participants.map { it.owningKey } }).toSet()) } isRelevant(it.value.data, keyManagementService.filterMyKeys(tx.outputs.flatMap { it.data.participants.map { it.owningKey } }).toSet())
}
StatesToRecord.ALL_VISIBLE -> tx.outputs.withIndex() StatesToRecord.ALL_VISIBLE -> tx.outputs.withIndex()
}.map { tx.outRef<ContractState>(it.index) } }.map { tx.outRef<ContractState>(it.index) }
@ -168,16 +198,13 @@ class NodeVaultService(
else -> throw IllegalArgumentException("Unsupported transaction type: ${tx.javaClass.name}") else -> throw IllegalArgumentException("Unsupported transaction type: ${tx.javaClass.name}")
} }
val myKeys by lazy { keyManagementService.filterMyKeys(ltx.outputs.flatMap { it.data.participants.map { it.owningKey } }) } val myKeys by lazy { keyManagementService.filterMyKeys(ltx.outputs.flatMap { it.data.participants.map { it.owningKey } }) }
val (consumedStateAndRefs, producedStates) = ltx.inputs. val (consumedStateAndRefs, producedStates) = ltx.inputs.zip(ltx.outputs).filter { (_, output) ->
zip(ltx.outputs). if (statesToRecord == StatesToRecord.ONLY_RELEVANT) {
filter { (_, output) -> isRelevant(output.data, myKeys.toSet())
if (statesToRecord == StatesToRecord.ONLY_RELEVANT) { } else {
isRelevant(output.data, myKeys.toSet()) true
} else { }
true }.unzip()
}
}.
unzip()
val producedStateAndRefs = producedStates.map { ltx.outRef<ContractState>(it.data) } val producedStateAndRefs = producedStates.map { ltx.outRef<ContractState>(it.data) }
if (consumedStateAndRefs.isEmpty() && producedStateAndRefs.isEmpty()) { if (consumedStateAndRefs.isEmpty() && producedStateAndRefs.isEmpty()) {
@ -403,7 +430,7 @@ class NodeVaultService(
if (!seen) { if (!seen) {
val contractInterfaces = deriveContractInterfaces(concreteType) val contractInterfaces = deriveContractInterfaces(concreteType)
contractInterfaces.map { contractInterfaces.map {
val contractInterface = contractStateTypeMappings.getOrPut(it.name, { mutableSetOf() }) val contractInterface = contractStateTypeMappings.getOrPut(it.name) { mutableSetOf() }
contractInterface.add(concreteType.name) contractInterface.add(concreteType.name)
} }
} }
@ -471,7 +498,7 @@ class NodeVaultService(
if (!paging.isDefault && index == paging.pageSize) // skip last result if paged if (!paging.isDefault && index == paging.pageSize) // skip last result if paged
return@forEachIndexed return@forEachIndexed
val vaultState = result[0] as VaultSchemaV1.VaultStates val vaultState = result[0] as VaultSchemaV1.VaultStates
val stateRef = StateRef(SecureHash.parse(vaultState.stateRef!!.txId!!), vaultState.stateRef!!.index!!) val stateRef = StateRef(SecureHash.parse(vaultState.stateRef!!.txId), vaultState.stateRef!!.index)
stateRefs.add(stateRef) stateRefs.add(stateRef)
statesMeta.add(Vault.StateMetadata(stateRef, statesMeta.add(Vault.StateMetadata(stateRef,
vaultState.contractStateClassName, vaultState.contractStateClassName,
@ -529,13 +556,24 @@ class NodeVaultService(
val distinctTypes = results.map { it } val distinctTypes = results.map { it }
val contractInterfaceToConcreteTypes = mutableMapOf<String, MutableSet<String>>() val contractInterfaceToConcreteTypes = mutableMapOf<String, MutableSet<String>>()
val unknownTypes = mutableSetOf<String>()
distinctTypes.forEach { type -> distinctTypes.forEach { type ->
val concreteType: Class<ContractState> = uncheckedCast(Class.forName(type)) val concreteType: Class<ContractState>? = try {
val contractInterfaces = deriveContractInterfaces(concreteType) uncheckedCast(Class.forName(type))
contractInterfaces.map { } catch (e: ClassNotFoundException) {
val contractInterface = contractInterfaceToConcreteTypes.getOrPut(it.name, { mutableSetOf() }) unknownTypes += type
contractInterface.add(concreteType.name) null
} }
concreteType?.let {
val contractInterfaces = deriveContractInterfaces(it)
contractInterfaces.map {
val contractInterface = contractInterfaceToConcreteTypes.getOrPut(it.name) { mutableSetOf() }
contractInterface.add(it.name)
}
}
}
if (unknownTypes.isNotEmpty()) {
log.warn("There are unknown contract state types in the vault, which will prevent these states from being used. The relevant CorDapps must be loaded for these states to be used. The types not on the classpath are ${unknownTypes.joinToString(", ", "[", "]")}.")
} }
return contractInterfaceToConcreteTypes return contractInterfaceToConcreteTypes
} }

View File

@ -25,12 +25,10 @@ import net.corda.core.flows.FlowException
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.AbstractAttachment import net.corda.core.internal.AbstractAttachment
import net.corda.core.internal.x500Name
import net.corda.core.serialization.* import net.corda.core.serialization.*
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.node.serialization.amqp.AMQPServerSerializationScheme import net.corda.node.serialization.amqp.AMQPServerSerializationScheme
import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
import net.corda.serialization.internal.* import net.corda.serialization.internal.*
import net.corda.serialization.internal.amqp.SerializerFactory.Companion.isPrimitive import net.corda.serialization.internal.amqp.SerializerFactory.Companion.isPrimitive
@ -45,6 +43,7 @@ import org.apache.qpid.proton.amqp.*
import org.apache.qpid.proton.codec.DecoderImpl import org.apache.qpid.proton.codec.DecoderImpl
import org.apache.qpid.proton.codec.EncoderImpl import org.apache.qpid.proton.codec.EncoderImpl
import org.assertj.core.api.Assertions.* import org.assertj.core.api.Assertions.*
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509v2CRLBuilder import org.bouncycastle.cert.X509v2CRLBuilder
import org.bouncycastle.cert.jcajce.JcaX509CRLConverter import org.bouncycastle.cert.jcajce.JcaX509CRLConverter
import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.jce.provider.BouncyCastleProvider
@ -667,8 +666,8 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
val scheme = AMQPServerSerializationScheme(emptyList()) val scheme = AMQPServerSerializationScheme(emptyList())
val func = scheme::class.superclasses.single { it.simpleName == "AbstractAMQPSerializationScheme" } val func = scheme::class.superclasses.single { it.simpleName == "AbstractAMQPSerializationScheme" }
.java.getDeclaredMethod("registerCustomSerializers", .java.getDeclaredMethod("registerCustomSerializers",
SerializationContext::class.java, SerializationContext::class.java,
SerializerFactory::class.java) SerializerFactory::class.java)
func.isAccessible = true func.isAccessible = true
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
@ -1021,7 +1020,7 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
} }
private fun emptyCrl(): X509CRL { private fun emptyCrl(): X509CRL {
val builder = X509v2CRLBuilder(CordaX500Name.build(DEV_INTERMEDIATE_CA.certificate.issuerX500Principal).x500Name, Date()) val builder = X509v2CRLBuilder(X500Name("CN=Corda Root CA, O=R3 HoldCo LLC, L=New York, C=US"), Date())
val provider = BouncyCastleProvider() val provider = BouncyCastleProvider()
val crlHolder = builder.build(ContentSignerBuilder.build(Crypto.RSA_SHA256, Crypto.generateKeyPair(Crypto.RSA_SHA256).private, provider)) val crlHolder = builder.build(ContentSignerBuilder.build(Crypto.RSA_SHA256, Crypto.generateKeyPair(Crypto.RSA_SHA256).private, provider))
return JcaX509CRLConverter().setProvider(provider).getCRL(crlHolder) return JcaX509CRLConverter().setProvider(provider).getCRL(crlHolder)
@ -1330,12 +1329,12 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
} }
interface DataClassByInterface<V> { interface DataClassByInterface<V> {
val v : V val v: V
} }
@Test @Test
fun dataClassBy() { fun dataClassBy() {
data class C (val s: String) : DataClassByInterface<String> { data class C(val s: String) : DataClassByInterface<String> {
override val v: String = "-- $s" override val v: String = "-- $s"
} }
@ -1349,8 +1348,8 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
try { try {
val i2 = DeserializationInput(testDefaultFactory()).deserialize(bytes) val i2 = DeserializationInput(testDefaultFactory()).deserialize(bytes)
} catch (e : NotSerializableException) { } catch (e: NotSerializableException) {
throw Error ("Deserializing serialized \$C should not throw") throw Error("Deserializing serialized \$C should not throw")
} }
} }
} }

View File

@ -21,8 +21,7 @@ import net.corda.testing.node.NotarySpec
import java.nio.file.Path import java.nio.file.Path
enum class VerifierType { enum class VerifierType {
InMemory, InMemory
OutOfProcess
} }
/** /**