From 953a4a3790940d65217450d544043efdafc45dfe Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Wed, 15 Nov 2017 12:58:37 +0000 Subject: [PATCH 01/31] Revert "Introducing network parameters." This reverts commit d04e487 --- .ci/api-current.txt | 32 ---------- .idea/compiler.xml | 2 + .../net/corda/core/node/NetworkParameters.kt | 40 ------------- .../core/node/services/NetworkMapCache.kt | 3 +- .../node/services/BFTNotaryServiceTests.kt | 24 +++----- .../node/services/DistributedServiceTests.kt | 3 +- .../net/corda/node/internal/AbstractNode.kt | 60 +++++++------------ .../node/services/config/NodeConfiguration.kt | 1 + .../network/PersistentNetworkMapCache.kt | 33 ++++++---- .../utilities/ServiceIdentityGenerator.kt | 4 +- .../corda/node/services/NotaryChangeTests.kt | 10 +++- .../messaging/ArtemisMessagingTests.kt | 2 +- .../net/corda/notarydemo/BFTNotaryCordform.kt | 7 +-- .../corda/notarydemo/RaftNotaryCordform.kt | 6 +- .../kotlin/net/corda/testing/driver/Driver.kt | 24 +++----- .../corda/testing/internal/NodeBasedTest.kt | 10 ---- .../testing/internal/demorun/DemoRunner.kt | 1 - .../corda/testing/node/MockNetworkMapCache.kt | 3 +- .../kotlin/net/corda/testing/node/MockNode.kt | 23 +------ .../net/corda/testing/node/NotarySpec.kt | 11 +++- .../net/corda/smoketesting/NodeProcess.kt | 13 ---- .../internal/NetworkParametersCopier.kt | 32 ---------- .../common/internal/ParametersUtilities.kt | 18 ------ .../net/corda/loadtest/tests/NotaryTest.kt | 2 +- 24 files changed, 89 insertions(+), 275 deletions(-) delete mode 100644 core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt delete mode 100644 testing/test-common/src/main/kotlin/net/corda/testing/common/internal/NetworkParametersCopier.kt delete mode 100644 testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt diff --git a/.ci/api-current.txt b/.ci/api-current.txt index b59f61b0ac..9416a54a9c 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -1684,27 +1684,6 @@ public @interface net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowHandle startFlow(net.corda.core.flows.FlowLogic) @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowProgressHandle startTrackedFlow(net.corda.core.flows.FlowLogic) ## -@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NetworkParameters extends java.lang.Object - public (int, List, java.time.Duration, int, int, java.time.Instant, int) - public final int component1() - @org.jetbrains.annotations.NotNull public final List component2() - @org.jetbrains.annotations.NotNull public final java.time.Duration component3() - public final int component4() - public final int component5() - @org.jetbrains.annotations.NotNull public final java.time.Instant component6() - public final int component7() - @org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters copy(int, List, java.time.Duration, int, int, java.time.Instant, int) - public boolean equals(Object) - public final int getEpoch() - @org.jetbrains.annotations.NotNull public final java.time.Duration getEventHorizon() - public final int getMaxMessageSize() - public final int getMaxTransactionSize() - public final int getMinimumPlatformVersion() - @org.jetbrains.annotations.NotNull public final java.time.Instant getModifiedTime() - @org.jetbrains.annotations.NotNull public final List getNotaries() - public int hashCode() - public String toString() -## @net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NodeInfo extends java.lang.Object public (List, List, int, long) @org.jetbrains.annotations.NotNull public final List component1() @@ -1723,17 +1702,6 @@ public @interface net.corda.core.messaging.RPCReturnsObservables public final boolean isLegalIdentity(net.corda.core.identity.Party) public String toString() ## -@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NotaryInfo extends java.lang.Object - public (net.corda.core.identity.Party, boolean) - @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component1() - public final boolean component2() - @org.jetbrains.annotations.NotNull public final net.corda.core.node.NotaryInfo copy(net.corda.core.identity.Party, boolean) - public boolean equals(Object) - @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party getIdentity() - public final boolean getValidating() - public int hashCode() - public String toString() -## @net.corda.core.DoNotImplement public interface net.corda.core.node.ServiceHub extends net.corda.core.node.ServicesForResolution @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction) @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey) diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 67675cf9a5..e754504179 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -43,6 +43,8 @@ + + diff --git a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt deleted file mode 100644 index d5a035175a..0000000000 --- a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt +++ /dev/null @@ -1,40 +0,0 @@ -package net.corda.core.node - -import net.corda.core.identity.Party -import net.corda.core.serialization.CordaSerializable -import java.time.Duration -import java.time.Instant - -/** - * @property minimumPlatformVersion - * @property notaries - * @property eventHorizon - * @property maxMessageSize Maximum P2P message sent over the wire in bytes. - * @property maxTransactionSize Maximum permitted transaction size in bytes. - * @property modifiedTime - * @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set - * of parameters. - */ -// TODO Wire up the parameters -@CordaSerializable -data class NetworkParameters( - val minimumPlatformVersion: Int, - val notaries: List, - val eventHorizon: Duration, - val maxMessageSize: Int, - val maxTransactionSize: Int, - val modifiedTime: Instant, - val epoch: Int -) { - init { - require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" } - require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" } - require(epoch > 0) { "epoch must be at least 1" } - } -} - -/** - * - */ -@CordaSerializable -data class NotaryInfo(val identity: Party, val validating: Boolean) diff --git a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt index 6cfe29dbec..5b5226087a 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt @@ -53,6 +53,7 @@ interface NetworkMapCacheBase { * * Note that the identities are sorted based on legal name, and the ordering might change once new notaries are introduced. */ + // TODO this list will be taken from NetworkParameters distributed by NetworkMap. val notaryIdentities: List // DOCEND 1 @@ -116,7 +117,7 @@ interface NetworkMapCacheBase { fun getNotary(name: CordaX500Name): Party? = notaryIdentities.firstOrNull { it.name == name } // DOCEND 2 - /** Returns true if and only if the given [Party] is a notary, which is defined by the network parameters. */ + /** Checks whether a given party is an advertised notary identity. */ fun isNotary(party: Party): Boolean = party in notaryIdentities /** diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index e7d05ff80e..2b02e383ac 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -13,8 +13,6 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.deleteIfExists import net.corda.core.internal.div -import net.corda.core.node.NotaryInfo -import net.corda.core.node.services.NotaryService import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.NetworkHostAndPort @@ -23,12 +21,11 @@ import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NotaryConfig +import net.corda.node.services.transactions.BFTNonValidatingNotaryService import net.corda.node.services.transactions.minClusterSize import net.corda.node.services.transactions.minCorrectReplicas import net.corda.node.utilities.ServiceIdentityGenerator import net.corda.testing.chooseIdentity -import net.corda.testing.common.internal.NetworkParametersCopier -import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.contracts.DummyContract import net.corda.testing.dummyCommand import net.corda.testing.node.MockNetwork @@ -57,26 +54,19 @@ class BFTNotaryServiceTests { notary = ServiceIdentityGenerator.generateToDisk( replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) }, - CordaX500Name("BFT", "Zurich", "CH"), - NotaryService.constructId(validating = false, bft = true)) - - val networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(notary, false)))) + CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH") + ) val clusterAddresses = replicaIds.map { NetworkHostAndPort("localhost", 11000 + it * 10) } - val nodes = replicaIds.map { replicaId -> - mockNet.createUnstartedNode(MockNodeParameters(configOverrides = { + replicaIds.forEach { replicaId -> + mockNet.createNode(MockNodeParameters(configOverrides = { val notary = NotaryConfig(validating = false, bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses, exposeRaces = exposeRaces)) doReturn(notary).whenever(it).notary })) - } + mockNet.createUnstartedNode() + } - // MockNetwork doesn't support BFT clusters, so we create all the nodes we need unstarted, and then install the - // network-parameters in their directories before they're started. - node = nodes.map { node -> - networkParameters.install(mockNet.baseDirectory(node.id)) - node.start() - }.last() + node = mockNet.createNode() } /** Failure mode is the redundant replica gets stuck in startup, so we can't dispose it cleanly at the end. */ diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt index 15a8914e35..ce5f107d93 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt @@ -13,6 +13,7 @@ import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.Permissions.Companion.startFlow +import net.corda.node.services.transactions.RaftValidatingNotaryService import net.corda.nodeapi.User import net.corda.testing.* import net.corda.testing.driver.NodeHandle @@ -41,7 +42,7 @@ class DistributedServiceTests { driver( extraCordappPackagesToScan = listOf("net.corda.finance.contracts"), - notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name, rpcUsers = listOf(testUser), cluster = ClusterSpec.Raft(clusterSize = 3)))) + notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name.copy(commonName = RaftValidatingNotaryService.id), rpcUsers = listOf(testUser), cluster = ClusterSpec.Raft(clusterSize = 3)))) { alice = startNode(providedName = ALICE.name, rpcUsers = listOf(testUser)).getOrThrow() raftNotaryIdentity = defaultNotaryIdentity diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index e0122433bb..078edad345 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -20,7 +20,10 @@ import net.corda.core.internal.concurrent.openFuture import net.corda.core.messaging.* import net.corda.core.node.* import net.corda.core.node.services.* -import net.corda.core.serialization.* +import net.corda.core.serialization.SerializationWhitelist +import net.corda.core.serialization.SerializeAsToken +import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.core.serialization.serialize import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.debug @@ -43,15 +46,8 @@ import net.corda.node.services.events.ScheduledActivityObserver import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.keys.PersistentKeyManagementService import net.corda.node.services.messaging.MessagingService -import net.corda.node.services.network.NetworkMapCacheImpl -import net.corda.node.services.network.NodeInfoWatcher -import net.corda.node.services.network.PersistentNetworkMapCache -import net.corda.node.services.persistence.* import net.corda.node.services.network.* -import net.corda.node.services.persistence.DBCheckpointStorage -import net.corda.node.services.persistence.DBTransactionMappingStorage -import net.corda.node.services.persistence.DBTransactionStorage -import net.corda.node.services.persistence.NodeAttachmentService +import net.corda.node.services.persistence.* import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.statemachine.* @@ -89,8 +85,6 @@ import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair * Marked as SingletonSerializeAsToken to prevent the invisible reference to AbstractNode in the ServiceHub accidentally * sweeping up the Node into the Kryo checkpoint serialization via any flows holding a reference to ServiceHub. */ -// TODO Log warning if this node is a notary but not one of the ones specified in the network parameters, both for core and custom - // In theory the NodeInfo for the node should be passed in, instead, however currently this is constructed by the // AbstractNode. It should be possible to generate the NodeInfo outside of AbstractNode, so it can be passed in. abstract class AbstractNode(val configuration: NodeConfiguration, @@ -120,7 +114,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, // low-performance prototyping period. protected abstract val serverThread: AffinityExecutor - protected lateinit var networkParameters: NetworkParameters private val cordappServices = MutableClassToInstanceMap.create() private val flowFactories = ConcurrentHashMap>, InitiatedFlowFactory<*>>() @@ -186,7 +179,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, log.info("Node starting up ...") initCertificate() val keyPairs = initNodeInfo() - readNetworkParameters() val schemaService = NodeSchemaService(cordappLoader) // Do all of this in a database transaction so anything that might need a connection has one. val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService) { database -> @@ -257,18 +249,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val keyPairs = mutableSetOf(identityKeyPair) myNotaryIdentity = configuration.notary?.let { - if (it.isClusterConfig) { - val (notaryIdentity, notaryIdentityKeyPair) = obtainIdentity(it) - keyPairs += notaryIdentityKeyPair - notaryIdentity - } else { - // In case of a single notary service myNotaryIdentity will be the node's single identity. - identity - } + val (notaryIdentity, notaryIdentityKeyPair) = obtainIdentity(it) + keyPairs += notaryIdentityKeyPair + notaryIdentity } info = NodeInfo( myAddresses(), - setOf(identity, myNotaryIdentity).filterNotNull(), + listOf(identity, myNotaryIdentity).filterNotNull(), versionInfo.platformVersion, platformClock.instant().toEpochMilli() ) @@ -596,13 +583,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, return PersistentKeyManagementService(identityService, keyPairs) } - private fun readNetworkParameters() { - val file = configuration.baseDirectory / "network-parameters" - networkParameters = file.readAll().deserialize>().verified() - log.info(networkParameters.toString()) - check(networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) { "Node is too old for the network" } - } - private fun makeCoreNotaryService(notaryConfig: NotaryConfig, database: CordaPersistence): NotaryService { val notaryKey = myNotaryIdentity?.owningKey ?: throw IllegalArgumentException("No notary identity initialized when creating a notary service") return notaryConfig.run { @@ -661,16 +641,22 @@ abstract class AbstractNode(val configuration: NodeConfiguration, private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair { val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) - val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) { - // Node's main identity or if it's a single node notary + val (id, singleName) = if (notaryConfig == null) { + // Node's main identity Pair("identity", myLegalName) } else { val notaryId = notaryConfig.run { NotaryService.constructId(validating, raft != null, bftSMaRt != null, custom) } - // The node is part of a distributed notary whose identity must already be generated beforehand. - Pair(notaryId, null) + if (!notaryConfig.isClusterConfig) { + // Node's notary identity + Pair(notaryId, myLegalName.copy(commonName = notaryId)) + } else { + // The node is part of a distributed notary whose identity must already be generated beforehand + Pair(notaryId, null) + } } + // TODO: Integrate with Key management service? val privateKeyAlias = "$id-private-key" @@ -734,13 +720,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage() override val auditService = DummyAuditService() override val transactionVerifierService by lazy { makeTransactionVerifierService() } - override val networkMapCache by lazy { - NetworkMapCacheImpl( - PersistentNetworkMapCache( - database, - networkParameters.notaries), - identityService) - } + override val networkMapCache by lazy { NetworkMapCacheImpl(PersistentNetworkMapCache(database), identityService) } override val vaultService by lazy { makeVaultService(keyManagementService, stateLoader, database.hibernateConfig) } override val contractUpgradeService by lazy { ContractUpgradeServiceImpl() } override val attachments: AttachmentStorage get() = this@AbstractNode.attachments diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 84b4940436..cffde21e21 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -117,6 +117,7 @@ data class NodeConfigurationImpl( // This is a sanity feature do not remove. require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" } require(devModeOptions == null || devMode) { "Cannot use devModeOptions outside of dev mode" } + require(myLegalName.commonName == null) { "Common name must be null: $myLegalName" } } } diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index b6e9cd7818..35da4d013b 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -12,9 +12,9 @@ import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.schemas.NodeInfoSchemaV1 import net.corda.core.messaging.DataFeed import net.corda.core.node.NodeInfo -import net.corda.core.node.NotaryInfo import net.corda.core.node.services.IdentityService import net.corda.core.node.services.NetworkMapCache.MapChange +import net.corda.core.node.services.NotaryService import net.corda.core.node.services.PartyInfo import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.serialize @@ -32,7 +32,6 @@ import java.security.PublicKey import java.util.* import javax.annotation.concurrent.ThreadSafe import kotlin.collections.HashMap -import kotlin.collections.HashSet class NetworkMapCacheImpl( networkMapCacheBase: NetworkMapCacheBaseInternal, @@ -62,15 +61,13 @@ class NetworkMapCacheImpl( * Extremely simple in-memory cache of the network map. */ @ThreadSafe -open class PersistentNetworkMapCache( - private val database: CordaPersistence, - notaries: List -) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal { +open class PersistentNetworkMapCache(private val database: CordaPersistence) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal { companion object { val logger = loggerFor() } - // TODO Cleanup registered and party nodes + // TODO Small explanation, partyNodes and registeredNodes is left in memory as it was before, because it will be removed in + // next PR that gets rid of services. These maps are used only for queries by service. protected val registeredNodes: MutableMap = Collections.synchronizedMap(HashMap()) protected val partyNodes: MutableList get() = registeredNodes.map { it.value }.toMutableList() private val _changed = PublishSubject.create() @@ -84,9 +81,22 @@ open class PersistentNetworkMapCache( override val nodeReady: CordaFuture get() = _registrationFuture private var _loadDBSuccess: Boolean = false override val loadDBSuccess get() = _loadDBSuccess - - override val notaryIdentities: List = notaries.map { it.identity } - private val validatingNotaries = notaries.mapNotNullTo(HashSet()) { if (it.validating) it.identity else null } + // TODO From the NetworkMapService redesign doc: Remove the concept of network services. + // As a temporary hack, just assume for now that every network has a notary service named "Notary Service" that can be looked up in the map. + // This should eliminate the only required usage of services. + // It is ensured on node startup when constructing a notary that the name contains "notary". + override val notaryIdentities: List + get() { + return partyNodes + .flatMap { + // TODO: validate notary identity certificates before loading into network map cache. + // Notary certificates have to be signed by the doorman directly + it.legalIdentities + } + .filter { it.name.commonName?.startsWith(NotaryService.ID_PREFIX) ?: false } + .toSet() // Distinct, because of distributed service nodes + .sortedBy { it.name.toString() } + } init { database.transaction { loadFromDB(session) } @@ -117,7 +127,7 @@ open class PersistentNetworkMapCache( } } - override fun isValidatingNotary(party: Party): Boolean = party in validatingNotaries + override fun isValidatingNotary(party: Party): Boolean = isNotary(party) && "validating" in party.name.commonName!! override fun getPartyInfo(party: Party): PartyInfo? { val nodes = database.transaction { queryByIdentityKey(session, party.owningKey) } @@ -300,6 +310,7 @@ open class PersistentNetworkMapCache( id = 0, hash = nodeInfo.serialize().hash.toString(), addresses = nodeInfo.addresses.map { NodeInfoSchemaV1.DBHostAndPort.fromHostAndPort(it) }, + // TODO Another ugly hack with special first identity... legalIdentitiesAndCerts = nodeInfo.legalIdentitiesAndCerts.mapIndexed { idx, elem -> NodeInfoSchemaV1.DBPartyAndCertificate(elem, isMain = idx == 0) }, diff --git a/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt b/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt index 2ff06f1d70..872892161b 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt @@ -20,12 +20,11 @@ object ServiceIdentityGenerator { * This method should be called *before* any of the nodes are started. * * @param dirs List of node directories to place the generated identity and key pairs in. - * @param serviceName The legal name of the distributed service. + * @param serviceName The legal name of the distributed service, with service id as CN. * @param threshold The threshold for the generated group [CompositeKey]. */ fun generateToDisk(dirs: List, serviceName: CordaX500Name, - serviceId: String, threshold: Int = 1): Party { log.trace { "Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}" } val keyPairs = (1..dirs.size).map { generateKeyPair() } @@ -40,6 +39,7 @@ object ServiceIdentityGenerator { val compositeKeyCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, issuer.certificate, issuer.keyPair, serviceName, notaryKey) val certPath = (dir / "certificates").createDirectories() / "distributedService.jks" val keystore = loadOrCreateKeyStore(certPath, "cordacadevpass") + val serviceId = serviceName.commonName keystore.setCertificateEntry("$serviceId-composite-key", compositeKeyCert.cert) keystore.setKeyEntry("$serviceId-private-key", keyPair.private, "cordacadevkeypass".toCharArray(), arrayOf(serviceKeyCert.cert, issuer.certificate.cert, rootCert)) keystore.save(certPath, "cordacadevpass") diff --git a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt index 737c3c54c9..3c3540edab 100644 --- a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt @@ -27,6 +27,10 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue class NotaryChangeTests { + companion object { + private val DUMMY_NOTARY_SERVICE_NAME: CordaX500Name = DUMMY_NOTARY.name.copy(commonName = "corda.notary.validating") + } + private lateinit var mockNet: MockNetwork private lateinit var oldNotaryNode: StartedNode private lateinit var clientNodeA: StartedNode @@ -36,7 +40,7 @@ class NotaryChangeTests { @Before fun setUp() { - val oldNotaryName = DUMMY_REGULATOR.name + val oldNotaryName = DUMMY_NOTARY.name.copy(organisation = "Old Dummy Notary") mockNet = MockNetwork( notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name), NotarySpec(oldNotaryName)), cordappPackages = listOf("net.corda.testing.contracts") @@ -44,8 +48,8 @@ class NotaryChangeTests { clientNodeA = mockNet.createNode() clientNodeB = mockNet.createNode() oldNotaryNode = mockNet.notaryNodes[1] - newNotaryParty = clientNodeA.services.networkMapCache.getNotary(DUMMY_NOTARY.name)!! - oldNotaryParty = clientNodeA.services.networkMapCache.getNotary(oldNotaryName)!! + newNotaryParty = clientNodeA.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME)!! + oldNotaryParty = clientNodeA.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME.copy(organisation = "Old Dummy Notary"))!! } @After diff --git a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt index 1adfc955b1..1398a3666a 100644 --- a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt @@ -80,7 +80,7 @@ class ArtemisMessagingTests { LogHelper.setLevel(PersistentUniquenessProvider::class) database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService) networkMapRegistrationFuture = doneFuture(Unit) - networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, emptyList()), rigorousMock()) + networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database), rigorousMock()) } @After diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt index cb0be14ee0..e0919a43ab 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt @@ -5,7 +5,6 @@ import net.corda.cordform.CordformDefinition import net.corda.cordform.CordformNode import net.corda.core.identity.CordaX500Name import net.corda.core.internal.div -import net.corda.core.node.services.NotaryService import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NotaryConfig @@ -63,10 +62,6 @@ class BFTNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { } override fun setup(context: CordformContext) { - ServiceIdentityGenerator.generateToDisk( - notaryNames.map { context.baseDirectory(it.toString()) }, - clusterName, - NotaryService.constructId(validating = false, bft = true), - minCorrectReplicas(clusterSize)) + ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it.toString()) }, clusterName, threshold = minCorrectReplicas(clusterSize)) } } diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt index 677ec08a9a..ff128c6edf 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt @@ -5,7 +5,6 @@ import net.corda.cordform.CordformDefinition import net.corda.cordform.CordformNode import net.corda.core.identity.CordaX500Name import net.corda.core.internal.div -import net.corda.core.node.services.NotaryService import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.RaftConfig @@ -59,9 +58,6 @@ class RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { } override fun setup(context: CordformContext) { - ServiceIdentityGenerator.generateToDisk( - notaryNames.map { context.baseDirectory(it.toString()) }, - clusterName, - NotaryService.constructId(validating = true, raft = true)) + ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it.toString()) }, clusterName) } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 1fd398e6ac..db770d8524 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -16,9 +16,7 @@ import net.corda.core.identity.Party import net.corda.core.internal.* import net.corda.core.internal.concurrent.* import net.corda.core.messaging.CordaRPCOps -import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo -import net.corda.core.node.NotaryInfo import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NotaryService import net.corda.core.toFuture @@ -35,8 +33,6 @@ import net.corda.nodeapi.User import net.corda.nodeapi.config.toConfig import net.corda.nodeapi.internal.addShutdownHook import net.corda.testing.* -import net.corda.testing.common.internal.NetworkParametersCopier -import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.internal.ProcessUtilities import net.corda.testing.node.ClusterSpec import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO @@ -585,7 +581,6 @@ class DriverDSL( private val countObservables = mutableMapOf>() private lateinit var _notaries: List override val notaryHandles: List get() = _notaries - private lateinit var networkParameters: NetworkParametersCopier class State { val processes = ArrayList() @@ -713,33 +708,29 @@ class DriverDSL( _shutdownManager = ShutdownManager(executorService) shutdownManager.registerShutdown { nodeInfoFilesCopier.close() } val notaryInfos = generateNotaryIdentities() - // The network parameters must be serialised before starting any of the nodes - networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) val nodeHandles = startNotaries() _notaries = notaryInfos.zip(nodeHandles) { (identity, validating), nodes -> NotaryHandle(identity, validating, nodes) } } - private fun generateNotaryIdentities(): List { + private fun generateNotaryIdentities(): List> { return notarySpecs.map { spec -> val identity = if (spec.cluster == null) { ServiceIdentityGenerator.generateToDisk( dirs = listOf(baseDirectory(spec.name)), - serviceName = spec.name, - serviceId = "identity") + serviceName = spec.name.copy(commonName = NotaryService.constructId(validating = spec.validating)) + ) } else { ServiceIdentityGenerator.generateToDisk( dirs = generateNodeNames(spec).map { baseDirectory(it) }, - serviceName = spec.name, - serviceId = NotaryService.constructId( - validating = spec.validating, - raft = spec.cluster is ClusterSpec.Raft)) + serviceName = spec.name + ) } - NotaryInfo(identity, spec.validating) + Pair(identity, spec.validating) } } private fun generateNodeNames(spec: NotarySpec): List { - return (0 until spec.cluster!!.clusterSize).map { spec.name.copy(organisation = "${spec.name.organisation}-$it") } + return (0 until spec.cluster!!.clusterSize).map { spec.name.copy(commonName = null, organisation = "${spec.name.organisation}-$it") } } private fun startNotaries(): List>> { @@ -862,7 +853,6 @@ class DriverDSL( maximumHeapSize: String): CordaFuture { val configuration = config.parseAsNodeConfiguration() val baseDirectory = configuration.baseDirectory.createDirectories() - networkParameters.install(baseDirectory) nodeInfoFilesCopier.addConfig(baseDirectory) val onNodeExit: () -> Unit = { nodeInfoFilesCopier.removeConfig(baseDirectory) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/NodeBasedTest.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/NodeBasedTest.kt index 8a107a0fe6..8b32eabd37 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/NodeBasedTest.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/NodeBasedTest.kt @@ -16,14 +16,11 @@ import net.corda.node.services.config.parseAsNodeConfiguration import net.corda.node.services.config.plus import net.corda.nodeapi.User import net.corda.testing.SerializationEnvironmentRule -import net.corda.testing.common.internal.NetworkParametersCopier -import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.driver.addressMustNotBeBoundFuture import net.corda.testing.getFreeLocalPorts import net.corda.testing.node.MockServices import org.apache.logging.log4j.Level import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.rules.TemporaryFolder import java.nio.file.Path @@ -43,7 +40,6 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi @JvmField val tempFolder = TemporaryFolder() - private lateinit var defaultNetworkParameters: NetworkParametersCopier private val nodes = mutableListOf>() private val nodeInfos = mutableListOf() @@ -51,11 +47,6 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi System.setProperty("consoleLogLevel", Level.DEBUG.name().toLowerCase()) } - @Before - fun init() { - defaultNetworkParameters = NetworkParametersCopier(testNetworkParameters(emptyList())) - } - /** * Stops the network map node and all the nodes started by [startNode]. This is called automatically after each test * but can also be called manually within a test. @@ -99,7 +90,6 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi ) val parsedConfig = config.parseAsNodeConfiguration() - defaultNetworkParameters.install(baseDirectory) val node = Node( parsedConfig, MockServices.MOCK_VERSION_INFO.copy(platformVersion = platformVersion), diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt index 958ac48503..188b6a30de 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt @@ -15,7 +15,6 @@ fun CordformDefinition.clean() { /** * Creates and starts all nodes required for the demo. */ -// TODO add notaries to cordform! fun CordformDefinition.runNodes() { driver(isDebug = true, driverDirectory = driverDirectory, portAllocation = PortAllocation.Incremental(10001), waitForAllNodesToFinish = true) { setup(this) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt index 26167cbe11..e70a9d4237 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt @@ -7,7 +7,6 @@ import net.corda.core.internal.concurrent.doneFuture import net.corda.core.node.NodeInfo import net.corda.core.node.services.NetworkMapCache import net.corda.core.utilities.NetworkHostAndPort -import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.network.PersistentNetworkMapCache import net.corda.node.utilities.CordaPersistence import net.corda.testing.getTestPartyAndCertificate @@ -18,7 +17,7 @@ import java.math.BigInteger /** * Network map cache with no backing map service. */ -class MockNetworkMapCache(database: CordaPersistence) : PersistentNetworkMapCache(database, emptyList()) { +class MockNetworkMapCache(database: CordaPersistence) : PersistentNetworkMapCache(database) { private companion object { val BANK_C = getTestPartyAndCertificate(CordaX500Name(organisation = "Bank C", locality = "London", country = "GB"), entropyToKeyPair(BigInteger.valueOf(1000)).public) val BANK_D = getTestPartyAndCertificate(CordaX500Name(organisation = "Bank D", locality = "London", country = "GB"), entropyToKeyPair(BigInteger.valueOf(2000)).public) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 2f59e327d7..699cb76b4b 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -14,7 +14,6 @@ import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient -import net.corda.core.node.NotaryInfo import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SerializationWhitelist @@ -36,13 +35,10 @@ import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.node.utilities.CordaPersistence -import net.corda.node.utilities.ServiceIdentityGenerator import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.common.internal.NetworkParametersCopier -import net.corda.testing.common.internal.testNetworkParameters -import net.corda.testing.setGlobalSerialization import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties +import net.corda.testing.setGlobalSerialization import net.corda.testing.testNodeConfiguration import org.apache.activemq.artemis.utils.ReusableLatch import org.slf4j.Logger @@ -134,7 +130,6 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete val messagingNetwork = InMemoryMessagingNetwork(networkSendManuallyPumped, servicePeerAllocationStrategy, busyLatch) // A unique identifier for this network to segregate databases with the same nodeID but different networks. private val networkId = random63BitValue() - private val networkParameters: NetworkParametersCopier private val _nodes = mutableListOf() private val serializationEnv = setGlobalSerialization(initialiseSerialization) private val sharedUserCount = AtomicInteger(0) @@ -164,7 +159,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete * @see defaultNotaryNode */ val defaultNotaryIdentity: Party get() { - return defaultNotaryNode.info.legalIdentities.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities") + return defaultNotaryNode.info.legalIdentities[1] // TODO Resolve once network parameters is merged back in } /** @@ -191,22 +186,9 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete init { filesystem.getPath("/nodes").createDirectory() - val notaryInfos = generateNotaryIdentities() - // The network parameters must be serialised before starting any of the nodes - networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) notaryNodes = createNotaries() } - private fun generateNotaryIdentities(): List { - return notarySpecs.mapIndexed { index, spec -> - val identity = ServiceIdentityGenerator.generateToDisk( - dirs = listOf(baseDirectory(nextNodeId + index)), - serviceName = spec.name, - serviceId = "identity") - NotaryInfo(identity, spec.validating) - } - } - private fun createNotaries(): List> { return notarySpecs.map { spec -> createNode(MockNodeParameters(legalName = spec.name, configOverrides = { @@ -240,7 +222,6 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete override val started: StartedNode? get() = uncheckedCast(super.started) override fun start(): StartedNode { - mockNet.networkParameters.install(configuration.baseDirectory) val started: StartedNode = uncheckedCast(super.start()) advertiseNodeToNetwork(started) return started diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt index 336d6bfc8d..852d3e899a 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt @@ -2,6 +2,7 @@ package net.corda.testing.node import net.corda.core.identity.CordaX500Name import net.corda.node.services.config.VerifierType +import net.corda.node.services.transactions.RaftValidatingNotaryService import net.corda.nodeapi.User data class NotarySpec( @@ -10,7 +11,15 @@ data class NotarySpec( val rpcUsers: List = emptyList(), val verifierType: VerifierType = VerifierType.InMemory, val cluster: ClusterSpec? = null -) +) { + init { + // TODO This will be removed once network parameters define the notaries + when (cluster) { + is ClusterSpec.Raft -> require(name.commonName == RaftValidatingNotaryService.id) + null -> require(name.commonName == null) + } + } +} sealed class ClusterSpec { abstract val clusterSize: Int diff --git a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt index 2634e295e0..34c5a9d8cc 100644 --- a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt +++ b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt @@ -2,15 +2,11 @@ package net.corda.smoketesting import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCConnection -import net.corda.client.rpc.internal.KryoClientSerializationScheme import net.corda.core.internal.copyTo import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.loggerFor -import net.corda.testing.common.internal.NetworkParametersCopier -import net.corda.testing.common.internal.testNetworkParameters -import net.corda.testing.common.internal.asContextEnv import java.nio.file.Path import java.nio.file.Paths import java.time.Instant @@ -53,14 +49,6 @@ class NodeProcess( private companion object { val javaPath: Path = Paths.get(System.getProperty("java.home"), "bin", "java") val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(systemDefault()) - val defaultNetworkParameters = run { - KryoClientSerializationScheme.createSerializationEnv().asContextEnv { - // There are no notaries in the network parameters for smoke test nodes. If this is required then we would - // need to introduce the concept of a "network" which predefines the notaries, like the driver and MockNetwork - NetworkParametersCopier(testNetworkParameters(emptyList())) - } - } - init { try { Class.forName("net.corda.node.Corda") @@ -80,7 +68,6 @@ class NodeProcess( log.info("Node directory: {}", nodeDir) config.toText().byteInputStream().copyTo(nodeDir / "node.conf") - defaultNetworkParameters.install(nodeDir) val process = startNode(nodeDir) val client = CordaRPCClient(NetworkHostAndPort("localhost", config.rpcPort)) diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/NetworkParametersCopier.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/NetworkParametersCopier.kt deleted file mode 100644 index 25ba327a73..0000000000 --- a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/NetworkParametersCopier.kt +++ /dev/null @@ -1,32 +0,0 @@ -package net.corda.testing.common.internal - -import net.corda.core.crypto.SignedData -import net.corda.core.crypto.entropyToKeyPair -import net.corda.core.crypto.sign -import net.corda.core.internal.copyTo -import net.corda.core.internal.div -import net.corda.core.node.NetworkParameters -import net.corda.core.serialization.serialize -import java.math.BigInteger -import java.nio.file.FileAlreadyExistsException -import java.nio.file.Path - -class NetworkParametersCopier(networkParameters: NetworkParameters) { - private companion object { - val DUMMY_MAP_KEY = entropyToKeyPair(BigInteger.valueOf(123)) - } - - private val serializedNetworkParameters = networkParameters.let { - val serialize = it.serialize() - val signature = DUMMY_MAP_KEY.sign(serialize) - SignedData(serialize, signature).serialize() - } - - fun install(dir: Path) { - try { - serializedNetworkParameters.open().copyTo(dir / "network-parameters") - } catch (e: FileAlreadyExistsException) { - // Leave the file untouched if it already exists - } - } -} \ No newline at end of file diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt deleted file mode 100644 index da40ba6ac3..0000000000 --- a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt +++ /dev/null @@ -1,18 +0,0 @@ -package net.corda.testing.common.internal - -import net.corda.core.node.NetworkParameters -import net.corda.core.node.NotaryInfo -import net.corda.core.utilities.days -import java.time.Instant - -fun testNetworkParameters(notaries: List): NetworkParameters { - return NetworkParameters( - minimumPlatformVersion = 1, - notaries = notaries, - modifiedTime = Instant.now(), - eventHorizon = 10000.days, - maxMessageSize = 40000, - maxTransactionSize = 40000, - epoch = 1 - ) -} \ No newline at end of file diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt index 88b517b9db..b96f6f4f40 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt @@ -24,7 +24,7 @@ val dummyNotarisationTest = LoadTest( val issuerServices = MockServices(DUMMY_CASH_ISSUER_KEY) val generateTx = Generator.pickOne(simpleNodes).flatMap { node -> Generator.int().map { - val issueBuilder = DummyContract.generateInitial(it, notary.info.legalIdentities[0], DUMMY_CASH_ISSUER) // TODO notary choice + val issueBuilder = DummyContract.generateInitial(it, notary.info.legalIdentities[1], DUMMY_CASH_ISSUER) // TODO notary choice val issueTx = issuerServices.signInitialTransaction(issueBuilder) val asset = issueTx.tx.outRef(0) val moveBuilder = DummyContract.move(asset, DUMMY_CASH_ISSUER.party) From 3d87a1a73545b14c0840dc44b6644dad71b8c5ec Mon Sep 17 00:00:00 2001 From: Thomas Schroeter Date: Wed, 15 Nov 2017 11:57:21 +0000 Subject: [PATCH 02/31] RaftCluster metrics for available members --- .../node/services/transactions/RaftUniquenessProvider.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt index ca50e8f8d5..b097d45dc5 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt @@ -14,6 +14,7 @@ import io.atomix.copycat.client.ConnectionStrategies import io.atomix.copycat.client.CopycatClient import io.atomix.copycat.client.RecoveryStrategies import io.atomix.copycat.server.CopycatServer +import io.atomix.copycat.server.cluster.Member import io.atomix.copycat.server.storage.Storage import io.atomix.copycat.server.storage.StorageLevel import net.corda.core.contracts.StateRef @@ -185,6 +186,14 @@ class RaftUniquenessProvider(private val transportConfiguration: NodeSSLConfigur metrics.register("RaftCluster.Members", Gauge> { server.cluster().members().map { it.address().toString() } }) + + metrics.register("RaftCluster.AvailableMembers", Gauge> { + server.cluster().members().filter { it.status() == Member.Status.AVAILABLE }.map { it.address().toString() } + }) + + metrics.register("RaftCluster.AvailableMembersCount", Gauge { + server.cluster().members().filter { it.status() == Member.Status.AVAILABLE }.size + }) } From 039cacae76011a4afeda134a73cb5a532ec5c0fb Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Fri, 17 Nov 2017 12:24:55 +0000 Subject: [PATCH 03/31] CORDA-787 Remove unused class (#2049) * and refactor a superclass that isn't really one * move it to internal * misc refactorings --- .../ArtemisMessagingComponent.kt | 9 +-- .../messaging/MQSecurityAsNodeTest.kt | 4 +- .../services/messaging/MQSecurityTest.kt | 8 +-- .../kotlin/net/corda/node/internal/Node.kt | 2 +- .../node/services/api/AbstractNodeService.kt | 56 ------------------- .../messaging/ArtemisMessagingServer.kt | 20 ++++--- .../node/services/messaging/Messaging.kt | 13 ----- .../services/messaging/NodeMessagingClient.kt | 17 ++++-- .../node/services/messaging/RPCServer.kt | 2 +- .../node/services/messaging/RpcAuthContext.kt | 2 +- .../InMemoryTransactionVerifierService.kt | 4 +- .../OutOfProcessTransactionVerifierService.kt | 12 ++-- .../messaging/ArtemisMessagingTests.kt | 4 +- .../testing/node/InMemoryMessagingNetwork.kt | 25 +++++++-- .../corda/testing/messaging/SimpleMQClient.kt | 3 +- .../net/corda/verifier/VerifierDriver.kt | 2 +- 16 files changed, 63 insertions(+), 120 deletions(-) rename node-api/src/main/kotlin/net/corda/nodeapi/{ => internal}/ArtemisMessagingComponent.kt (89%) delete mode 100644 node/src/main/kotlin/net/corda/node/services/api/AbstractNodeService.kt diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisMessagingComponent.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ArtemisMessagingComponent.kt similarity index 89% rename from node-api/src/main/kotlin/net/corda/nodeapi/ArtemisMessagingComponent.kt rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/ArtemisMessagingComponent.kt index 897ea6e713..7c11cf4bb7 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisMessagingComponent.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ArtemisMessagingComponent.kt @@ -1,19 +1,17 @@ -package net.corda.nodeapi +package net.corda.nodeapi.internal import net.corda.core.messaging.MessageRecipientGroup import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.serialization.CordaSerializable -import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.toBase58String -import net.corda.nodeapi.config.SSLConfiguration import java.security.PublicKey /** * The base class for Artemis services that defines shared data structures and SSL transport configuration. */ -abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() { +class ArtemisMessagingComponent { companion object { init { System.setProperty("org.jboss.logging.provider", "slf4j") @@ -66,7 +64,4 @@ abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() { data class ServiceAddress(val identity: PublicKey) : ArtemisAddress, MessageRecipientGroup { override val queueName: String = "$PEERS_PREFIX${identity.toBase58String()}" } - - /** The config object is used to pass in the passwords for the certificate KeyStore and TrustStore */ - abstract val config: SSLConfiguration? } diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt index 3f27c4e2b2..2d7b09cf1d 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt @@ -3,8 +3,8 @@ package net.corda.services.messaging import net.corda.core.crypto.Crypto import net.corda.core.internal.* import net.corda.node.utilities.* -import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER -import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEER_USER +import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER +import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER import net.corda.nodeapi.RPCApi import net.corda.nodeapi.config.SSLConfiguration import net.corda.testing.MEGA_CORP diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt index 15ba68fb24..1ddeb4497f 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt @@ -17,10 +17,10 @@ import net.corda.core.utilities.toBase58String import net.corda.core.utilities.unwrap import net.corda.node.internal.Node import net.corda.node.internal.StartedNode -import net.corda.nodeapi.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX -import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS -import net.corda.nodeapi.ArtemisMessagingComponent.Companion.P2P_QUEUE -import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEERS_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.P2P_QUEUE +import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX import net.corda.nodeapi.RPCApi import net.corda.nodeapi.User import net.corda.nodeapi.config.SSLConfiguration diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index b631a4a806..d6850bd695 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -142,7 +142,7 @@ open class Node(configuration: NodeConfiguration, info.legalIdentities[0].owningKey, serverThread, database, - services.monitoringService, + services.monitoringService.metrics, advertisedAddress) } diff --git a/node/src/main/kotlin/net/corda/node/services/api/AbstractNodeService.kt b/node/src/main/kotlin/net/corda/node/services/api/AbstractNodeService.kt deleted file mode 100644 index 42b12629c4..0000000000 --- a/node/src/main/kotlin/net/corda/node/services/api/AbstractNodeService.kt +++ /dev/null @@ -1,56 +0,0 @@ -package net.corda.node.services.api - -import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.core.serialization.deserialize -import net.corda.core.serialization.serialize -import net.corda.node.services.messaging.* -import javax.annotation.concurrent.ThreadSafe - -/** - * Abstract superclass for services that a node can host, which provides helper functions. - */ -@ThreadSafe -abstract class AbstractNodeService(val network: MessagingService) : SingletonSerializeAsToken() { - /** - * Register a handler for a message topic. In comparison to using net.addMessageHandler() this manages a lot of - * common boilerplate code. Exceptions are caught and passed to the provided consumer. If you just want a simple - * acknowledgement response with no content, use [net.corda.core.messaging.Ack]. - * - * @param topic the topic, without the default session ID postfix (".0). - * @param handler a function to handle the deserialised request and return an optional response (if return type not Unit) - * @param exceptionConsumer a function to which any thrown exception is passed. - */ - protected inline fun - addMessageHandler(topic: String, - crossinline handler: (Q) -> R, - crossinline exceptionConsumer: (Message, Exception) -> Unit): MessageHandlerRegistration { - return network.addMessageHandler(topic, MessagingService.DEFAULT_SESSION_ID) { message, _ -> - try { - val request = message.data.deserialize() - val response = handler(request) - // If the return type R is Unit, then do not send a response - if (response.javaClass != Unit.javaClass) { - val msg = network.createMessage(topic, request.sessionID, response.serialize().bytes) - network.send(msg, request.replyTo) - } - } catch (e: Exception) { - exceptionConsumer(message, e) - } - } - } - - /** - * Register a handler for a message topic. In comparison to using net.addMessageHandler() this manages a lot of - * common boilerplate code. Exceptions are propagated to the messaging layer. If you just want a simple - * acknowledgement response with no content, use [net.corda.core.messaging.Ack]. - * - * @param topic the topic, without the default session ID postfix (".0). - * @param handler a function to handle the deserialised request and return an optional response (if return type not Unit). - */ - protected inline fun - addMessageHandler(topic: String, - crossinline handler: (Q) -> R): MessageHandlerRegistration { - return addMessageHandler(topic, handler, { _: Message, exception: Exception -> throw exception }) - } - -} diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt index 19ddd89a42..8166d75080 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt @@ -11,6 +11,7 @@ import net.corda.core.internal.uncheckedCast 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.serialization.SingletonSerializeAsToken import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.debug import net.corda.core.utilities.loggerFor @@ -27,8 +28,14 @@ import net.corda.node.utilities.X509Utilities.CORDA_CLIENT_TLS import net.corda.node.utilities.X509Utilities.CORDA_ROOT_CA import net.corda.node.utilities.loadKeyStore import net.corda.nodeapi.* -import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER -import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEER_USER +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.NOTIFICATIONS_ADDRESS +import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE +import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX +import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER +import net.corda.nodeapi.internal.ArtemisMessagingComponent.ArtemisPeerAddress +import net.corda.nodeapi.internal.ArtemisMessagingComponent.NodeAddress import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl import org.apache.activemq.artemis.core.config.BridgeConfiguration @@ -86,19 +93,16 @@ import javax.security.cert.CertificateException * a fully connected network, trusted network or on localhost. */ @ThreadSafe -class ArtemisMessagingServer(override val config: NodeConfiguration, - val p2pPort: Int, +class ArtemisMessagingServer(private val config: NodeConfiguration, + private val p2pPort: Int, val rpcPort: Int?, val networkMapCache: NetworkMapCache, - val userService: RPCUserService) : ArtemisMessagingComponent() { + val userService: RPCUserService) : SingletonSerializeAsToken() { companion object { private val log = loggerFor() /** 10 MiB maximum allowed file size for attachments, including message headers. TODO: acquire this value from Network Map when supported. */ @JvmStatic val MAX_FILE_SIZE = 10485760 - - val ipDetectRequestProperty = "ip-request-id" - val ipDetectResponseProperty = "ip-address" } private class InnerState { diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt index 1697b49047..2241e3096f 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt @@ -205,19 +205,6 @@ fun MessagingService.send(topic: String, sessionID: Long, payload: Any, to: Mess fun MessagingService.send(topicSession: TopicSession, payload: Any, to: MessageRecipients, uuid: UUID = UUID.randomUUID(), retryId: Long? = null) = send(createMessage(topicSession, payload.serialize().bytes, uuid), to, retryId) -/** - * This class lets you start up a [MessagingService]. Its purpose is to stop you from getting access to the methods - * on the messaging service interface until you have successfully started up the system. One of these objects should - * be the only way to obtain a reference to a [MessagingService]. Startup may be a slow process: some implementations - * may let you cast the returned future to an object that lets you get status info. - * - * A specific implementation of the controller class will have extra features that let you customise it before starting - * it up. - */ -interface MessagingServiceBuilder { - fun start(): ListenableFuture -} - interface MessageHandlerRegistration /** diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt index 4ae2639b38..0720bd443a 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt @@ -1,5 +1,6 @@ package net.corda.node.services.messaging +import com.codahale.metrics.MetricRegistry import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name import net.corda.core.internal.ThreadBox @@ -10,6 +11,7 @@ import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.services.PartyInfo import net.corda.core.node.services.TransactionVerifierService import net.corda.core.serialization.SerializationDefaults +import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.deserialize import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.serialization.serialize @@ -20,14 +22,17 @@ import net.corda.core.utilities.sequence import net.corda.core.utilities.trace import net.corda.node.VersionInfo import net.corda.node.services.RPCUserService -import net.corda.node.services.api.MonitoringService import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.VerifierType import net.corda.node.services.statemachine.StateMachineManagerImpl import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.services.transactions.OutOfProcessTransactionVerifierService import net.corda.node.utilities.* -import net.corda.nodeapi.ArtemisMessagingComponent +import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER +import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE +import net.corda.nodeapi.internal.ArtemisMessagingComponent.ArtemisAddress +import net.corda.nodeapi.internal.ArtemisMessagingComponent.NodeAddress +import net.corda.nodeapi.internal.ArtemisMessagingComponent.ServiceAddress import net.corda.nodeapi.ArtemisTcpTransport import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.VerifierApi @@ -72,15 +77,15 @@ import javax.persistence.Lob * If not provided, will default to [serverAddress]. */ @ThreadSafe -class NodeMessagingClient(override val config: NodeConfiguration, +class NodeMessagingClient(private val config: NodeConfiguration, private val versionInfo: VersionInfo, private val serverAddress: NetworkHostAndPort, private val myIdentity: PublicKey, private val nodeExecutor: AffinityExecutor.ServiceAffinityExecutor, private val database: CordaPersistence, - private val monitoringService: MonitoringService, + private val metrics: MetricRegistry, advertisedAddress: NetworkHostAndPort = serverAddress -) : ArtemisMessagingComponent(), MessagingService { +) : SingletonSerializeAsToken(), MessagingService { companion object { private val log = loggerFor() @@ -560,7 +565,7 @@ class NodeMessagingClient(override val config: NodeConfiguration, } private fun createOutOfProcessVerifierService(): TransactionVerifierService { - return object : OutOfProcessTransactionVerifierService(monitoringService) { + return object : OutOfProcessTransactionVerifierService(metrics) { override fun sendRequest(nonce: Long, transaction: LedgerTransaction) { messagingExecutor.fetchFrom { state.locked { diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt index 30c8d4adb1..9de779a9a9 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt @@ -31,7 +31,7 @@ import net.corda.core.utilities.seconds import net.corda.node.services.RPCUserService import net.corda.node.services.logging.pushToLoggingContext import net.corda.nodeapi.* -import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER +import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER import org.apache.activemq.artemis.api.core.Message import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RpcAuthContext.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RpcAuthContext.kt index 7631c797c6..8daa20f2a3 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RpcAuthContext.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RpcAuthContext.kt @@ -3,7 +3,7 @@ package net.corda.node.services.messaging import net.corda.client.rpc.PermissionException import net.corda.core.context.InvocationContext import net.corda.node.services.Permissions -import net.corda.nodeapi.ArtemisMessagingComponent +import net.corda.nodeapi.internal.ArtemisMessagingComponent data class RpcAuthContext(val invocation: InvocationContext, val grantedPermissions: RpcPermissions) { diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/InMemoryTransactionVerifierService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/InMemoryTransactionVerifierService.kt index 5cc77a3bec..62cef75b56 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/InMemoryTransactionVerifierService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/InMemoryTransactionVerifierService.kt @@ -1,6 +1,5 @@ package net.corda.node.services.transactions -import com.google.common.util.concurrent.MoreExecutors import net.corda.core.internal.concurrent.fork import net.corda.core.node.services.TransactionVerifierService import net.corda.core.serialization.SingletonSerializeAsToken @@ -8,7 +7,6 @@ import net.corda.core.transactions.LedgerTransaction import java.util.concurrent.Executors class InMemoryTransactionVerifierService(numberOfWorkers: Int) : SingletonSerializeAsToken(), TransactionVerifierService { - private val workerPool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(numberOfWorkers)) - + private val workerPool = Executors.newFixedThreadPool(numberOfWorkers) override fun verify(transaction: LedgerTransaction) = workerPool.fork(transaction::verify) } diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/OutOfProcessTransactionVerifierService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/OutOfProcessTransactionVerifierService.kt index 1a6507796c..4819672f9f 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/OutOfProcessTransactionVerifierService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/OutOfProcessTransactionVerifierService.kt @@ -1,6 +1,7 @@ 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 @@ -11,13 +12,12 @@ import net.corda.core.internal.concurrent.openFuture import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.transactions.LedgerTransaction import net.corda.core.utilities.loggerFor -import net.corda.node.services.api.MonitoringService import net.corda.nodeapi.VerifierApi import org.apache.activemq.artemis.api.core.client.ClientConsumer import java.util.concurrent.ConcurrentHashMap abstract class OutOfProcessTransactionVerifierService( - val monitoringService: MonitoringService + private val metrics: MetricRegistry ) : SingletonSerializeAsToken(), TransactionVerifierService { companion object { val log = loggerFor() @@ -34,16 +34,16 @@ abstract class OutOfProcessTransactionVerifierService( // Metrics private fun metric(name: String) = "OutOfProcessTransactionVerifierService.$name" - private val durationTimer = monitoringService.metrics.timer(metric("Verification.Duration")) - private val successMeter = monitoringService.metrics.meter(metric("Verification.Success")) - private val failureMeter = monitoringService.metrics.meter(metric("Verification.Failure")) + 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") - monitoringService.metrics.register(metric("VerificationsInFlight"), Gauge { verificationHandles.size }) + metrics.register(metric("VerificationsInFlight"), Gauge { verificationHandles.size }) responseConsumer.setMessageHandler { message -> val response = VerifierApi.VerificationResponse.fromClientMessage(message) val handle = verificationHandles.remove(response.verificationId) ?: diff --git a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt index 1398a3666a..ebc9e59b74 100644 --- a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt @@ -9,7 +9,6 @@ import net.corda.core.messaging.RPCOps import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.RPCUserService import net.corda.node.services.RPCUserServiceImpl -import net.corda.node.services.api.MonitoringService import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.configureWithDevSSLCertificate import net.corda.node.services.network.NetworkMapCacheImpl @@ -30,7 +29,6 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder -import org.mockito.Mockito.mock import java.net.ServerSocket import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.TimeUnit.MILLISECONDS @@ -213,7 +211,7 @@ class ArtemisMessagingTests { identity.public, ServiceAffinityExecutor("ArtemisMessagingTests", 1), database, - MonitoringService(MetricRegistry())).apply { + MetricRegistry()).apply { config.configureWithDevSSLCertificate() messagingClient = this } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt index 406e72dda0..e1195084a5 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt @@ -1,8 +1,6 @@ package net.corda.testing.node -import com.google.common.util.concurrent.Futures -import com.google.common.util.concurrent.ListenableFuture -import com.google.common.util.concurrent.SettableFuture +import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.CompositeKey import net.corda.core.identity.CordaX500Name import net.corda.core.internal.ThreadBox @@ -12,6 +10,8 @@ import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate +import net.corda.core.internal.concurrent.doneFuture +import net.corda.core.internal.concurrent.openFuture import net.corda.core.node.services.PartyInfo import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SingletonSerializeAsToken @@ -33,6 +33,19 @@ import javax.annotation.concurrent.ThreadSafe import kotlin.concurrent.schedule import kotlin.concurrent.thread +/** + * This class lets you start up a [MessagingService]. Its purpose is to stop you from getting access to the methods + * on the messaging service interface until you have successfully started up the system. One of these objects should + * be the only way to obtain a reference to a [MessagingService]. Startup may be a slow process: some implementations + * may let you cast the returned future to an object that lets you get status info. + * + * A specific implementation of the controller class will have extra features that let you customise it before starting + * it up. + */ +interface MessagingServiceBuilder { + fun start(): CordaFuture +} + /** * An in-memory network allows you to manufacture [InMemoryMessaging]s for a set of participants. Each * [InMemoryMessaging] maintains a queue of messages it has received, and a background thread that dispatches @@ -190,7 +203,7 @@ class InMemoryMessagingNetwork( val serviceHandles: List, val executor: AffinityExecutor, val database: CordaPersistence) : MessagingServiceBuilder { - override fun start(): ListenableFuture { + override fun start(): CordaFuture { synchronized(this@InMemoryMessagingNetwork) { val node = InMemoryMessaging(manuallyPumped, id, executor, database) handleEndpointMap[id] = node @@ -198,7 +211,7 @@ class InMemoryMessagingNetwork( serviceToPeersMapping.getOrPut(it) { LinkedHashSet() }.add(id) Unit } - return Futures.immediateFuture(node) + return doneFuture(node) } } } @@ -244,7 +257,7 @@ class InMemoryMessagingNetwork( log.trace { transfer.toString() } val calc = latencyCalculator if (calc != null && transfer.recipients is SingleMessageRecipient) { - val messageSent = SettableFuture.create() + val messageSent = openFuture() // Inject some artificial latency. timer.schedule(calc.between(transfer.sender, transfer.recipients).toMillis()) { pumpSendInternal(transfer) diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/messaging/SimpleMQClient.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/messaging/SimpleMQClient.kt index 6109baffc8..705f150f89 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/messaging/SimpleMQClient.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/messaging/SimpleMQClient.kt @@ -3,7 +3,6 @@ package net.corda.testing.messaging import net.corda.core.identity.CordaX500Name import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.utilities.NetworkHostAndPort -import net.corda.nodeapi.ArtemisMessagingComponent import net.corda.nodeapi.ArtemisTcpTransport import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.config.SSLConfiguration @@ -14,7 +13,7 @@ import org.apache.activemq.artemis.api.core.client.* * As the name suggests this is a simple client for connecting to MQ brokers. */ class SimpleMQClient(val target: NetworkHostAndPort, - override val config: SSLConfiguration? = configureTestSSL(DEFAULT_MQ_LEGAL_NAME)) : ArtemisMessagingComponent() { + private val config: SSLConfiguration? = configureTestSSL(DEFAULT_MQ_LEGAL_NAME)) { companion object { val DEFAULT_MQ_LEGAL_NAME = CordaX500Name(organisation = "SimpleMQClient", locality = "London", country = "GB") } diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt index ecacae7cdb..1a53162b8b 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt @@ -16,7 +16,7 @@ import net.corda.core.transactions.LedgerTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.loggerFor import net.corda.node.services.config.configureDevKeyAndTrustStores -import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER +import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.ArtemisTcpTransport import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.VerifierApi From 787de9d95626ea81e348420f299d12c0ad961969 Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Fri, 17 Nov 2017 12:28:34 +0000 Subject: [PATCH 04/31] CORDA-716 Fix last of the test thread leaks (#2069) * copycat servers/clients * an SMM CheckpointChecker * and log error rather than fail on propagation of stale inheritable thread local --- .../net/corda/core/internal/ToggleField.kt | 17 +-- .../internal/concurrent/CordaFutureImpl.kt | 3 + .../internal/SerializationEnvironment.kt | 10 +- .../corda/core/internal/ToggleFieldTest.kt | 111 +++++++++++++----- .../events/NodeSchedulerServiceTest.kt | 9 +- .../DistributedImmutableMapTests.kt | 13 +- 6 files changed, 108 insertions(+), 55 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/internal/ToggleField.kt b/core/src/main/kotlin/net/corda/core/internal/ToggleField.kt index 8e0db8303d..36ae18d05c 100644 --- a/core/src/main/kotlin/net/corda/core/internal/ToggleField.kt +++ b/core/src/main/kotlin/net/corda/core/internal/ToggleField.kt @@ -45,18 +45,21 @@ class ThreadLocalToggleField(name: String) : ToggleField(name) { /** The named thread has leaked from a previous test. */ class ThreadLeakException : RuntimeException("Leaked thread detected: ${Thread.currentThread().name}") -/** @param exceptionHandler should throw the exception, or may return normally to suppress inheritance. */ +/** @param isAGlobalThreadBeingCreated whether a global thread (that should not inherit any value) is being created. */ class InheritableThreadLocalToggleField(name: String, private val log: Logger = loggerFor>(), - private val exceptionHandler: (ThreadLeakException) -> Unit = { throw it }) : ToggleField(name) { + private val isAGlobalThreadBeingCreated: (Array) -> Boolean) : ToggleField(name) { private inner class Holder(value: T) : AtomicReference(value) { fun valueOrDeclareLeak() = get() ?: throw ThreadLeakException() fun childValue(): Holder? { - get() != null && return this // Current thread isn't leaked. - val e = ThreadLeakException() - exceptionHandler(e) - log.warn(e.message) - return null + val e = ThreadLeakException() // Expensive, but so is starting the new thread. + return if (isAGlobalThreadBeingCreated(e.stackTrace)) { + get() ?: log.warn(e.message) + null + } else { + get() ?: log.error(e.message) + this + } } } diff --git a/core/src/main/kotlin/net/corda/core/internal/concurrent/CordaFutureImpl.kt b/core/src/main/kotlin/net/corda/core/internal/concurrent/CordaFutureImpl.kt index e9a949d2e9..a97eff4c56 100644 --- a/core/src/main/kotlin/net/corda/core/internal/concurrent/CordaFutureImpl.kt +++ b/core/src/main/kotlin/net/corda/core/internal/concurrent/CordaFutureImpl.kt @@ -57,6 +57,9 @@ fun CordaFuture.flatMap(transform: (V) -> CordaFuture): Cor }) } +/** Wrap a CompletableFuture, for example one that was returned by some API. */ +fun CompletableFuture.asCordaFuture(): CordaFuture = CordaFutureImpl(this) + /** * If all of the given futures succeed, the returned future's outcome is a list of all their values. * The values are in the same order as the futures in the collection, not the order of completion. diff --git a/core/src/main/kotlin/net/corda/core/serialization/internal/SerializationEnvironment.kt b/core/src/main/kotlin/net/corda/core/serialization/internal/SerializationEnvironment.kt index 5c77ce3bf7..06531d0c81 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/internal/SerializationEnvironment.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/internal/SerializationEnvironment.kt @@ -43,12 +43,12 @@ val _globalSerializationEnv = SimpleToggleField("globa @VisibleForTesting val _contextSerializationEnv = ThreadLocalToggleField("contextSerializationEnv") @VisibleForTesting -val _inheritableContextSerializationEnv = InheritableThreadLocalToggleField("inheritableContextSerializationEnv") suppressInherit@ { - it.stackTrace.forEach { - // A dying Netty thread's death event restarting the Netty global executor: - it.className == "io.netty.util.concurrent.GlobalEventExecutor" && it.methodName == "startThread" && return@suppressInherit +val _inheritableContextSerializationEnv = InheritableThreadLocalToggleField("inheritableContextSerializationEnv") { stack -> + stack.fold(false) { isAGlobalThreadBeingCreated, e -> + isAGlobalThreadBeingCreated || + (e.className == "io.netty.util.concurrent.GlobalEventExecutor" && e.methodName == "startThread") || + (e.className == "java.util.concurrent.ForkJoinPool\$DefaultForkJoinWorkerThreadFactory" && e.methodName == "newThread") } - throw it } private val serializationEnvProperties = listOf(_nodeSerializationEnv, _globalSerializationEnv, _contextSerializationEnv, _inheritableContextSerializationEnv) val effectiveSerializationEnv: SerializationEnvironment diff --git a/core/src/test/kotlin/net/corda/core/internal/ToggleFieldTest.kt b/core/src/test/kotlin/net/corda/core/internal/ToggleFieldTest.kt index b00e5d4e03..0967e93b8a 100644 --- a/core/src/test/kotlin/net/corda/core/internal/ToggleFieldTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/ToggleFieldTest.kt @@ -7,7 +7,10 @@ import com.nhaarman.mockito_kotlin.verifyNoMoreInteractions import net.corda.core.internal.concurrent.fork import net.corda.core.utilities.getOrThrow import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Rule import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runners.model.Statement import org.slf4j.Logger import java.util.concurrent.ExecutorService import java.util.concurrent.Executors @@ -28,9 +31,34 @@ private fun withSingleThreadExecutor(callable: ExecutorService.() -> T) = Ex } class ToggleFieldTest { + companion object { + @Suppress("JAVA_CLASS_ON_COMPANION") + private val companionName = javaClass.name + + private fun globalThreadCreationMethod(task: () -> T) = task() + } + + private val log = mock() + @Rule + @JvmField + val verifyNoMoreInteractions = TestRule { base, _ -> + object : Statement() { + override fun evaluate() { + base.evaluate() + verifyNoMoreInteractions(log) // Only on success. + } + } + } + + private fun inheritableThreadLocalToggleField() = InheritableThreadLocalToggleField("inheritable", log) { stack -> + stack.fold(false) { isAGlobalThreadBeingCreated, e -> + isAGlobalThreadBeingCreated || (e.className == companionName && e.methodName == "globalThreadCreationMethod") + } + } + @Test fun `toggle is enforced`() { - listOf(SimpleToggleField("simple"), ThreadLocalToggleField("local"), InheritableThreadLocalToggleField("inheritable")).forEach { field -> + listOf(SimpleToggleField("simple"), ThreadLocalToggleField("local"), inheritableThreadLocalToggleField()).forEach { field -> assertNull(field.get()) assertThatThrownBy { field.set(null) }.isInstanceOf(IllegalStateException::class.java) field.set("hello") @@ -71,7 +99,7 @@ class ToggleFieldTest { @Test fun `inheritable thread local works`() { - val field = InheritableThreadLocalToggleField("field") + val field = inheritableThreadLocalToggleField() assertNull(field.get()) field.set("hello") assertEquals("hello", field.get()) @@ -84,7 +112,7 @@ class ToggleFieldTest { @Test fun `existing threads do not inherit`() { - val field = InheritableThreadLocalToggleField("field") + val field = inheritableThreadLocalToggleField() withSingleThreadExecutor { field.set("hello") assertEquals("hello", field.get()) @@ -93,16 +121,8 @@ class ToggleFieldTest { } @Test - fun `with default exception handler, inherited values are poisoned on clear`() { - `inherited values are poisoned on clear`(InheritableThreadLocalToggleField("field") { throw it }) - } - - @Test - fun `with lenient exception handler, inherited values are poisoned on clear`() { - `inherited values are poisoned on clear`(InheritableThreadLocalToggleField("field") {}) - } - - private fun `inherited values are poisoned on clear`(field: InheritableThreadLocalToggleField) { + fun `inherited values are poisoned on clear`() { + val field = inheritableThreadLocalToggleField() field.set("hello") withSingleThreadExecutor { assertEquals("hello", fork(field::get).getOrThrow()) @@ -121,39 +141,70 @@ class ToggleFieldTest { } } + /** We log an error rather than failing-fast as the new thread may be an undetected global. */ @Test - fun `with default exception handler, leaked thread is detected as soon as it tries to create another`() { - val field = InheritableThreadLocalToggleField("field") { throw it } + fun `leaked thread propagates holder to non-global thread, with error`() { + val field = inheritableThreadLocalToggleField() field.set("hello") withSingleThreadExecutor { assertEquals("hello", fork(field::get).getOrThrow()) field.set(null) // The executor thread is now considered leaked. - val threadName = fork { Thread.currentThread().name }.getOrThrow() - val future = fork(::Thread) - assertThatThrownBy { future.getOrThrow() } - .isInstanceOf(ThreadLeakException::class.java) - .hasMessageContaining(threadName) + fork { + val leakedThreadName = Thread.currentThread().name + verifyNoMoreInteractions(log) + withSingleThreadExecutor { + // If ThreadLeakException is seen in practice, these errors form a trail of where the holder has been: + verify(log).error(argThat { contains(leakedThreadName) }) + val newThreadName = fork { Thread.currentThread().name }.getOrThrow() + val future = fork(field::get) + assertThatThrownBy { future.getOrThrow() } + .isInstanceOf(ThreadLeakException::class.java) + .hasMessageContaining(newThreadName) + fork { + verifyNoMoreInteractions(log) + withSingleThreadExecutor { + verify(log).error(argThat { contains(newThreadName) }) + } + }.getOrThrow() + } + }.getOrThrow() } } @Test - fun `with lenient exception handler, leaked thread logs a warning and does not propagate the holder`() { - val log = mock() - val field = InheritableThreadLocalToggleField("field", log) {} + fun `leaked thread does not propagate holder to global thread, with warning`() { + val field = inheritableThreadLocalToggleField() field.set("hello") withSingleThreadExecutor { assertEquals("hello", fork(field::get).getOrThrow()) field.set(null) // The executor thread is now considered leaked. - val threadName = fork { Thread.currentThread().name }.getOrThrow() fork { - verifyNoMoreInteractions(log) - withSingleThreadExecutor { - verify(log).warn(argThat { contains(threadName) }) - // In practice the new thread is for example a static thread we can't get rid of: - assertNull(fork(field::get).getOrThrow()) + val leakedThreadName = Thread.currentThread().name + globalThreadCreationMethod { + verifyNoMoreInteractions(log) + withSingleThreadExecutor { + verify(log).warn(argThat { contains(leakedThreadName) }) + // In practice the new thread is for example a static thread we can't get rid of: + assertNull(fork(field::get).getOrThrow()) + } + } + }.getOrThrow() + } + } + + @Test + fun `non-leaked thread does not propagate holder to global thread, without warning`() { + val field = inheritableThreadLocalToggleField() + field.set("hello") + withSingleThreadExecutor { + fork { + assertEquals("hello", field.get()) + globalThreadCreationMethod { + withSingleThreadExecutor { + assertNull(fork(field::get).getOrThrow()) + } } }.getOrThrow() } - verifyNoMoreInteractions(log) } } diff --git a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index 25b931fb76..3dda01deb1 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt @@ -40,10 +40,7 @@ import net.corda.testing.node.MockServices.Companion.makeTestDataSourcePropertie import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties import net.corda.testing.node.MockServices.Companion.makeTestIdentityService import org.assertj.core.api.Assertions.assertThat -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.* import java.nio.file.Paths import java.security.PublicKey import java.time.Clock @@ -77,7 +74,6 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { private lateinit var smmHasRemovedAllFlows: CountDownLatch private lateinit var kms: MockKeyManagementService private lateinit var mockSMM: StateMachineManager - private val ourIdentity = ALICE_NAME var calls: Int = 0 /** @@ -132,6 +128,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { } } + private var allowedUnsuspendedFiberCount = 0 @After fun tearDown() { // We need to make sure the StateMachineManager is done before shutting down executors. @@ -141,6 +138,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { smmExecutor.shutdown() smmExecutor.awaitTermination(60, TimeUnit.SECONDS) database.close() + mockSMM.stop(allowedUnsuspendedFiberCount) } // Ignore IntelliJ when it says these properties can be private, if they are we cannot serialise them @@ -224,6 +222,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { @Test fun `test activity due in the future and schedule another later`() { + allowedUnsuspendedFiberCount = 1 val time = stoppedClock.instant() + 1.days scheduleTX(time) diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt index e95243479a..9de048310e 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt @@ -6,6 +6,8 @@ import io.atomix.copycat.client.CopycatClient import io.atomix.copycat.server.CopycatServer import io.atomix.copycat.server.storage.Storage import io.atomix.copycat.server.storage.StorageLevel +import net.corda.core.internal.concurrent.asCordaFuture +import net.corda.core.internal.concurrent.transpose import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.getOrThrow import net.corda.node.utilities.CordaPersistence @@ -17,10 +19,7 @@ import net.corda.testing.freeLocalHostAndPort import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties import net.corda.testing.node.MockServices.Companion.makeTestIdentityService -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.* import java.util.concurrent.CompletableFuture import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -44,10 +43,8 @@ class DistributedImmutableMapTests { @After fun tearDown() { LogHelper.reset("org.apache.activemq") - cluster.forEach { - it.client.close() - it.server.shutdown() - } + cluster.map { it.client.close().asCordaFuture() }.transpose().getOrThrow() + cluster.map { it.server.shutdown().asCordaFuture() }.transpose().getOrThrow() databases.forEach { it.close() } } From aabc3c5b02eb3efa0a5809ac6d061dee01050fdf Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Fri, 17 Nov 2017 13:07:18 +0000 Subject: [PATCH 05/31] NodeInfo is no longer lateinit. (#2068) --- .../net/corda/node/internal/AbstractNode.kt | 30 +++++++++---------- .../kotlin/net/corda/node/internal/Node.kt | 3 +- .../kotlin/net/corda/testing/node/MockNode.kt | 3 +- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 078edad345..14a8179d19 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -119,7 +119,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, protected val services: ServiceHubInternal get() = _services private lateinit var _services: ServiceHubInternalImpl - protected lateinit var info: NodeInfo protected val nodeStateObservable: PublishSubject = PublishSubject.create() protected var myNotaryIdentity: PartyAndCertificate? = null protected lateinit var checkpointStorage: CheckpointStorage @@ -166,7 +165,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, check(started == null) { "Node has already been started" } log.info("Generating nodeInfo ...") initCertificate() - val keyPairs = initNodeInfo() + val (keyPairs, info) = initNodeInfo() val identityKeypair = keyPairs.first { it.public == info.legalIdentities.first().owningKey } val serialisedNodeInfo = info.serialize() val signature = identityKeypair.sign(serialisedNodeInfo) @@ -178,13 +177,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration, check(started == null) { "Node has already been started" } log.info("Node starting up ...") initCertificate() - val keyPairs = initNodeInfo() + val (keyPairs, info) = initNodeInfo() val schemaService = NodeSchemaService(cordappLoader) // Do all of this in a database transaction so anything that might need a connection has one. val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService) { database -> val transactionStorage = makeTransactionStorage(database) val stateLoader = StateLoaderImpl(transactionStorage) - val nodeServices = makeServices(keyPairs, schemaService, transactionStorage, stateLoader, database) + val nodeServices = makeServices(keyPairs, schemaService, transactionStorage, stateLoader, database, info) val notaryService = makeNotaryService(nodeServices, database) smm = makeStateMachineManager(database) val flowStarter = FlowStarterImpl(serverThread, smm) @@ -244,7 +243,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } } - private fun initNodeInfo(): Set { + private fun initNodeInfo(): Pair, NodeInfo> { val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null) val keyPairs = mutableSetOf(identityKeyPair) @@ -253,13 +252,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration, keyPairs += notaryIdentityKeyPair notaryIdentity } - info = NodeInfo( + val info = NodeInfo( myAddresses(), listOf(identity, myNotaryIdentity).filterNotNull(), versionInfo.platformVersion, platformClock.instant().toEpochMilli() ) - return keyPairs + return Pair(keyPairs, info) } protected abstract fun myAddresses(): List @@ -485,12 +484,12 @@ abstract class AbstractNode(val configuration: NodeConfiguration, * Builds node internal, advertised, and plugin services. * Returns a list of tokenizable services to be added to the serialisation context. */ - private fun makeServices(keyPairs: Set, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, stateLoader: StateLoader, database: CordaPersistence): MutableList { + private fun makeServices(keyPairs: Set, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, stateLoader: StateLoader, database: CordaPersistence, info: NodeInfo): MutableList { checkpointStorage = DBCheckpointStorage() val metrics = MetricRegistry() attachments = NodeAttachmentService(metrics) val cordappProvider = CordappProviderImpl(cordappLoader, attachments) - val identityService = makeIdentityService() + val identityService = makeIdentityService(info) val keyManagementService = makeKeyManagementService(identityService, keyPairs) _services = ServiceHubInternalImpl( identityService, @@ -500,8 +499,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration, stateLoader, MonitoringService(metrics), cordappProvider, - database) - network = makeMessagingService(database) + database, + info) + network = makeMessagingService(database, info) val tokenizableServices = mutableListOf(attachments, network, services.vaultService, services.keyManagementService, services.identityService, platformClock, services.auditService, services.monitoringService, services.networkMapCache, services.schemaService, @@ -606,7 +606,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } } - private fun makeIdentityService(): IdentityService { + private fun makeIdentityService(info: NodeInfo): IdentityService { val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword) val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) val trustRoot = trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA) @@ -635,7 +635,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, _started = null } - protected abstract fun makeMessagingService(database: CordaPersistence): MessagingService + protected abstract fun makeMessagingService(database: CordaPersistence, info: NodeInfo): MessagingService protected abstract fun startMessagingService(rpcOps: RPCOps) private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair { @@ -714,7 +714,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration, private val stateLoader: StateLoader, override val monitoringService: MonitoringService, override val cordappProvider: CordappProviderInternal, - override val database: CordaPersistence + override val database: CordaPersistence, + override val myInfo: NodeInfo ) : SingletonSerializeAsToken(), ServiceHubInternal, StateLoader by stateLoader { override val rpcFlows = ArrayList>>() override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage() @@ -726,7 +727,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, override val attachments: AttachmentStorage get() = this@AbstractNode.attachments override val networkService: MessagingService get() = network override val clock: Clock get() = platformClock - override val myInfo: NodeInfo get() = info override val myNodeStateObservable: Observable get() = nodeStateObservable override val configuration: NodeConfiguration get() = this@AbstractNode.configuration override fun cordaService(type: Class): T { diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index d6850bd695..534a63c344 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -6,6 +6,7 @@ import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.thenMatch import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.RPCOps +import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.loggerFor @@ -127,7 +128,7 @@ open class Node(configuration: NodeConfiguration, private var shutdownHook: ShutdownHook? = null private lateinit var userService: RPCUserService - override fun makeMessagingService(database: CordaPersistence): MessagingService { + override fun makeMessagingService(database: CordaPersistence, info: NodeInfo): MessagingService { userService = RPCUserServiceImpl(configuration.rpcUsers) val serverAddress = configuration.messagingServerAddress ?: makeLocalMessageBroker() diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 699cb76b4b..5f7b04135a 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -14,6 +14,7 @@ import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient +import net.corda.core.node.NodeInfo import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SerializationWhitelist @@ -238,7 +239,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete // We only need to override the messaging service here, as currently everything that hits disk does so // through the java.nio API which we are already mocking via Jimfs. - override fun makeMessagingService(database: CordaPersistence): MessagingService { + override fun makeMessagingService(database: CordaPersistence, info: NodeInfo): MessagingService { require(id >= 0) { "Node ID must be zero or positive, was passed: " + id } return mockNet.messagingNetwork.createNodeWithID( !mockNet.threadPerNode, From 19aba62fc6fa7fbe45103745eab40091e1077d63 Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Fri, 17 Nov 2017 14:16:17 +0000 Subject: [PATCH 06/31] Move tests to using named identities (#1879) Move tests to using named identities in preparation for multiple identities work. Includes: * NetworkMapCacheTest * NodeInterestRatesTest * NodeVaultServiceTest * ContractUpgradeFlowTest * Cash tests * AttachmentSerializationTest * CordaRPCOpsImplTest * VaultWithCashTest * ScheduledFlowTests --- .../corda/client/jfx/NodeMonitorModelTest.kt | 12 ++--- .../corda/client/rpc/CordaRPCClientTest.kt | 21 ++++----- .../confidential/SwapIdentitiesFlowTests.kt | 18 +++++--- .../kotlin/net/corda/core/node/NodeInfo.kt | 19 ++++++-- .../net/corda/core/flows/FlowsInJavaTest.java | 10 +++-- .../core/flows/ContractUpgradeFlowTest.kt | 21 ++++++--- .../corda/core/flows/ReceiveAllFlowTests.kt | 6 +-- .../internal/ResolveTransactionsFlowTest.kt | 6 +-- .../AttachmentSerializationTest.kt | 36 ++++++++------- .../LedgerTransactionQueryTests.kt | 11 +++-- .../corda/finance/flows/CashExitFlowTests.kt | 7 ++- .../corda/finance/flows/CashIssueFlowTests.kt | 7 ++- .../finance/flows/CashPaymentFlowTests.kt | 6 +-- .../net/corda/node/CordaRPCOpsImplTest.kt | 13 +++--- .../node/messaging/TwoPartyTradeFlowTests.kt | 45 +++++++++++-------- .../corda/node/services/NotaryChangeTests.kt | 31 +++++++------ .../services/events/ScheduledFlowTests.kt | 43 +++++++++--------- .../services/network/NetworkMapCacheTest.kt | 44 +++++++++--------- .../services/vault/NodeVaultServiceTest.kt | 31 +++++++------ .../node/services/vault/VaultWithCashTest.kt | 17 ++++--- .../corda/irs/api/NodeInterestRatesTest.kt | 20 +++++---- .../kotlin/net/corda/testing/node/MockNode.kt | 9 ++++ 22 files changed, 254 insertions(+), 179 deletions(-) diff --git a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt index 9ca6dabb37..ad83ee723e 100644 --- a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt +++ b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt @@ -64,7 +64,7 @@ class NodeMonitorModelTest { invokeRpc(CordaRPCOps::stateMachinesFeed), invokeRpc(CordaRPCOps::networkMapFeed)) ) - val aliceNodeHandle = startNode(providedName = ALICE.name, rpcUsers = listOf(cashUser)).getOrThrow() + val aliceNodeHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(cashUser)).getOrThrow() aliceNode = aliceNodeHandle.nodeInfo newNode = { nodeName -> startNode(providedName = nodeName).getOrThrow().nodeInfo } val monitor = NodeMonitorModel() @@ -79,7 +79,7 @@ class NodeMonitorModelTest { rpc = monitor.proxyObservable.value!! notaryParty = defaultNotaryIdentity - val bobNodeHandle = startNode(providedName = BOB.name, rpcUsers = listOf(cashUser)).getOrThrow() + val bobNodeHandle = startNode(providedName = BOB_NAME, rpcUsers = listOf(cashUser)).getOrThrow() bobNode = bobNodeHandle.nodeInfo val monitorBob = NodeMonitorModel() stateMachineUpdatesBob = monitorBob.stateMachineUpdates.bufferUntilSubscribed() @@ -91,20 +91,20 @@ class NodeMonitorModelTest { @Test fun `network map update`() = setup { - val charlieNode = newNode(CHARLIE.name) + val charlieNode = newNode(CHARLIE_NAME) val nonServiceIdentities = aliceNode.legalIdentitiesAndCerts + bobNode.legalIdentitiesAndCerts + charlieNode.legalIdentitiesAndCerts networkMapUpdates.filter { it.node.legalIdentitiesAndCerts.any { it in nonServiceIdentities } } .expectEvents(isStrict = false) { sequence( // TODO : Add test for remove when driver DSL support individual node shutdown. expect { output: NetworkMapCache.MapChange -> - require(output.node.chooseIdentity().name == ALICE.name) { "Expecting : ${ALICE.name}, Actual : ${output.node.chooseIdentity().name}" } + require(output.node.legalIdentities.any { it.name == ALICE_NAME }) { "Expecting : ${ALICE_NAME}, Actual : ${output.node.legalIdentities.map(Party::name)}" } }, expect { output: NetworkMapCache.MapChange -> - require(output.node.chooseIdentity().name == BOB.name) { "Expecting : ${BOB.name}, Actual : ${output.node.chooseIdentity().name}" } + require(output.node.legalIdentities.any { it.name == BOB_NAME }) { "Expecting : ${BOB_NAME}, Actual : ${output.node.legalIdentities.map(Party::name)}" } }, expect { output: NetworkMapCache.MapChange -> - require(output.node.chooseIdentity().name == CHARLIE.name) { "Expecting : ${CHARLIE.name}, Actual : ${output.node.chooseIdentity().name}" } + require(output.node.legalIdentities.any { it.name == CHARLIE_NAME }) { "Expecting : ${CHARLIE_NAME}, Actual : ${output.node.legalIdentities.map(Party::name)}" } } ) } diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt index 065abee220..fd14bf35d0 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt @@ -2,6 +2,7 @@ package net.corda.client.rpc import net.corda.core.context.* import net.corda.core.crypto.random63BitValue +import net.corda.core.identity.Party import net.corda.core.internal.concurrent.flatMap import net.corda.core.internal.packageName import net.corda.core.messaging.* @@ -28,7 +29,6 @@ import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.After import org.junit.Before import org.junit.Test -import kotlin.reflect.KClass import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -42,6 +42,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C invokeRpc("vaultQueryByCriteria")) ) private lateinit var node: StartedNode + private lateinit var identity: Party private lateinit var client: CordaRPCClient private var connection: CordaRPCConnection? = null @@ -51,8 +52,9 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C @Before fun setUp() { - node = startNode(ALICE.name, rpcUsers = listOf(rpcUser)) + node = startNode(ALICE_NAME, rpcUsers = listOf(rpcUser)) client = CordaRPCClient(node.internals.configuration.rpcAddress!!) + identity = node.info.identityFromX500Name(ALICE_NAME) } @After @@ -86,7 +88,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C println("Creating proxy") println("Starting flow") val flowHandle = connection!!.proxy.startTrackedFlow(::CashIssueFlow, - 20.DOLLARS, OpaqueBytes.of(0), node.info.chooseIdentity() + 20.DOLLARS, OpaqueBytes.of(0), identity ) println("Started flow, waiting on result") flowHandle.progress.subscribe { @@ -98,7 +100,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C @Test fun `sub-type of FlowException thrown by flow`() { login(rpcUser.username, rpcUser.password) - val handle = connection!!.proxy.startFlow(::CashPaymentFlow, 100.DOLLARS, node.info.chooseIdentity()) + val handle = connection!!.proxy.startFlow(::CashPaymentFlow, 100.DOLLARS, identity) assertThatExceptionOfType(CashException::class.java).isThrownBy { handle.returnValue.getOrThrow() } @@ -107,7 +109,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C @Test fun `check basic flow has no progress`() { login(rpcUser.username, rpcUser.password) - connection!!.proxy.startFlow(::CashPaymentFlow, 100.DOLLARS, node.info.chooseIdentity()).use { + connection!!.proxy.startFlow(::CashPaymentFlow, 100.DOLLARS, identity).use { assertFalse(it is FlowProgressHandle<*>) } } @@ -120,7 +122,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C assertTrue(startCash.isEmpty(), "Should not start with any cash") val flowHandle = proxy.startFlow(::CashIssueFlow, - 123.DOLLARS, OpaqueBytes.of(0), node.info.chooseIdentity() + 123.DOLLARS, OpaqueBytes.of(0), identity ) println("Started issuing cash, waiting on result") flowHandle.returnValue.get() @@ -136,13 +138,12 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C val impersonatedActor = Actor(Actor.Id("Mark Dadada"), AuthServiceId("Test"), owningLegalIdentity = BOB.name) login(rpcUser.username, rpcUser.password, externalTrace, impersonatedActor) val proxy = connection!!.proxy - val nodeIdentity = node.info.chooseIdentity() val updates = proxy.stateMachinesFeed().updates - node.services.startFlow(CashIssueFlow(2000.DOLLARS, OpaqueBytes.of(0), nodeIdentity), InvocationContext.shell()).flatMap { it.resultFuture }.getOrThrow() - proxy.startFlow(::CashIssueFlow, 123.DOLLARS, OpaqueBytes.of(0), nodeIdentity).returnValue.getOrThrow() - proxy.startFlowDynamic(CashIssueFlow::class.java, 1000.DOLLARS, OpaqueBytes.of(0), nodeIdentity).returnValue.getOrThrow() + node.services.startFlow(CashIssueFlow(2000.DOLLARS, OpaqueBytes.of(0),identity), InvocationContext.shell()).flatMap { it.resultFuture }.getOrThrow() + proxy.startFlow(::CashIssueFlow, 123.DOLLARS, OpaqueBytes.of(0), identity).returnValue.getOrThrow() + proxy.startFlowDynamic(CashIssueFlow::class.java, 1000.DOLLARS, OpaqueBytes.of(0), identity).returnValue.getOrThrow() val historicalIds = mutableSetOf() var sessionId: Trace.SessionId? = null diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt index 6fc8270b2d..e709715eb9 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt @@ -3,10 +3,12 @@ package net.corda.confidential import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party +import net.corda.core.identity.PartyAndCertificate import net.corda.core.utilities.getOrThrow import net.corda.testing.* import net.corda.testing.node.MockNetwork import org.junit.Before +import net.corda.testing.node.MockNodeParameters import org.junit.Test import kotlin.test.* @@ -83,28 +85,30 @@ class SwapIdentitiesFlowTests { val notaryNode = mockNet.defaultNotaryNode val aliceNode = mockNet.createPartyNode(ALICE.name) val bobNode = mockNet.createPartyNode(BOB.name) - val bob: Party = bobNode.services.myInfo.singleIdentity() + val alice: PartyAndCertificate = aliceNode.info.singleIdentityAndCert() + val bob: PartyAndCertificate = bobNode.info.singleIdentityAndCert() + val notary: PartyAndCertificate = mockNet.defaultNotaryIdentityAndCert // Check that the wrong signature is rejected notaryNode.database.transaction { - notaryNode.services.keyManagementService.freshKeyAndCert(notaryNode.services.myInfo.chooseIdentityAndCert(), false) + notaryNode.services.keyManagementService.freshKeyAndCert(notary, false) }.let { anonymousNotary -> val sigData = SwapIdentitiesFlow.buildDataToSign(anonymousNotary) val signature = notaryNode.services.keyManagementService.sign(sigData, anonymousNotary.owningKey) assertFailsWith("Signature does not match the given identity and nonce") { - SwapIdentitiesFlow.validateAndRegisterIdentity(aliceNode.services.identityService, bob, anonymousNotary, signature.withoutKey()) + SwapIdentitiesFlow.validateAndRegisterIdentity(aliceNode.services.identityService, bob.party, anonymousNotary, signature.withoutKey()) } } // Check that the right signing key, but wrong identity is rejected - val anonymousAlice = aliceNode.database.transaction { - aliceNode.services.keyManagementService.freshKeyAndCert(aliceNode.services.myInfo.chooseIdentityAndCert(), false) + val anonymousAlice: PartyAndCertificate = aliceNode.database.transaction { + aliceNode.services.keyManagementService.freshKeyAndCert(alice, false) } bobNode.database.transaction { - bobNode.services.keyManagementService.freshKeyAndCert(bobNode.services.myInfo.chooseIdentityAndCert(), false) + bobNode.services.keyManagementService.freshKeyAndCert(bob, false) }.let { anonymousBob -> val sigData = SwapIdentitiesFlow.buildDataToSign(anonymousAlice) val signature = bobNode.services.keyManagementService.sign(sigData, anonymousBob.owningKey) assertFailsWith("Signature does not match the given identity and nonce.") { - SwapIdentitiesFlow.validateAndRegisterIdentity(aliceNode.services.identityService, bob, anonymousBob, signature.withoutKey()) + SwapIdentitiesFlow.validateAndRegisterIdentity(aliceNode.services.identityService, bob.party, anonymousBob, signature.withoutKey()) } } diff --git a/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt b/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt index 07e6c550be..1977b601d9 100644 --- a/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt +++ b/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt @@ -44,8 +44,21 @@ data class NodeInfo(val addresses: List, /** Returns true if [party] is one of the identities of this node, else false. */ fun isLegalIdentity(party: Party): Boolean = party in legalIdentities - fun identityFromX500Name(name: CordaX500Name): Party { - val identity = legalIdentitiesAndCerts.singleOrNull { it.name == name } ?: throw IllegalArgumentException("Node does not have an identity \"$name\"") - return identity.party + /** + * Get a legal identity of this node from the X.500 name. This is intended for use in cases where the node is + * expected to have a matching identity, and will throw an exception if no match is found. + * + * @throws IllegalArgumentException if the node has no matching identity. + */ + fun identityFromX500Name(name: CordaX500Name): Party = identityAndCertFromX500Name(name).party + + /** + * Get a legal identity and certificate of this node from the X.500 name. This is intended for use in cases where + * the node is expected to have a matching identity, and will throw an exception if no match is found. + * + * @throws IllegalArgumentException if the node has no matching identity. + */ + fun identityAndCertFromX500Name(name: CordaX500Name): PartyAndCertificate { + return legalIdentitiesAndCerts.singleOrNull { it.name == name } ?: throw IllegalArgumentException("Node does not have an identity \"$name\"") } } diff --git a/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java b/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java index 8393c915e5..27f15c91f6 100644 --- a/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java +++ b/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java @@ -4,8 +4,8 @@ import co.paralleluniverse.fibers.Suspendable; import com.google.common.primitives.Primitives; import net.corda.core.identity.Party; import net.corda.node.internal.StartedNode; -import net.corda.testing.node.MockNetwork; import net.corda.testing.TestConstants; +import net.corda.testing.node.MockNetwork; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -13,7 +13,7 @@ import org.junit.Test; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -import static net.corda.testing.CoreTestUtils.chooseIdentity; +import static net.corda.testing.CoreTestUtils.singleIdentity; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.Assert.fail; import static net.corda.testing.NodeTestUtils.startFlow; @@ -22,11 +22,13 @@ public class FlowsInJavaTest { private final MockNetwork mockNet = new MockNetwork(); private StartedNode aliceNode; private StartedNode bobNode; + private Party bob; @Before public void setUp() throws Exception { aliceNode = mockNet.createPartyNode(TestConstants.getALICE().getName()); bobNode = mockNet.createPartyNode(TestConstants.getBOB().getName()); + bob = singleIdentity(bobNode.getInfo()); } @After @@ -37,7 +39,7 @@ public class FlowsInJavaTest { @Test public void suspendableActionInsideUnwrap() throws Exception { bobNode.getInternals().registerInitiatedFlow(SendHelloAndThenReceive.class); - Future result = startFlow(aliceNode.getServices(), new SendInUnwrapFlow(chooseIdentity(bobNode.getInfo()))).getResultFuture(); + Future result = startFlow(aliceNode.getServices(), new SendInUnwrapFlow(bob)).getResultFuture(); mockNet.runNetwork(); assertThat(result.get()).isEqualTo("Hello"); } @@ -52,7 +54,7 @@ public class FlowsInJavaTest { } private void primitiveReceiveTypeTest(Class receiveType) throws InterruptedException { - PrimitiveReceiveFlow flow = new PrimitiveReceiveFlow(chooseIdentity(bobNode.getInfo()), receiveType); + PrimitiveReceiveFlow flow = new PrimitiveReceiveFlow(bob, receiveType); Future result = startFlow(aliceNode.getServices(), flow).getResultFuture(); mockNet.runNetwork(); try { diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index 6022a95a35..ba0cd17038 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -41,13 +41,20 @@ class ContractUpgradeFlowTest { private lateinit var aliceNode: StartedNode private lateinit var bobNode: StartedNode private lateinit var notary: Party + private lateinit var alice: Party + private lateinit var bob: Party @Before fun setup() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts.asset", "net.corda.core.flows")) - aliceNode = mockNet.createPartyNode(ALICE.name) - bobNode = mockNet.createPartyNode(BOB.name) + aliceNode = mockNet.createPartyNode(ALICE_NAME) + bobNode = mockNet.createPartyNode(BOB_NAME) notary = mockNet.defaultNotaryIdentity + alice = aliceNode.info.singleIdentity() + bob = bobNode.info.singleIdentity() + + // Process registration + mockNet.runNetwork() } @After @@ -58,11 +65,11 @@ class ContractUpgradeFlowTest { @Test fun `2 parties contract upgrade`() { // Create dummy contract. - val twoPartyDummyContract = DummyContract.generateInitial(0, notary, aliceNode.info.chooseIdentity().ref(1), bobNode.info.chooseIdentity().ref(1)) + val twoPartyDummyContract = DummyContract.generateInitial(0, notary, alice.ref(1), bob.ref(1)) val signedByA = aliceNode.services.signInitialTransaction(twoPartyDummyContract) val stx = bobNode.services.addSignature(signedByA) - aliceNode.services.startFlow(FinalityFlow(stx, setOf(bobNode.info.chooseIdentity()))) + aliceNode.services.startFlow(FinalityFlow(stx, setOf(bob))) mockNet.runNetwork() val atx = aliceNode.database.transaction { aliceNode.services.validatedTransactions.getTransaction(stx.id) } @@ -128,7 +135,7 @@ class ContractUpgradeFlowTest { fun `2 parties contract upgrade using RPC`() { rpcDriver(initialiseSerialization = false) { // Create dummy contract. - val twoPartyDummyContract = DummyContract.generateInitial(0, notary, aliceNode.info.chooseIdentity().ref(1), bobNode.info.chooseIdentity().ref(1)) + val twoPartyDummyContract = DummyContract.generateInitial(0, notary, alice.ref(1), bob.ref(1)) val signedByA = aliceNode.services.signInitialTransaction(twoPartyDummyContract) val stx = bobNode.services.addSignature(signedByA) @@ -140,7 +147,7 @@ class ContractUpgradeFlowTest { )) val rpcA = startProxy(aliceNode, user) val rpcB = startProxy(bobNode, user) - val handle = rpcA.startFlow(::FinalityInvoker, stx, setOf(bobNode.info.chooseIdentity())) + val handle = rpcA.startFlow(::FinalityInvoker, stx, setOf(bob)) mockNet.runNetwork() handle.returnValue.getOrThrow() @@ -202,7 +209,7 @@ class ContractUpgradeFlowTest { @Test fun `upgrade Cash to v2`() { // Create some cash. - val chosenIdentity = aliceNode.info.chooseIdentity() + val chosenIdentity = alice val result = aliceNode.services.startFlow(CashIssueFlow(Amount(1000, USD), OpaqueBytes.of(1), notary)).resultFuture mockNet.runNetwork() val stx = result.getOrThrow().stx diff --git a/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt index c5a9e0a3e0..ea2db6947b 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt @@ -5,8 +5,8 @@ import net.corda.core.identity.Party import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap -import net.corda.testing.chooseIdentity import net.corda.testing.node.network +import net.corda.testing.singleIdentity import net.corda.testing.startFlow import org.assertj.core.api.Assertions.assertThat import org.junit.Test @@ -20,7 +20,7 @@ class ReceiveMultipleFlowTests { val stringValue = "Thriller" nodes[2].registerAnswer(AlgorithmDefinition::class, stringValue) - val flow = nodes[0].services.startFlow(ParallelAlgorithmMap(nodes[1].info.chooseIdentity(), nodes[2].info.chooseIdentity())) + val flow = nodes[0].services.startFlow(ParallelAlgorithmMap(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity())) runNetwork() val result = flow.resultFuture.getOrThrow() @@ -37,7 +37,7 @@ class ReceiveMultipleFlowTests { val value2 = 6.0 nodes[2].registerAnswer(ParallelAlgorithmList::class, value2) - val flow = nodes[0].services.startFlow(ParallelAlgorithmList(nodes[1].info.chooseIdentity(), nodes[2].info.chooseIdentity())) + val flow = nodes[0].services.startFlow(ParallelAlgorithmList(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity())) runNetwork() val data = flow.resultFuture.getOrThrow() diff --git a/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt b/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt index 2473c58a0e..f71470b406 100644 --- a/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt @@ -10,9 +10,9 @@ import net.corda.core.utilities.sequence import net.corda.node.internal.StartedNode import net.corda.testing.MEGA_CORP import net.corda.testing.MINI_CORP -import net.corda.testing.chooseIdentity import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork +import net.corda.testing.singleIdentity import net.corda.testing.startFlow import org.junit.After import org.junit.Before @@ -45,8 +45,8 @@ class ResolveTransactionsFlowTest { megaCorpNode.internals.registerInitiatedFlow(TestResponseFlow::class.java) miniCorpNode.internals.registerInitiatedFlow(TestResponseFlow::class.java) notary = mockNet.defaultNotaryIdentity - megaCorp = megaCorpNode.info.chooseIdentity() - miniCorp = miniCorpNode.info.chooseIdentity() + megaCorp = megaCorpNode.info.singleIdentity() + miniCorp = miniCorpNode.info.singleIdentity() } @After diff --git a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt index 00f176f268..b2d9b80816 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt @@ -7,6 +7,7 @@ import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowSession import net.corda.core.flows.InitiatingFlow import net.corda.core.flows.TestDataVendingFlow +import net.corda.core.identity.Party import net.corda.core.internal.FetchAttachmentsFlow import net.corda.core.internal.FetchDataFlow import net.corda.core.utilities.getOrThrow @@ -15,9 +16,11 @@ import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.internal.StartedNode import net.corda.node.services.persistence.NodeAttachmentService import net.corda.node.utilities.currentDBSession -import net.corda.testing.chooseIdentity +import net.corda.testing.ALICE_NAME +import net.corda.testing.BOB_NAME import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNodeParameters +import net.corda.testing.singleIdentity import net.corda.testing.startFlow import org.junit.After import org.junit.Before @@ -63,13 +66,16 @@ class AttachmentSerializationTest { private lateinit var mockNet: MockNetwork private lateinit var server: StartedNode private lateinit var client: StartedNode + private lateinit var serverIdentity: Party @Before fun setUp() { mockNet = MockNetwork() - server = mockNet.createNode() - client = mockNet.createNode() + server = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) + client = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME)) client.internals.disableDBCloseOnStop() // Otherwise the in-memory database may disappear (taking the checkpoint with it) while we reboot the client. + mockNet.runNetwork() + serverIdentity = server.info.singleIdentity() } @After @@ -91,9 +97,7 @@ class AttachmentSerializationTest { private class ClientResult(internal val attachmentContent: String) @InitiatingFlow - private abstract class ClientLogic(server: StartedNode<*>) : FlowLogic() { - internal val server = server.info.chooseIdentity() - + private abstract class ClientLogic(val serverIdentity: Party) : FlowLogic() { @Suspendable internal fun communicate(serverSession: FlowSession) { serverSession.sendAndReceive("ping one").unwrap { assertEquals("pong", it) } @@ -112,30 +116,30 @@ class AttachmentSerializationTest { override val signers get() = throw UnsupportedOperationException() } - private class CustomAttachmentLogic(server: StartedNode<*>, private val attachmentId: SecureHash, private val customContent: String) : ClientLogic(server) { + private class CustomAttachmentLogic(serverIdentity: Party, private val attachmentId: SecureHash, private val customContent: String) : ClientLogic(serverIdentity) { @Suspendable override fun getAttachmentContent(): String { val customAttachment = CustomAttachment(attachmentId, customContent) - val session = initiateFlow(server) + val session = initiateFlow(serverIdentity) communicate(session) return customAttachment.customContent } } - private class OpenAttachmentLogic(server: StartedNode<*>, private val attachmentId: SecureHash) : ClientLogic(server) { + private class OpenAttachmentLogic(serverIdentity: Party, private val attachmentId: SecureHash) : ClientLogic(serverIdentity) { @Suspendable override fun getAttachmentContent(): String { val localAttachment = serviceHub.attachments.openAttachment(attachmentId)!! - val session = initiateFlow(server) + val session = initiateFlow(serverIdentity) communicate(session) return localAttachment.extractContent() } } - private class FetchAttachmentLogic(server: StartedNode<*>, private val attachmentId: SecureHash) : ClientLogic(server) { + private class FetchAttachmentLogic(serverIdentity: Party, private val attachmentId: SecureHash) : ClientLogic(serverIdentity) { @Suspendable override fun getAttachmentContent(): String { - val serverSession = initiateFlow(server) + val serverSession = initiateFlow(serverIdentity) val (downloadedAttachment) = subFlow(FetchAttachmentsFlow(setOf(attachmentId), serverSession)).downloaded serverSession.send(FetchDataFlow.Request.End) communicate(serverSession) @@ -166,14 +170,14 @@ class AttachmentSerializationTest { @Test fun `custom (and non-persisted) attachment should be saved in checkpoint`() { val attachmentId = SecureHash.sha256("any old data") - launchFlow(CustomAttachmentLogic(server, attachmentId, "custom"), 1) + launchFlow(CustomAttachmentLogic(serverIdentity, attachmentId, "custom"), 1) assertEquals("custom", rebootClientAndGetAttachmentContent()) } @Test fun `custom attachment should be saved in checkpoint even if its data was persisted`() { val attachmentId = client.saveAttachment("genuine") - launchFlow(CustomAttachmentLogic(server, attachmentId, "custom"), 1) + launchFlow(CustomAttachmentLogic(serverIdentity, attachmentId, "custom"), 1) client.hackAttachment(attachmentId, "hacked") // Should not be reloaded, checkAttachmentsOnLoad may cause next line to blow up if client attempts it. assertEquals("custom", rebootClientAndGetAttachmentContent()) } @@ -182,7 +186,7 @@ class AttachmentSerializationTest { fun `only the hash of a regular attachment should be saved in checkpoint`() { val attachmentId = client.saveAttachment("genuine") client.attachments.checkAttachmentsOnLoad = false // Cached by AttachmentImpl. - launchFlow(OpenAttachmentLogic(server, attachmentId), 1) + launchFlow(OpenAttachmentLogic(serverIdentity, attachmentId), 1) client.hackAttachment(attachmentId, "hacked") assertEquals("hacked", rebootClientAndGetAttachmentContent(false)) // Pass in false to allow non-genuine data to be loaded. } @@ -190,7 +194,7 @@ class AttachmentSerializationTest { @Test fun `only the hash of a FetchAttachmentsFlow attachment should be saved in checkpoint`() { val attachmentId = server.saveAttachment("genuine") - launchFlow(FetchAttachmentLogic(server, attachmentId), 2, sendData = true) + launchFlow(FetchAttachmentLogic(serverIdentity, attachmentId), 2, sendData = true) client.hackAttachment(attachmentId, "hacked") assertEquals("hacked", rebootClientAndGetAttachmentContent(false)) } diff --git a/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt b/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt index 28d7b6615c..eba531f13a 100644 --- a/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt @@ -2,9 +2,13 @@ package net.corda.core.transactions import net.corda.core.contracts.* import net.corda.core.identity.AbstractParty -import net.corda.testing.* +import net.corda.core.identity.Party +import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.contracts.DummyContract +import net.corda.testing.dummyCommand import net.corda.testing.node.MockServices +import net.corda.testing.singleIdentity import org.junit.Before import org.junit.Rule import org.junit.Test @@ -18,6 +22,7 @@ class LedgerTransactionQueryTests { @JvmField val testSerialization = SerializationEnvironmentRule() private val services: MockServices = MockServices() + private val identity: Party = services.myInfo.singleIdentity() @Before fun setup() { @@ -66,8 +71,8 @@ class LedgerTransactionQueryTests { tx.addInputState(makeDummyStateAndRef(i.toString())) tx.addOutputState(makeDummyState(i), DummyContract.PROGRAM_ID) tx.addOutputState(makeDummyState(i.toString()), DummyContract.PROGRAM_ID) - tx.addCommand(Commands.Cmd1(i), listOf(services.myInfo.chooseIdentity().owningKey)) - tx.addCommand(Commands.Cmd2(i), listOf(services.myInfo.chooseIdentity().owningKey)) + tx.addCommand(Commands.Cmd1(i), listOf(identity.owningKey)) + tx.addCommand(Commands.Cmd2(i), listOf(identity.owningKey)) } return tx.toLedgerTransaction(services) } diff --git a/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt b/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt index d6bbaa30ac..bead3cefde 100644 --- a/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt @@ -7,8 +7,7 @@ import net.corda.finance.DOLLARS import net.corda.finance.`issued by` import net.corda.finance.contracts.asset.Cash import net.corda.node.internal.StartedNode -import net.corda.testing.BOC -import net.corda.testing.chooseIdentity +import net.corda.testing.BOC_NAME import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode @@ -31,8 +30,8 @@ class CashExitFlowTests { fun start() { mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts.asset")) - bankOfCordaNode = mockNet.createPartyNode(BOC.name) - bankOfCorda = bankOfCordaNode.info.chooseIdentity() + bankOfCordaNode = mockNet.createPartyNode(BOC_NAME) + bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME) notary = mockNet.defaultNotaryIdentity val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, notary)).resultFuture mockNet.runNetwork() diff --git a/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt b/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt index c4ea887d0c..7a8fa93377 100644 --- a/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt @@ -7,8 +7,7 @@ import net.corda.finance.DOLLARS import net.corda.finance.`issued by` import net.corda.finance.contracts.asset.Cash import net.corda.node.internal.StartedNode -import net.corda.testing.BOC -import net.corda.testing.chooseIdentity +import net.corda.testing.BOC_NAME import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode @@ -28,8 +27,8 @@ class CashIssueFlowTests { @Before fun start() { mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts.asset")) - bankOfCordaNode = mockNet.createPartyNode(BOC.name) - bankOfCorda = bankOfCordaNode.info.chooseIdentity() + bankOfCordaNode = mockNet.createPartyNode(BOC_NAME) + bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME) notary = mockNet.defaultNotaryIdentity } diff --git a/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt b/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt index 89e753aac3..26b9ab267d 100644 --- a/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt @@ -31,9 +31,9 @@ class CashPaymentFlowTests { @Before fun start() { mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts.asset")) - bankOfCordaNode = mockNet.createPartyNode(BOC.name) - aliceNode = mockNet.createPartyNode(ALICE.name) - bankOfCorda = bankOfCordaNode.info.chooseIdentity() + bankOfCordaNode = mockNet.createPartyNode(BOC_NAME) + bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME) + aliceNode = mockNet.createPartyNode(ALICE_NAME) val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, mockNet.defaultNotaryIdentity)).resultFuture future.getOrThrow() } diff --git a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt index 13e2387e8b..02e2f80dc4 100644 --- a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt @@ -34,6 +34,7 @@ import net.corda.node.services.messaging.RpcPermissions import net.corda.testing.* import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode +import net.corda.testing.node.MockNodeParameters import org.apache.commons.io.IOUtils import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.After @@ -54,6 +55,7 @@ class CordaRPCOpsImplTest { private lateinit var mockNet: MockNetwork private lateinit var aliceNode: StartedNode + private lateinit var alice: Party private lateinit var notary: Party private lateinit var rpc: CordaRPCOps private lateinit var stateMachineUpdates: Observable @@ -63,14 +65,15 @@ class CordaRPCOpsImplTest { @Before fun setup() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset")) - aliceNode = mockNet.createNode() + aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) rpc = SecureCordaRPCOps(aliceNode.services, aliceNode.smm, aliceNode.database, aliceNode.services) CURRENT_RPC_CONTEXT.set(RpcAuthContext(InvocationContext.rpc(testActor()), RpcPermissions.NONE)) mockNet.runNetwork() withPermissions(invokeRpc(CordaRPCOps::notaryIdentities)) { - notary = rpc.notaryIdentities().first() + notary = rpc.notaryIdentities().single() } + alice = aliceNode.services.myInfo.identityFromX500Name(ALICE_NAME) } @After @@ -115,7 +118,7 @@ class CordaRPCOpsImplTest { val anonymisedRecipient = result.returnValue.getOrThrow().recipient!! val expectedState = Cash.State(Amount(quantity, - Issued(aliceNode.info.chooseIdentity().ref(ref), GBP)), + Issued(alice.ref(ref), GBP)), anonymisedRecipient) // Query vault via RPC @@ -153,7 +156,7 @@ class CordaRPCOpsImplTest { mockNet.runNetwork() - rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, aliceNode.info.chooseIdentity()) + rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, alice) mockNet.runNetwork() @@ -187,7 +190,7 @@ class CordaRPCOpsImplTest { require(stx.tx.outputs.size == 1) val signaturePubKeys = stx.sigs.map { it.by }.toSet() // Only Alice signed, as issuer - val aliceKey = aliceNode.info.chooseIdentity().owningKey + val aliceKey = alice.owningKey require(signaturePubKeys.size <= aliceKey.keys.size) require(aliceKey.isFulfilledBy(signaturePubKeys)) }, diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index cf418a7fea..ac8bfbc3b0 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -97,6 +97,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { val bankNode = mockNet.createPartyNode(BOC_NAME) val alice = aliceNode.info.singleIdentity() val bank = bankNode.info.singleIdentity() + val bob = bobNode.info.singleIdentity() val notary = mockNet.defaultNotaryIdentity val cashIssuer = bank.ref(1) val cpIssuer = bank.ref(1, 2, 3) @@ -114,9 +115,9 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { 1200.DOLLARS `issued by` bank.ref(0), null, notary).second } - insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) + insertFakeTransactions(alicesFakePaper, aliceNode, alice, notaryNode, bankNode) - val (bobStateMachine, aliceResult) = runBuyerAndSeller(notary, aliceNode, bobNode, + val (bobStateMachine, aliceResult) = runBuyerAndSeller(notary, bob, aliceNode, bobNode, "alice's paper".outputStateAndRef()) // TODO: Verify that the result was inserted into the transaction database. @@ -147,6 +148,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { val bankNode = mockNet.createPartyNode(BOC_NAME) val alice = aliceNode.info.singleIdentity() val bank = bankNode.info.singleIdentity() + val bob = bobNode.info.singleIdentity() val issuer = bank.ref(1) val notary = mockNet.defaultNotaryIdentity @@ -163,7 +165,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { 1200.DOLLARS `issued by` bank.ref(0), null, notary).second } - insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) + insertFakeTransactions(alicesFakePaper, aliceNode, alice, notaryNode, bankNode) val cashLockId = UUID.randomUUID() bobNode.database.transaction { @@ -174,7 +176,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { } } - val (bobStateMachine, aliceResult) = runBuyerAndSeller(notary, aliceNode, bobNode, + val (bobStateMachine, aliceResult) = runBuyerAndSeller(notary, bob, aliceNode, bobNode, "alice's paper".outputStateAndRef()) assertEquals(aliceResult.getOrThrow(), bobStateMachine.getOrThrow().resultFuture.getOrThrow()) @@ -210,6 +212,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { val notary = mockNet.defaultNotaryIdentity val alice = aliceNode.info.singleIdentity() val bank = bankNode.info.singleIdentity() + val bob = bobNode.info.singleIdentity() val issuer = bank.ref(1, 2, 3) bobNode.database.transaction { @@ -220,8 +223,8 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { fillUpForSeller(false, issuer, alice, 1200.DOLLARS `issued by` bank.ref(0), null, notary).second } - insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) - val aliceFuture = runBuyerAndSeller(notary, aliceNode, bobNode, "alice's paper".outputStateAndRef()).sellerResult + insertFakeTransactions(alicesFakePaper, aliceNode, alice, notaryNode, bankNode) + val aliceFuture = runBuyerAndSeller(notary, bob, aliceNode, bobNode, "alice's paper".outputStateAndRef()).sellerResult // Everything is on this thread so we can now step through the flow one step at a time. // Seller Alice already sent a message to Buyer Bob. Pump once: @@ -333,16 +336,16 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { val bobsFakeCash = bobNode.database.transaction { fillUpForBuyer(false, issuer, AnonymousParty(bob.owningKey), notary) }.second - val bobsSignedTxns = insertFakeTransactions(bobsFakeCash, bobNode, notaryNode, bankNode) + val bobsSignedTxns = insertFakeTransactions(bobsFakeCash, bobNode, bob, notaryNode, bankNode) val alicesFakePaper = aliceNode.database.transaction { fillUpForSeller(false, issuer, alice, 1200.DOLLARS `issued by` bank.ref(0), attachmentID, notary).second } - val alicesSignedTxns = insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) + val alicesSignedTxns = insertFakeTransactions(alicesFakePaper, aliceNode, alice, notaryNode, bankNode) mockNet.runNetwork() // Clear network map registration messages - runBuyerAndSeller(notary, aliceNode, bobNode, "alice's paper".outputStateAndRef()) + runBuyerAndSeller(notary, bob, aliceNode, bobNode, "alice's paper".outputStateAndRef()) mockNet.runNetwork() @@ -420,6 +423,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { val notary = mockNet.defaultNotaryIdentity val alice: Party = aliceNode.info.singleIdentity() val bank: Party = bankNode.info.singleIdentity() + val bob = bobNode.info.singleIdentity() val issuer = bank.ref(1, 2, 3) ledger(aliceNode.services) { @@ -438,20 +442,20 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { val bobsFakeCash = bobNode.database.transaction { fillUpForBuyer(false, issuer, AnonymousParty(bobsKey), notary) }.second - insertFakeTransactions(bobsFakeCash, bobNode, notaryNode, bankNode) + insertFakeTransactions(bobsFakeCash, bobNode, bob, notaryNode, bankNode) val alicesFakePaper = aliceNode.database.transaction { fillUpForSeller(false, issuer, alice, 1200.DOLLARS `issued by` bank.ref(0), attachmentID, notary).second } - insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) + insertFakeTransactions(alicesFakePaper, aliceNode, alice, notaryNode, bankNode) val aliceTxStream = aliceNode.services.validatedTransactions.track().updates val aliceTxMappings = with(aliceNode) { database.transaction { services.stateMachineRecordedTransactionMapping.track().updates } } - val aliceSmId = runBuyerAndSeller(notary, aliceNode, bobNode, + val aliceSmId = runBuyerAndSeller(notary, bob, aliceNode, bobNode, "alice's paper".outputStateAndRef()).sellerId mockNet.runNetwork() @@ -511,12 +515,13 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { ) private fun runBuyerAndSeller(notary: Party, + buyer: Party, sellerNode: StartedNode, buyerNode: StartedNode, assetToSell: StateAndRef): RunResult { val buyerFlows: Observable> = buyerNode.internals.registerInitiatedFlow(BuyerAcceptor::class.java) val firstBuyerFiber = buyerFlows.toFuture().map { it.stateMachine } - val seller = SellerInitiator(buyerNode.info.chooseIdentity(), notary, assetToSell, 1000.DOLLARS, anonymous) + val seller = SellerInitiator(buyer, notary, assetToSell, 1000.DOLLARS, anonymous) val sellerResult = sellerNode.services.startFlow(seller).resultFuture return RunResult(firstBuyerFiber, sellerResult, seller.stateMachine.id) } @@ -582,10 +587,10 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { fillUpForSeller(aliceError, issuer, alice,1200.DOLLARS `issued by` issuer, null, notary).second } - insertFakeTransactions(bobsBadCash, bobNode, notaryNode, bankNode) - insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) + insertFakeTransactions(bobsBadCash, bobNode, bob, notaryNode, bankNode) + insertFakeTransactions(alicesFakePaper, aliceNode, alice, notaryNode, bankNode) - val (bobStateMachine, aliceResult) = runBuyerAndSeller(notary, aliceNode, bobNode, "alice's paper".outputStateAndRef()) + val (bobStateMachine, aliceResult) = runBuyerAndSeller(notary, bob, aliceNode, bobNode, "alice's paper".outputStateAndRef()) mockNet.runNetwork() @@ -605,13 +610,14 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { private fun insertFakeTransactions( wtxToSign: List, node: StartedNode<*>, + identity: Party, notaryNode: StartedNode<*>, vararg extraSigningNodes: StartedNode<*>): Map { val notaryParty = mockNet.defaultNotaryIdentity val signed = wtxToSign.map { val id = it.id val sigs = mutableListOf() - val nodeKey = node.info.chooseIdentity().owningKey + val nodeKey = identity.owningKey sigs += node.services.keyManagementService.sign( SignableData(id, SignatureMetadata(1, Crypto.findSignatureScheme(nodeKey).schemeNumberID)), nodeKey @@ -621,11 +627,12 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { notaryParty.owningKey ) extraSigningNodes.forEach { currentNode -> + val currentIdentity = currentNode.info.singleIdentity() sigs += currentNode.services.keyManagementService.sign( SignableData(id, SignatureMetadata( 1, - Crypto.findSignatureScheme(currentNode.info.chooseIdentity().owningKey).schemeNumberID)), - currentNode.info.chooseIdentity().owningKey) + Crypto.findSignatureScheme(currentIdentity.owningKey).schemeNumberID)), + currentIdentity.owningKey) } SignedTransaction(it, sigs) } diff --git a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt index 3c3540edab..ee3e96d280 100644 --- a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt @@ -17,6 +17,7 @@ import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.NotarySpec +import net.corda.testing.node.MockNodeParameters import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.After import org.junit.Before @@ -37,6 +38,7 @@ class NotaryChangeTests { private lateinit var clientNodeB: StartedNode private lateinit var newNotaryParty: Party private lateinit var oldNotaryParty: Party + private lateinit var clientA: Party @Before fun setUp() { @@ -45,8 +47,9 @@ class NotaryChangeTests { notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name), NotarySpec(oldNotaryName)), cordappPackages = listOf("net.corda.testing.contracts") ) - clientNodeA = mockNet.createNode() - clientNodeB = mockNet.createNode() + clientNodeA = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) + clientNodeB = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME)) + clientA = clientNodeA.info.singleIdentity() oldNotaryNode = mockNet.notaryNodes[1] newNotaryParty = clientNodeA.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME)!! oldNotaryParty = clientNodeA.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME.copy(organisation = "Old Dummy Notary"))!! @@ -59,7 +62,7 @@ class NotaryChangeTests { @Test fun `should change notary for a state with single participant`() { - val state = issueState(clientNodeA, oldNotaryParty) + val state = issueState(clientNodeA.services, clientA, oldNotaryParty) assertEquals(state.state.notary, oldNotaryParty) val newState = changeNotary(state, clientNodeA, newNotaryParty) assertEquals(newState.state.notary, newNotaryParty) @@ -97,7 +100,7 @@ class NotaryChangeTests { @Test fun `should not break encumbrance links`() { - val issueTx = issueEncumberedState(clientNodeA, oldNotaryParty) + val issueTx = issueEncumberedState(clientNodeA.services, clientA, oldNotaryParty) val state = StateAndRef(issueTx.outputs.first(), StateRef(issueTx.id, 0)) val newNotary = newNotaryParty @@ -131,7 +134,7 @@ class NotaryChangeTests { @Test fun `notary change and regular transactions are properly handled during resolution in longer chains`() { - val issued = issueState(clientNodeA, oldNotaryParty) + val issued = issueState(clientNodeA.services, clientA, oldNotaryParty) val moved = moveState(issued, clientNodeA, clientNodeB) // We don't to tx resolution when moving state to another node, so need to add the issue transaction manually @@ -170,8 +173,8 @@ class NotaryChangeTests { return finalTransaction.tx.outRef(0) } - private fun issueEncumberedState(node: StartedNode<*>, notaryIdentity: Party): WireTransaction { - val owner = node.info.chooseIdentity().ref(0) + private fun issueEncumberedState(services: ServiceHub, nodeIdentity: Party, notaryIdentity: Party): WireTransaction { + val owner = nodeIdentity.ref(0) val stateA = DummyContract.SingleOwnerState(Random().nextInt(), owner.party) val stateB = DummyContract.SingleOwnerState(Random().nextInt(), owner.party) val stateC = DummyContract.SingleOwnerState(Random().nextInt(), owner.party) @@ -182,9 +185,9 @@ class NotaryChangeTests { addOutputState(stateC, DummyContract.PROGRAM_ID, notaryIdentity) addOutputState(stateB, DummyContract.PROGRAM_ID, notaryIdentity, encumbrance = 1) // Encumbered by stateC } - val stx = node.services.signInitialTransaction(tx) - node.services.recordTransactions(stx) - return tx.toWireTransaction(node.services) + val stx = services.signInitialTransaction(tx) + services.recordTransactions(stx) + return tx.toWireTransaction(services) } // TODO: Add more test cases once we have a general flow/service exception handling mechanism: @@ -196,10 +199,10 @@ class NotaryChangeTests { // - The transaction type is not a notary change transaction at all. } -fun issueState(node: StartedNode<*>, notaryIdentity: Party): StateAndRef { - val tx = DummyContract.generateInitial(Random().nextInt(), notaryIdentity, node.info.chooseIdentity().ref(0)) - val stx = node.services.signInitialTransaction(tx) - node.services.recordTransactions(stx) +fun issueState(services: ServiceHub, nodeIdentity: Party, notaryIdentity: Party): StateAndRef { + val tx = DummyContract.generateInitial(Random().nextInt(), notaryIdentity, nodeIdentity.ref(0)) + val stx = services.signInitialTransaction(tx) + services.recordTransactions(stx) return stx.tx.outRef(0) } diff --git a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt index 52ed332a7f..af4a80e3b2 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt @@ -2,13 +2,13 @@ package net.corda.node.services.events import co.paralleluniverse.fibers.Suspendable import net.corda.core.concurrent.CordaFuture +import net.corda.core.context.Origin import net.corda.core.contracts.* import net.corda.core.flows.FinalityFlow import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogicRefFactory import net.corda.core.flows.SchedulableFlow import net.corda.core.identity.Party -import net.corda.core.context.Origin import net.corda.core.node.services.VaultService import net.corda.core.node.services.queryBy import net.corda.core.node.services.vault.DEFAULT_PAGE_NUM @@ -20,11 +20,10 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode import net.corda.node.services.statemachine.StateMachineManager -import net.corda.testing.chooseIdentity +import net.corda.testing.* import net.corda.testing.contracts.DummyContract -import net.corda.testing.dummyCommand import net.corda.testing.node.MockNetwork -import net.corda.testing.startFlow +import net.corda.testing.node.MockNodeParameters import org.junit.After import org.junit.Assert.* import org.junit.Before @@ -39,9 +38,11 @@ class ScheduledFlowTests { } private lateinit var mockNet: MockNetwork - private lateinit var nodeA: StartedNode - private lateinit var nodeB: StartedNode + private lateinit var aliceNode: StartedNode + private lateinit var bobNode: StartedNode private lateinit var notary: Party + private lateinit var alice: Party + private lateinit var bob: Party data class ScheduledState(val creationTime: Instant, val source: Party, @@ -97,9 +98,11 @@ class ScheduledFlowTests { @Before fun setup() { mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.testing.contracts")) - nodeA = mockNet.createNode() - nodeB = mockNet.createNode() + aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) + bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME)) notary = mockNet.defaultNotaryIdentity + alice = aliceNode.info.singleIdentity() + bob = bobNode.info.singleIdentity() } @After @@ -110,20 +113,20 @@ class ScheduledFlowTests { @Test fun `create and run scheduled flow then wait for result`() { var countScheduledFlows = 0 - nodeA.smm.track().updates.subscribe { + aliceNode.smm.track().updates.subscribe { if (it is StateMachineManager.Change.Add) { val context = it.logic.stateMachine.context if (context.origin is Origin.Scheduled) countScheduledFlows++ } } - nodeA.services.startFlow(InsertInitialStateFlow(nodeB.info.chooseIdentity(), notary)) + aliceNode.services.startFlow(InsertInitialStateFlow(bob, notary)) mockNet.waitQuiescent() - val stateFromA = nodeA.database.transaction { - nodeA.services.vaultService.queryBy().states.single() + val stateFromA = aliceNode.database.transaction { + aliceNode.services.vaultService.queryBy().states.single() } - val stateFromB = nodeB.database.transaction { - nodeB.services.vaultService.queryBy().states.single() + val stateFromB = bobNode.database.transaction { + bobNode.services.vaultService.queryBy().states.single() } assertEquals(1, countScheduledFlows) assertEquals("Must be same copy on both nodes", stateFromA, stateFromB) @@ -135,8 +138,8 @@ class ScheduledFlowTests { val N = 100 val futures = mutableListOf>() for (i in 0 until N) { - futures.add(nodeA.services.startFlow(InsertInitialStateFlow(nodeB.info.chooseIdentity(), notary)).resultFuture) - futures.add(nodeB.services.startFlow(InsertInitialStateFlow(nodeA.info.chooseIdentity(), notary)).resultFuture) + futures.add(aliceNode.services.startFlow(InsertInitialStateFlow(bob, notary)).resultFuture) + futures.add(bobNode.services.startFlow(InsertInitialStateFlow(alice, notary)).resultFuture) } mockNet.waitQuiescent() @@ -144,11 +147,11 @@ class ScheduledFlowTests { futures.forEach { it.getOrThrow() } // Convert the states into maps to make error reporting easier - val statesFromA: List> = nodeA.database.transaction { - queryStatesWithPaging(nodeA.services.vaultService) + val statesFromA: List> = aliceNode.database.transaction { + queryStatesWithPaging(aliceNode.services.vaultService) } - val statesFromB: List> = nodeB.database.transaction { - queryStatesWithPaging(nodeB.services.vaultService) + val statesFromB: List> = bobNode.database.transaction { + queryStatesWithPaging(bobNode.services.vaultService) } assertEquals("Expect all states to be present", 2 * N, statesFromA.count()) statesFromA.forEach { ref -> diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt index 26881dfd23..861742ff2a 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt @@ -1,11 +1,12 @@ package net.corda.node.services.network import net.corda.core.node.services.NetworkMapCache -import net.corda.testing.ALICE -import net.corda.testing.BOB -import net.corda.testing.chooseIdentity +import net.corda.testing.ALICE_NAME +import net.corda.testing.BOB_NAME +import net.corda.testing.DUMMY_NOTARY import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNodeParameters +import net.corda.testing.singleIdentity import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Test @@ -23,26 +24,29 @@ class NetworkMapCacheTest { @Test fun `key collision`() { val entropy = BigInteger.valueOf(24012017L) - val aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE.name, entropyRoot = entropy)) + val aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME, entropyRoot = entropy)) + val alice = aliceNode.info.singleIdentity() // Node A currently knows only about itself, so this returns node A - assertEquals(aliceNode.services.networkMapCache.getNodesByLegalIdentityKey(aliceNode.info.chooseIdentity().owningKey).singleOrNull(), aliceNode.info) - val bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB.name, entropyRoot = entropy)) - assertEquals(aliceNode.info.chooseIdentity(), bobNode.info.chooseIdentity()) + assertEquals(aliceNode.services.networkMapCache.getNodesByLegalIdentityKey(alice.owningKey).singleOrNull(), aliceNode.info) + val bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME, entropyRoot = entropy)) + val bob = bobNode.info.singleIdentity() + assertEquals(alice, bob) aliceNode.services.networkMapCache.addNode(bobNode.info) // The details of node B write over those for node A - assertEquals(aliceNode.services.networkMapCache.getNodesByLegalIdentityKey(aliceNode.info.chooseIdentity().owningKey).singleOrNull(), bobNode.info) + assertEquals(aliceNode.services.networkMapCache.getNodesByLegalIdentityKey(alice.owningKey).singleOrNull(), bobNode.info) } @Test fun `getNodeByLegalIdentity`() { - val aliceNode = mockNet.createPartyNode(ALICE.name) - val bobNode = mockNet.createPartyNode(BOB.name) + val aliceNode = mockNet.createPartyNode(ALICE_NAME) + val alice = aliceNode.info.singleIdentity() + val bobNode = mockNet.createPartyNode(BOB_NAME) val bobCache: NetworkMapCache = bobNode.services.networkMapCache val expected = aliceNode.info - val actual = bobNode.database.transaction { bobCache.getNodeByLegalIdentity(aliceNode.info.chooseIdentity()) } + val actual = bobNode.database.transaction { bobCache.getNodeByLegalIdentity(alice) } assertEquals(expected, actual) // TODO: Should have a test case with anonymous lookup @@ -50,27 +54,27 @@ class NetworkMapCacheTest { @Test fun `getPeerByLegalName`() { - val aliceNode = mockNet.createPartyNode(ALICE.name) - val bobNode = mockNet.createPartyNode(BOB.name) + val aliceNode = mockNet.createPartyNode(ALICE_NAME) + val bobNode = mockNet.createPartyNode(BOB_NAME) val bobCache: NetworkMapCache = bobNode.services.networkMapCache - val expected = aliceNode.info.legalIdentities.single() + val expected = aliceNode.info.singleIdentity() - val actual = bobNode.database.transaction { bobCache.getPeerByLegalName(ALICE.name) } + val actual = bobNode.database.transaction { bobCache.getPeerByLegalName(ALICE_NAME) } assertEquals(expected, actual) } @Test fun `remove node from cache`() { - val aliceNode = mockNet.createPartyNode(ALICE.name) - val bobNode = mockNet.createPartyNode(BOB.name) - val bobLegalIdentity = bobNode.info.chooseIdentity() - val alice = aliceNode.info.chooseIdentity() + val aliceNode = mockNet.createPartyNode(ALICE_NAME) + val bobNode = mockNet.createPartyNode(BOB_NAME) + val bob = bobNode.info.singleIdentity() + val alice = aliceNode.info.singleIdentity() val bobCache = bobNode.services.networkMapCache bobNode.database.transaction { assertThat(bobCache.getNodeByLegalIdentity(alice) != null) bobCache.removeNode(aliceNode.info) assertThat(bobCache.getNodeByLegalIdentity(alice) == null) - assertThat(bobCache.getNodeByLegalIdentity(bobLegalIdentity) != null) + assertThat(bobCache.getNodeByLegalIdentity(bob) != null) assertThat(bobCache.getNodeByLegalName(alice.name) == null) } } diff --git a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt index ff57f2d363..12689c3b30 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt @@ -9,6 +9,7 @@ import net.corda.core.crypto.* import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party +import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.packageName import net.corda.core.node.StatesToRecord import net.corda.core.node.services.StatesNotAvailableException @@ -59,11 +60,12 @@ class NodeVaultServiceTest { @Rule @JvmField val testSerialization = SerializationEnvironmentRule() - lateinit var services: MockServices + private lateinit var services: MockServices + private lateinit var identity: PartyAndCertificate private lateinit var issuerServices: MockServices private lateinit var bocServices: MockServices - val vaultService get() = services.vaultService as NodeVaultService - lateinit var database: CordaPersistence + private val vaultService get() = services.vaultService as NodeVaultService + private lateinit var database: CordaPersistence @Before fun setUp() { @@ -71,6 +73,8 @@ class NodeVaultServiceTest { val databaseAndServices = MockServices.makeTestDatabaseAndMockServices(cordappPackages = cordappPackages) database = databaseAndServices.first services = databaseAndServices.second + // This is safe because MockServices only ever have a single identity + identity = services.myInfo.singleIdentityAndCert() issuerServices = MockServices(cordappPackages, DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY) bocServices = MockServices(cordappPackages, BOC_NAME, BOC_KEY) services.identityService.verifyAndRegisterIdentity(DUMMY_CASH_ISSUER_IDENTITY) @@ -456,7 +460,7 @@ class NodeVaultServiceTest { fun addNoteToTransaction() { val megaCorpServices = MockServices(cordappPackages, MEGA_CORP.name, MEGA_CORP_KEY) database.transaction { - val freshKey = services.myInfo.chooseIdentity().owningKey + val freshKey = identity.owningKey // Issue a txn to Send us some Money val usefulBuilder = TransactionBuilder(null).apply { @@ -488,11 +492,11 @@ class NodeVaultServiceTest { fun `is ownable state relevant`() { val service = vaultService val amount = Amount(1000, Issued(BOC.ref(1), GBP)) - val wellKnownCash = Cash.State(amount, services.myInfo.chooseIdentity()) + val wellKnownCash = Cash.State(amount, identity.party) val myKeys = services.keyManagementService.filterMyKeys(listOf(wellKnownCash.owner.owningKey)) assertTrue { service.isRelevant(wellKnownCash, myKeys.toSet()) } - val anonymousIdentity = services.keyManagementService.freshKeyAndCert(services.myInfo.chooseIdentityAndCert(), false) + val anonymousIdentity = services.keyManagementService.freshKeyAndCert(identity, false) val anonymousCash = Cash.State(amount, anonymousIdentity.party) val anonymousKeys = services.keyManagementService.filterMyKeys(listOf(anonymousCash.owner.owningKey)) assertTrue { service.isRelevant(anonymousCash, anonymousKeys.toSet()) } @@ -508,6 +512,7 @@ class NodeVaultServiceTest { @Test fun `correct updates are generated for general transactions`() { val service = vaultService + val notary = identity.party val vaultSubscriber = TestSubscriber>().apply { service.updates.subscribe(this) } @@ -518,30 +523,30 @@ class NodeVaultServiceTest { val amount = Amount(1000, Issued(BOC.ref(1), GBP)) // Issue then move some cash - val builder = TransactionBuilder(identity.party).apply { + val issueBuilder = TransactionBuilder(notary).apply { Cash().generateIssue(this, amount, anonymousIdentity.party.anonymise(), identity.party) } - val issueTx = builder.toWireTransaction(bocServices) + val issueTx = issueBuilder.toWireTransaction(bocServices) val cashState = StateAndRef(issueTx.outputs.single(), StateRef(issueTx.id, 0)) // ensure transaction contract state is persisted in DBStorage - val signedIssuedTx = services.signInitialTransaction(builder) + val signedIssuedTx = services.signInitialTransaction(issueBuilder) services.validatedTransactions.addTransaction(signedIssuedTx) database.transaction { service.notify(StatesToRecord.ONLY_RELEVANT, issueTx) } val expectedIssueUpdate = Vault.Update(emptySet(), setOf(cashState), null) database.transaction { - val builder = TransactionBuilder(services.myInfo.chooseIdentity()).apply { + val moveBuilder = TransactionBuilder(notary).apply { Cash.generateSpend(services, this, Amount(1000, GBP), thirdPartyIdentity) } - val moveTx = builder.toWireTransaction(services) + val moveTx = moveBuilder.toWireTransaction(services) service.notify(StatesToRecord.ONLY_RELEVANT, moveTx) } val expectedMoveUpdate = Vault.Update(setOf(cashState), emptySet(), null) // ensure transaction contract state is persisted in DBStorage - val signedMoveTx = services.signInitialTransaction(builder) + val signedMoveTx = services.signInitialTransaction(issueBuilder) services.validatedTransactions.addTransaction(signedMoveTx) val observedUpdates = vaultSubscriber.onNextEvents @@ -551,7 +556,7 @@ class NodeVaultServiceTest { @Test fun `correct updates are generated when changing notaries`() { val service = vaultService - val notary = services.myInfo.chooseIdentity() + val notary = identity.party val vaultSubscriber = TestSubscriber>().apply { service.updates.subscribe(this) diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt index 1d95d8bf7d..aef17cd726 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt @@ -6,6 +6,7 @@ import net.corda.core.contracts.LinearState import net.corda.core.contracts.UniqueIdentifier import net.corda.core.crypto.generateKeyPair import net.corda.core.identity.AnonymousParty +import net.corda.core.identity.Party import net.corda.core.internal.concurrent.fork import net.corda.core.internal.concurrent.transpose import net.corda.core.internal.packageName @@ -53,7 +54,8 @@ class VaultWithCashTest { lateinit var issuerServices: MockServices val vaultService: VaultService get() = services.vaultService lateinit var database: CordaPersistence - lateinit var notaryServices: MockServices + private lateinit var notaryServices: MockServices + private lateinit var notary: Party @Before fun setUp() { @@ -63,6 +65,7 @@ class VaultWithCashTest { services = databaseAndServices.second issuerServices = MockServices(cordappPackages, DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY, MEGA_CORP_KEY) notaryServices = MockServices(cordappPackages, DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) + notary = notaryServices.myInfo.legalIdentitiesAndCerts.single().party } @After @@ -237,10 +240,10 @@ class VaultWithCashTest { val linearId = UniqueIdentifier() // Issue a linear state - val dummyIssueBuilder = TransactionBuilder(notary = DUMMY_NOTARY).apply { + val dummyIssueBuilder = TransactionBuilder(notary = notary).apply { addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshIdentity)), DUMMY_LINEAR_CONTRACT_PROGRAM_ID) addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshIdentity)), DUMMY_LINEAR_CONTRACT_PROGRAM_ID) - addCommand(dummyCommand(notaryServices.myInfo.chooseIdentity().owningKey)) + addCommand(dummyCommand(notary!!.owningKey)) } val dummyIssue = notaryServices.signInitialTransaction(dummyIssueBuilder) @@ -261,7 +264,7 @@ class VaultWithCashTest { // Issue a linear state val dummyIssueBuilder = TransactionBuilder(notary = DUMMY_NOTARY) .addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshIdentity)), DUMMY_LINEAR_CONTRACT_PROGRAM_ID) - .addCommand(dummyCommand(notaryServices.myInfo.chooseIdentity().owningKey)) + .addCommand(dummyCommand(notary.owningKey)) val dummyIssuePtx = notaryServices.signInitialTransaction(dummyIssueBuilder) val dummyIssue = services.addSignature(dummyIssuePtx) @@ -277,7 +280,7 @@ class VaultWithCashTest { val dummyMoveBuilder = TransactionBuilder(notary = DUMMY_NOTARY) .addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshIdentity)), DUMMY_LINEAR_CONTRACT_PROGRAM_ID) .addInputState(dummyIssue.tx.outRef(0)) - .addCommand(dummyCommand(notaryServices.myInfo.chooseIdentity().owningKey)) + .addCommand(dummyCommand(notary.owningKey)) val dummyMove = notaryServices.signInitialTransaction(dummyMoveBuilder) @@ -348,12 +351,12 @@ class VaultWithCashTest { linearStates.forEach { println(it.state.data.linearId) } // Create a txn consuming different contract types - val dummyMoveBuilder = TransactionBuilder(notary = DUMMY_NOTARY).apply { + val dummyMoveBuilder = TransactionBuilder(notary = notary).apply { addOutputState(DummyLinearContract.State(participants = listOf(freshIdentity)), DUMMY_LINEAR_CONTRACT_PROGRAM_ID) addOutputState(DummyDealContract.State(ref = "999", participants = listOf(freshIdentity)), DUMMY_DEAL_PROGRAM_ID) addInputState(linearStates.first()) addInputState(deals.first()) - addCommand(dummyCommand(notaryServices.myInfo.chooseIdentity().owningKey)) + addCommand(dummyCommand(notary!!.owningKey)) } val dummyMove = notaryServices.signInitialTransaction(dummyMoveBuilder) diff --git a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt index 19953b63fd..a5176e80fd 100644 --- a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt +++ b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt @@ -19,6 +19,7 @@ import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase import net.corda.testing.* import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties @@ -51,13 +52,15 @@ class NodeInterestRatesTest { private val DUMMY_CASH_ISSUER_KEY = generateKeyPair() private val DUMMY_CASH_ISSUER = Party(CordaX500Name(organisation = "Cash issuer", locality = "London", country = "GB"), DUMMY_CASH_ISSUER_KEY.public) private val services = MockServices(listOf("net.corda.finance.contracts.asset"), DUMMY_CASH_ISSUER.name, DUMMY_CASH_ISSUER_KEY, MEGA_CORP_KEY) + // This is safe because MockServices only ever have a single identity + private val identity = services.myInfo.singleIdentity() private lateinit var oracle: NodeInterestRates.Oracle private lateinit var database: CordaPersistence private fun fixCmdFilter(elem: Any): Boolean { return when (elem) { - is Command<*> -> services.myInfo.chooseIdentity().owningKey in elem.signers && elem.value is Fix + is Command<*> -> identity.owningKey in elem.signers && elem.value is Fix else -> false } } @@ -152,7 +155,7 @@ class NodeInterestRatesTest { database.transaction { val tx = makePartialTX() val fix = oracle.query(listOf(NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M"))).first() - tx.addCommand(fix, services.myInfo.chooseIdentity().owningKey) + tx.addCommand(fix, identity.owningKey) // Sign successfully. val wtx = tx.toWireTransaction(services) val ftx = wtx.buildFilteredTransaction(Predicate { fixCmdFilter(it) }) @@ -167,7 +170,7 @@ class NodeInterestRatesTest { val tx = makePartialTX() val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M") val badFix = Fix(fixOf, BigDecimal("0.6789")) - tx.addCommand(badFix, services.myInfo.chooseIdentity().owningKey) + tx.addCommand(badFix, identity.owningKey) val wtx = tx.toWireTransaction(services) val ftx = wtx.buildFilteredTransaction(Predicate { fixCmdFilter(it) }) val e1 = assertFailsWith { oracle.sign(ftx) } @@ -182,12 +185,12 @@ class NodeInterestRatesTest { val fix = oracle.query(listOf(NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M"))).first() fun filtering(elem: Any): Boolean { return when (elem) { - is Command<*> -> services.myInfo.chooseIdentity().owningKey in elem.signers && elem.value is Fix + is Command<*> -> identity.owningKey in elem.signers && elem.value is Fix is TransactionState -> true else -> false } } - tx.addCommand(fix, services.myInfo.chooseIdentity().owningKey) + tx.addCommand(fix, identity.owningKey) val wtx = tx.toWireTransaction(services) val ftx = wtx.buildFilteredTransaction(Predicate(::filtering)) assertFailsWith { oracle.sign(ftx) } @@ -205,17 +208,18 @@ class NodeInterestRatesTest { @Test fun `network tearoff`() = withoutTestSerialization { val mockNet = MockNetwork(cordappPackages = listOf("net.corda.finance.contracts", "net.corda.irs")) - val aliceNode = mockNet.createPartyNode(ALICE.name) - val oracleNode = mockNet.createNode().apply { + val aliceNode = mockNet.createPartyNode(ALICE_NAME) + val oracleNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME)).apply { internals.registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java) internals.registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java) database.transaction { services.cordaService(NodeInterestRates.Oracle::class.java).knownFixes = TEST_DATA } } + val oracle = oracleNode.services.myInfo.singleIdentity() val tx = makePartialTX() val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M") - val flow = FilteredRatesFlow(tx, oracleNode.info.chooseIdentity(), fixOf, BigDecimal("0.675"), BigDecimal("0.1")) + val flow = FilteredRatesFlow(tx, oracle, fixOf, BigDecimal("0.675"), BigDecimal("0.1")) LogHelper.setLevel("rates") mockNet.runNetwork() val future = aliceNode.services.startFlow(flow).resultFuture diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 5f7b04135a..a959fd87ec 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -8,6 +8,7 @@ import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party +import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.createDirectories import net.corda.core.internal.createDirectory import net.corda.core.internal.uncheckedCast @@ -163,6 +164,14 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete return defaultNotaryNode.info.legalIdentities[1] // TODO Resolve once network parameters is merged back in } + /** + * Return the identity of the default notary node. + * @see defaultNotaryNode + */ + val defaultNotaryIdentityAndCert: PartyAndCertificate get() { + return defaultNotaryNode.info.legalIdentitiesAndCerts.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities") + } + /** * Because this executor is shared, we need to be careful about nodes shutting it down. */ From f5c9fd8f4442d43032755f9f90a2b3a150fc461c Mon Sep 17 00:00:00 2001 From: josecoll Date: Fri, 17 Nov 2017 14:18:16 +0000 Subject: [PATCH 07/31] Cross provider Issuer Reference database storage (#2032) * consistent storage of Issuer Reference using `ByteArray` Kotlin type in Schema definition and a custom Hibernate Type to map this to a VARBINARY database type. Creation of a new Issued type now also validates maximum size permissible (512). --- core/build.gradle | 3 +++ .../kotlin/net/corda/core/contracts/Structures.kt | 8 ++++++++ .../kotlin/net/corda/core/crypto/SecureHash.kt | 2 ++ .../kotlin/net/corda/core/schemas/CommonSchema.kt | 13 +++++++------ .../asset/cash/selection/CashSelectionH2Impl.kt | 6 ++++-- .../cash/selection/CashSelectionPostgreSQLImpl.kt | 4 ++-- .../net/corda/finance/schemas/CashSchemaV1.kt | 5 ++++- .../finance/schemas/CommercialPaperSchemaV1.kt | 5 ++++- .../contracts/asset/DummyFungibleContract.kt | 2 +- .../corda/finance/schemas/SampleCashSchemaV1.kt | 5 ++++- .../corda/finance/schemas/SampleCashSchemaV2.kt | 5 +++-- .../corda/finance/schemas/SampleCashSchemaV3.kt | 5 ++++- .../schemas/SampleCommercialPaperSchemaV1.kt | 5 ++++- .../schemas/SampleCommercialPaperSchemaV2.kt | 10 +++++++--- .../services/persistence/HibernateConfiguration.kt | 14 ++++++++++++++ .../net/corda/node/services/vault/VaultSchema.kt | 5 ++++- 16 files changed, 75 insertions(+), 22 deletions(-) diff --git a/core/build.gradle b/core/build.gradle index 34d77bf8fc..ab67d23248 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -117,6 +117,9 @@ dependencies { // JPA 2.1 annotations. compile "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final" + + // required to use @Type annotation + compile "org.hibernate:hibernate-core:$hibernate_version" } // TODO Consider moving it to quasar-utils in the future (introduced with PR-1388) diff --git a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt index d0359f0ded..cc1abb0d56 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt @@ -45,9 +45,17 @@ interface NamedByHash { */ @CordaSerializable data class Issued(val issuer: PartyAndReference, val product: P) { + init { + require(issuer.reference.bytes.size <= MAX_ISSUER_REF_SIZE) { "Maximum issuer reference size is $MAX_ISSUER_REF_SIZE." } + } override fun toString() = "$product issued by $issuer" } +/** + * The maximum permissible size of an issuer reference. + */ +const val MAX_ISSUER_REF_SIZE = 512 + /** * Strips the issuer and returns an [Amount] of the raw token directly. This is useful when you are mixing code that * cares about specific issuers with code that will accept any, or which is imposing issuer constraints via some diff --git a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt index 6555ac6af7..9a2348c48f 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt @@ -103,3 +103,5 @@ fun ByteArray.sha256(): SecureHash.SHA256 = SecureHash.sha256(this) * Compute the SHA-256 hash for the contents of the [OpaqueBytes]. */ fun OpaqueBytes.sha256(): SecureHash.SHA256 = SecureHash.sha256(this.bytes) + + diff --git a/core/src/main/kotlin/net/corda/core/schemas/CommonSchema.kt b/core/src/main/kotlin/net/corda/core/schemas/CommonSchema.kt index 1d5559d45d..a630a7b8e3 100644 --- a/core/src/main/kotlin/net/corda/core/schemas/CommonSchema.kt +++ b/core/src/main/kotlin/net/corda/core/schemas/CommonSchema.kt @@ -1,12 +1,12 @@ package net.corda.core.schemas -import net.corda.core.contracts.ContractState -import net.corda.core.contracts.FungibleAsset -import net.corda.core.contracts.OwnableState -import net.corda.core.contracts.UniqueIdentifier +import net.corda.core.contracts.* import net.corda.core.identity.AbstractParty +import org.hibernate.annotations.Type import java.util.* -import javax.persistence.* +import javax.persistence.Column +import javax.persistence.ElementCollection +import javax.persistence.MappedSuperclass /** * JPA representation of the common schema entities @@ -74,7 +74,8 @@ object CommonSchemaV1 : MappedSchema(schemaFamily = CommonSchema.javaClass, vers @Column(name = "issuer_name") var issuer: AbstractParty, - @Column(name = "issuer_reference") + @Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE) + @Type(type = "corda-wrapper-binary") var issuerRef: ByteArray ) : PersistentState() } \ No newline at end of file diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt index ef46782996..d8be0af698 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt @@ -4,7 +4,9 @@ import net.corda.core.contracts.Amount import net.corda.core.crypto.toStringShort import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party -import net.corda.core.utilities.* +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.debug +import net.corda.core.utilities.loggerFor import java.sql.Connection import java.sql.DatabaseMetaData import java.sql.ResultSet @@ -60,7 +62,7 @@ class CashSelectionH2Impl : AbstractCashSelection() { if (onlyFromIssuerParties.isNotEmpty()) psSelectJoin.setObject(++pIndex, onlyFromIssuerParties.map { it.owningKey.toStringShort() as Any}.toTypedArray() ) if (withIssuerRefs.isNotEmpty()) - psSelectJoin.setObject(++pIndex, withIssuerRefs.map { it.bytes.toHexString() as Any }.toTypedArray()) + psSelectJoin.setObject(++pIndex, withIssuerRefs.map { it.bytes as Any }.toTypedArray()) log.debug { psSelectJoin.toString() } return psSelectJoin.executeQuery() diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt index a792c60501..d76fde536d 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt @@ -63,13 +63,13 @@ class CashSelectionPostgreSQLImpl : AbstractCashSelection() { paramOffset += 1 } if (onlyFromIssuerParties.isNotEmpty()) { - val issuerKeys = connection.createArrayOf("VARCHAR", onlyFromIssuerParties.map + val issuerKeys = connection.createArrayOf("BYTEA", onlyFromIssuerParties.map { it.owningKey.toBase58String() }.toTypedArray()) statement.setArray(3 + paramOffset, issuerKeys) paramOffset += 1 } if (withIssuerRefs.isNotEmpty()) { - val issuerRefs = connection.createArrayOf("BYTEA", withIssuerRefs.map + val issuerRefs = connection.createArrayOf("VARCHAR", withIssuerRefs.map { it.bytes }.toTypedArray()) statement.setArray(3 + paramOffset, issuerRefs) paramOffset += 1 diff --git a/finance/src/main/kotlin/net/corda/finance/schemas/CashSchemaV1.kt b/finance/src/main/kotlin/net/corda/finance/schemas/CashSchemaV1.kt index 4c39f010e6..351dcc0c70 100644 --- a/finance/src/main/kotlin/net/corda/finance/schemas/CashSchemaV1.kt +++ b/finance/src/main/kotlin/net/corda/finance/schemas/CashSchemaV1.kt @@ -5,6 +5,8 @@ import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.MAX_HASH_HEX_SIZE +import net.corda.core.contracts.MAX_ISSUER_REF_SIZE +import org.hibernate.annotations.Type import javax.persistence.* /** @@ -36,7 +38,8 @@ object CashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, version @Column(name = "issuer_key_hash", length = MAX_HASH_HEX_SIZE) var issuerPartyHash: String, - @Column(name = "issuer_ref") + @Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE) + @Type(type = "corda-wrapper-binary") var issuerRef: ByteArray ) : PersistentState() } diff --git a/finance/src/main/kotlin/net/corda/finance/schemas/CommercialPaperSchemaV1.kt b/finance/src/main/kotlin/net/corda/finance/schemas/CommercialPaperSchemaV1.kt index 6af1da2ce1..c3a9743115 100644 --- a/finance/src/main/kotlin/net/corda/finance/schemas/CommercialPaperSchemaV1.kt +++ b/finance/src/main/kotlin/net/corda/finance/schemas/CommercialPaperSchemaV1.kt @@ -1,9 +1,11 @@ package net.corda.finance.schemas +import net.corda.core.contracts.MAX_ISSUER_REF_SIZE import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.MAX_HASH_HEX_SIZE +import org.hibernate.annotations.Type import java.time.Instant import javax.persistence.Column import javax.persistence.Entity @@ -48,7 +50,8 @@ object CommercialPaperSchemaV1 : MappedSchema(schemaFamily = CommercialPaperSche @Column(name = "face_value_issuer_key_hash", length = MAX_HASH_HEX_SIZE) var faceValueIssuerPartyHash: String, - @Column(name = "face_value_issuer_ref") + @Column(name = "face_value_issuer_ref", length = MAX_ISSUER_REF_SIZE) + @Type(type = "corda-wrapper-binary") var faceValueIssuerRef: ByteArray ) : PersistentState() } diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/DummyFungibleContract.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/DummyFungibleContract.kt index 62702f215d..62f3b7ad8f 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/DummyFungibleContract.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/DummyFungibleContract.kt @@ -55,7 +55,7 @@ class DummyFungibleContract : OnLedgerAsset SampleCashSchemaV3.PersistentCashState( participants = this.participants.toMutableSet(), diff --git a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV1.kt b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV1.kt index 5acd34fe12..7ac073621e 100644 --- a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV1.kt +++ b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV1.kt @@ -1,8 +1,10 @@ package net.corda.finance.schemas +import net.corda.core.contracts.MAX_ISSUER_REF_SIZE import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.utilities.MAX_HASH_HEX_SIZE +import org.hibernate.annotations.Type import javax.persistence.Column import javax.persistence.Entity import javax.persistence.Index @@ -35,7 +37,8 @@ object SampleCashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, ve @Column(name = "issuer_key_hash", length = MAX_HASH_HEX_SIZE) var issuerPartyHash: String, - @Column(name = "issuer_ref") + @Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE) + @Type(type = "corda-wrapper-binary") var issuerRef: ByteArray ) : PersistentState() } diff --git a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV2.kt b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV2.kt index 95108eddfa..8b548f89ce 100644 --- a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV2.kt +++ b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV2.kt @@ -3,6 +3,7 @@ package net.corda.finance.schemas import net.corda.core.identity.AbstractParty import net.corda.core.schemas.CommonSchemaV1 import net.corda.core.schemas.MappedSchema +import net.corda.core.utilities.OpaqueBytes import javax.persistence.Column import javax.persistence.Entity import javax.persistence.Index @@ -32,6 +33,6 @@ object SampleCashSchemaV2 : MappedSchema(schemaFamily = CashSchema.javaClass, ve @Transient val _issuerParty: AbstractParty, @Transient - val _issuerRef: ByteArray - ) : CommonSchemaV1.FungibleState(_participants.toMutableSet(), _owner, _quantity, _issuerParty, _issuerRef) + val _issuerRef: OpaqueBytes + ) : CommonSchemaV1.FungibleState(_participants.toMutableSet(), _owner, _quantity, _issuerParty, _issuerRef.bytes) } diff --git a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV3.kt b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV3.kt index eaae33410f..0a6c0cc289 100644 --- a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV3.kt +++ b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV3.kt @@ -1,8 +1,10 @@ package net.corda.finance.schemas +import net.corda.core.contracts.MAX_ISSUER_REF_SIZE import net.corda.core.identity.AbstractParty import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState +import org.hibernate.annotations.Type import javax.persistence.Column import javax.persistence.ElementCollection import javax.persistence.Entity @@ -37,7 +39,8 @@ object SampleCashSchemaV3 : MappedSchema(schemaFamily = CashSchema.javaClass, ve @Column(name = "issuer_name") var issuer: AbstractParty, - @Column(name = "issuer_ref") + @Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE) + @Type(type = "corda-wrapper-binary") var issuerRef: ByteArray ) : PersistentState() } diff --git a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV1.kt b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV1.kt index e73983a5e2..f407bf7fc2 100644 --- a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV1.kt +++ b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV1.kt @@ -1,8 +1,10 @@ package net.corda.finance.schemas +import net.corda.core.contracts.MAX_ISSUER_REF_SIZE import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.utilities.MAX_HASH_HEX_SIZE +import org.hibernate.annotations.Type import java.time.Instant import javax.persistence.Column import javax.persistence.Entity @@ -46,7 +48,8 @@ object SampleCommercialPaperSchemaV1 : MappedSchema(schemaFamily = CommercialPap @Column(name = "face_value_issuer_key_hash", length = MAX_HASH_HEX_SIZE) var faceValueIssuerPartyHash: String, - @Column(name = "face_value_issuer_ref") + @Column(name = "face_value_issuer_ref", length = MAX_ISSUER_REF_SIZE) + @Type(type = "corda-wrapper-binary") var faceValueIssuerRef: ByteArray ) : PersistentState() } diff --git a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV2.kt b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV2.kt index d7bb1d5633..2c7882c048 100644 --- a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV2.kt +++ b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV2.kt @@ -1,9 +1,12 @@ package net.corda.finance.schemas +import net.corda.core.contracts.MAX_ISSUER_REF_SIZE import net.corda.core.identity.AbstractParty import net.corda.core.schemas.CommonSchemaV1 import net.corda.core.schemas.MappedSchema import net.corda.core.utilities.MAX_HASH_HEX_SIZE +import net.corda.core.utilities.OpaqueBytes +import org.hibernate.annotations.Type import java.time.Instant import javax.persistence.Column import javax.persistence.Entity @@ -30,7 +33,8 @@ object SampleCommercialPaperSchemaV2 : MappedSchema(schemaFamily = CommercialPap @Column(name = "face_value_issuer_key_hash", length = MAX_HASH_HEX_SIZE) var faceValueIssuerPartyHash: String, - @Column(name = "face_value_issuer_ref") + @Column(name = "face_value_issuer_ref", length = MAX_ISSUER_REF_SIZE) + @Type(type = "corda-wrapper-binary") var faceValueIssuerRef: ByteArray, /** parent attributes */ @@ -44,6 +48,6 @@ object SampleCommercialPaperSchemaV2 : MappedSchema(schemaFamily = CommercialPap @Transient val _issuerParty: AbstractParty, @Transient - val _issuerRef: ByteArray - ) : CommonSchemaV1.FungibleState(_participants.toMutableSet(), _owner, _quantity, _issuerParty, _issuerRef) + val _issuerRef: OpaqueBytes + ) : CommonSchemaV1.FungibleState(_participants.toMutableSet(), _owner, _quantity, _issuerParty, _issuerRef.bytes) } diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt index f5c7b4b5b5..cd6b93de57 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt @@ -3,6 +3,7 @@ package net.corda.node.services.persistence import net.corda.core.internal.castIfPossible import net.corda.core.node.services.IdentityService import net.corda.core.schemas.MappedSchema +import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.loggerFor import net.corda.core.utilities.toHexString import net.corda.node.services.api.SchemaService @@ -21,6 +22,7 @@ import org.hibernate.type.AbstractSingleColumnStandardBasicType import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry import org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor import org.hibernate.type.descriptor.sql.BlobTypeDescriptor +import org.hibernate.type.descriptor.sql.VarbinaryTypeDescriptor import java.sql.Connection import java.util.* import java.util.concurrent.ConcurrentHashMap @@ -83,6 +85,7 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab // Register a tweaked version of `org.hibernate.type.MaterializedBlobType` that truncates logged messages. // to avoid OOM when large blobs might get logged. applyBasicType(CordaMaterializedBlobType, CordaMaterializedBlobType.name) + applyBasicType(CordaWrapperBinaryType, CordaWrapperBinaryType.name) build() } @@ -139,4 +142,15 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab } } } + + // A tweaked version of `org.hibernate.type.WrapperBinaryType` that deals with ByteArray (java primitive byte[] type). + private object CordaWrapperBinaryType : AbstractSingleColumnStandardBasicType(VarbinaryTypeDescriptor.INSTANCE, PrimitiveByteArrayTypeDescriptor.INSTANCE) { + override fun getRegistrationKeys(): Array { + return arrayOf(name, "ByteArray", ByteArray::class.java.name) + } + + override fun getName(): String { + return "corda-wrapper-binary" + } + } } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt b/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt index d98dbb4c0b..e54f2cba23 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt @@ -1,6 +1,7 @@ package net.corda.node.services.vault import net.corda.core.contracts.ContractState +import net.corda.core.contracts.MAX_ISSUER_REF_SIZE import net.corda.core.contracts.UniqueIdentifier import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party @@ -9,6 +10,7 @@ import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.OpaqueBytes +import org.hibernate.annotations.Type import java.io.Serializable import java.time.Instant import java.util.* @@ -131,7 +133,8 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio @Column(name = "issuer_name") var issuer: AbstractParty, - @Column(name = "issuer_reference") + @Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE) + @Type(type = "corda-wrapper-binary") var issuerRef: ByteArray ) : PersistentState() { constructor(_owner: AbstractParty, _quantity: Long, _issuerParty: AbstractParty, _issuerRef: OpaqueBytes, _participants: List) : From 128d5cad0af7fc5595cac3287650663c9c9ac0a3 Mon Sep 17 00:00:00 2001 From: "Molina Atienza, Rose" Date: Thu, 21 Sep 2017 08:21:34 +0100 Subject: [PATCH 08/31] Fixed typo in KotlinUtils.kt seperate -> separate --- core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt index b12ea8353d..dd76afb644 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt @@ -14,7 +14,7 @@ import kotlin.reflect.KProperty // READ ME FIRST: // This is a collection of public utilities useful only for Kotlin code. Think carefully before adding anything here and // make sure it's tested and documented. If you're looking to add a public utility that is also relevant to Java then -// don't put it here but in a seperate file called Utils.kt +// don't put it here but in a separate file called Utils.kt // /** Like the + operator but throws [ArithmeticException] in case of integer overflow. */ From 857e3954390e7f2f3165107d0475545aed39a38d Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Fri, 17 Nov 2017 15:31:59 +0000 Subject: [PATCH 09/31] Change how mock notary identity is extracted (#2075) Change how mock notary identity is extracted, to match reversion in commit 3bb018a5 --- .../src/main/kotlin/net/corda/testing/node/MockNode.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index a959fd87ec..588eed3711 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -169,7 +169,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete * @see defaultNotaryNode */ val defaultNotaryIdentityAndCert: PartyAndCertificate get() { - return defaultNotaryNode.info.legalIdentitiesAndCerts.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities") + return defaultNotaryNode.info.legalIdentitiesAndCerts[1] // TODO Resolve once network parameters is merged back in } /** From 817748c87ed1ad1b8b345c534825c5861f8e1da9 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Fri, 17 Nov 2017 15:35:05 +0000 Subject: [PATCH 10/31] Performance improvement: cache app classloaders (#2060) Cache results of the classpath scanning for cordapps --- .../corda/node/internal/cordapp/CordappLoader.kt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt index 50daaa1d8d..025d0bc94e 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt @@ -16,6 +16,7 @@ import net.corda.core.utilities.loggerFor import net.corda.node.internal.classloading.requireAnnotation import net.corda.node.services.config.NodeConfiguration import net.corda.nodeapi.internal.serialization.DefaultWhitelist +import org.apache.commons.collections4.map.LRUMap import java.io.File import java.io.FileOutputStream import java.lang.reflect.Modifier @@ -67,6 +68,9 @@ class CordappLoader private constructor(private val cordappJarPaths: List, CordappLoader>(1000) + /** * Create a dev mode CordappLoader for test environments that creates and loads cordapps from the classpath * and cordapps directory. This is intended mostly for use by the driver. @@ -77,7 +81,8 @@ class CordappLoader private constructor(private val cordappJarPaths: List): CordappLoader { check(configuration.devMode) { "Package scanning can only occur in dev mode" } - return CordappLoader(getCordappsInDirectory(getCordappsPath(configuration.baseDirectory)) + testPackages.flatMap(this::createScanPackage)) + val paths = getCordappsInDirectory(getCordappsPath(configuration.baseDirectory)) + testPackages.flatMap(this::createScanPackage) + return cordappLoadersCache.computeIfAbsent(paths, { CordappLoader(paths) }) } /** @@ -89,7 +94,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List) - = CordappLoader(testPackages.flatMap(this::createScanPackage)) + = cordappLoadersCache.computeIfAbsent(testPackages, { CordappLoader(testPackages.flatMap(this::createScanPackage)) }) /** * Creates a dev mode CordappLoader intended only to be used in test environments @@ -242,9 +247,12 @@ class CordappLoader private constructor(private val cordappJarPaths: List(1000) private fun scanCordapp(cordappJarPath: RestrictedURL): RestrictedScanResult { logger.info("Scanning CorDapp in $cordappJarPath") - return RestrictedScanResult(FastClasspathScanner().addClassLoader(appClassLoader).overrideClasspath(cordappJarPath.url).scan(), cordappJarPath.qualifiedNamePrefix) + return cachedScanResult.computeIfAbsent(cordappJarPath, { + RestrictedScanResult(FastClasspathScanner().addClassLoader(appClassLoader).overrideClasspath(cordappJarPath.url).scan(), cordappJarPath.qualifiedNamePrefix) + }) } private class FlowTypeHierarchyComparator(val initiatingFlow: Class>) : Comparator>> { From 1f982933775be6d7954517ae165e81854542bee1 Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Fri, 17 Nov 2017 18:13:10 +0000 Subject: [PATCH 11/31] Stabilise NodeStatePersistenceTests (#2079) Pass notary identity into flow in `NodeStatePersistenceTests` rather than resolving it from the network map cache, which avoids a race condition between the flow starting and the notary registration being sent to the cache. --- .../test/node/NodeStatePersistenceTests.kt | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt index c2355f7723..7c7e09073f 100644 --- a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt @@ -40,26 +40,30 @@ class NodeStatePersistenceTests { val user = User("mark", "dadada", setOf(startFlow(), invokeRpc("vaultQuery"))) val message = Message("Hello world!") - driver(isDebug = true, startNodesInProcess = isQuasarAgentSpecified()) { + val stateAndRef: StateAndRef? = driver(isDebug = true, startNodesInProcess = isQuasarAgentSpecified()) { val nodeName = { val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow() val nodeName = nodeHandle.nodeInfo.chooseIdentity().name + // Ensure the notary node has finished starting up, before starting a flow that needs a notary + defaultNotaryNode.getOrThrow() nodeHandle.rpcClientToNode().start(user.username, user.password).use { - it.proxy.startFlow(::SendMessageFlow, message).returnValue.getOrThrow() + it.proxy.startFlow(::SendMessageFlow, message, defaultNotaryIdentity).returnValue.getOrThrow() } nodeHandle.stop() nodeName }() val nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user)).getOrThrow() - nodeHandle.rpcClientToNode().start(user.username, user.password).use { + val result = nodeHandle.rpcClientToNode().start(user.username, user.password).use { val page = it.proxy.vaultQuery(MessageState::class.java) - val stateAndRef = page.states.singleOrNull() - assertNotNull(stateAndRef) - val retrievedMessage = stateAndRef!!.state.data.message - assertEquals(message, retrievedMessage) + page.states.singleOrNull() } + nodeHandle.stop() + result } + assertNotNull(stateAndRef) + val retrievedMessage = stateAndRef!!.state.data.message + assertEquals(message, retrievedMessage) } } @@ -126,7 +130,7 @@ open class MessageContract : Contract { } @StartableByRPC -class SendMessageFlow(private val message: Message) : FlowLogic() { +class SendMessageFlow(private val message: Message, private val notary: Party) : FlowLogic() { companion object { object GENERATING_TRANSACTION : ProgressTracker.Step("Generating transaction based on the message.") object VERIFYING_TRANSACTION : ProgressTracker.Step("Verifying contract constraints.") @@ -142,8 +146,6 @@ class SendMessageFlow(private val message: Message) : FlowLogic Date: Fri, 17 Nov 2017 18:13:35 +0000 Subject: [PATCH 12/31] CORDA-759: Enforce key checks on identity de-anonymisation (#1993) Previously when de-anonymising a Party instance, the name of the Party was used rather than the key, meaning a Party could be constructed with a random nonsense key and any name, and be treated as corresponding to the well known identity. This is not a security hole in itself as in any real scenario a party shouldn't be trusted without having been registered, it creates a significant risk of a security hole depending on how trusted the anonymous identity is, and the returned identity is considered. --- docs/source/changelog.rst | 3 + .../finance/contracts/asset/CashTests.kt | 63 ++++++----- .../identity/InMemoryIdentityService.kt | 12 +- .../identity/PersistentIdentityService.kt | 12 +- ...bstractPartyToX500NameAsStringConverter.kt | 2 +- .../services/vault/VaultQueryJavaTests.java | 70 ++++++++---- .../identity/InMemoryIdentityServiceTests.kt | 18 ++- .../PersistentIdentityServiceTests.kt | 20 +++- .../persistence/HibernateConfigurationTest.kt | 104 ++++++++++-------- .../services/vault/NodeVaultServiceTest.kt | 16 ++- .../node/services/vault/VaultQueryTests.kt | 46 +++++--- .../node/services/vault/VaultWithCashTest.kt | 4 +- .../net/corda/testing/node/MockServices.kt | 3 +- .../kotlin/net/corda/testing/CoreTestUtils.kt | 3 - .../corda/testing/contracts/VaultFiller.kt | 26 ++++- 15 files changed, 250 insertions(+), 152 deletions(-) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index b8a7353ef3..11b2632071 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -11,6 +11,9 @@ UNRELEASED * ``CordaRPCOps`` implementation now checks permissions for any function invocation, rather than just when starting flows. +* ``wellKnownPartyFromAnonymous()`` now always resolve the key to a ``Party``, then the party to the well known party. + Previously if it was passed a ``Party`` it would use its name as-is without verifying the key matched that name. + * ``OpaqueBytes.bytes`` now returns a clone of its underlying ``ByteArray``, and has been redeclared as ``final``. This is a minor change to the public API, but is required to ensure that classes like ``SecureHash`` are immutable. diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt index 6884abbacf..db35907d50 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt @@ -54,8 +54,7 @@ class CashTests { lateinit var database: CordaPersistence private lateinit var vaultStatesUnconsumed: List> - private lateinit var OUR_IDENTITY_1: AbstractParty - private lateinit var OUR_IDENTITY_AND_CERT: PartyAndCertificate + private lateinit var ourIdentity: AbstractParty private lateinit var miniCorpAnonymised: AnonymousParty private val CHARLIE_ANONYMISED = CHARLIE_IDENTITY.party.anonymise() @@ -65,28 +64,33 @@ class CashTests { fun setUp() { LogHelper.setLevel(NodeVaultService::class) megaCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), MEGA_CORP.name, MEGA_CORP_KEY) + miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), MINI_CORP.name, MINI_CORP_KEY) + val notaryServices = MockServices(listOf("net.corda.finance.contracts.asset"), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) val databaseAndServices = makeTestDatabaseAndMockServices( cordappPackages = listOf("net.corda.finance.contracts.asset"), initialIdentityName = CordaX500Name(organisation = "Me", locality = "London", country = "GB"), keys = listOf(generateKeyPair())) database = databaseAndServices.first - miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), MINI_CORP.name, MINI_CORP_KEY) ourServices = databaseAndServices.second - OUR_IDENTITY_AND_CERT = ourServices.myInfo.singleIdentityAndCert() - OUR_IDENTITY_1 = ourServices.myInfo.singleIdentity() + + // Set up and register identities + ourIdentity = ourServices.myInfo.singleIdentity() + miniCorpAnonymised = miniCorpServices.myInfo.singleIdentityAndCert().party.anonymise() + (miniCorpServices.myInfo.legalIdentitiesAndCerts + megaCorpServices.myInfo.legalIdentitiesAndCerts + notaryServices.myInfo.legalIdentitiesAndCerts).forEach { identity -> + ourServices.identityService.verifyAndRegisterIdentity(identity) + } // Create some cash. Any attempt to spend >$500 will require multiple issuers to be involved. - database.transaction { + database.transaction { ourServices.fillWithSomeTestCash(howMuch = 100.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1, - ownedBy = OUR_IDENTITY_1, issuedBy = MEGA_CORP.ref(1), issuerServices = megaCorpServices) + owner = ourIdentity, issuedBy = MEGA_CORP.ref(1), issuerServices = megaCorpServices) ourServices.fillWithSomeTestCash(howMuch = 400.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1, - ownedBy = OUR_IDENTITY_1, issuedBy = MEGA_CORP.ref(1), issuerServices = megaCorpServices) + owner = ourIdentity, issuedBy = MEGA_CORP.ref(1), issuerServices = megaCorpServices) ourServices.fillWithSomeTestCash(howMuch = 80.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1, - ownedBy = OUR_IDENTITY_1, issuedBy = MINI_CORP.ref(1), issuerServices = miniCorpServices) + owner = ourIdentity, issuedBy = MINI_CORP.ref(1), issuerServices = miniCorpServices) ourServices.fillWithSomeTestCash(howMuch = 80.SWISS_FRANCS, atLeastThisManyStates = 1, atMostThisManyStates = 1, - ownedBy = OUR_IDENTITY_1, issuedBy = MINI_CORP.ref(1), issuerServices = miniCorpServices) + owner = ourIdentity, issuedBy = MINI_CORP.ref(1), issuerServices = miniCorpServices) } - miniCorpAnonymised = miniCorpServices.myInfo.singleIdentityAndCert().party.anonymise() database.transaction { vaultStatesUnconsumed = ourServices.vaultService.queryBy().states } @@ -495,7 +499,7 @@ class CashTests { private fun makeCash(amount: Amount, issuer: AbstractParty, depositRef: Byte = 1) = StateAndRef( - TransactionState(Cash.State(amount `issued by` issuer.ref(depositRef), OUR_IDENTITY_1), Cash.PROGRAM_ID, DUMMY_NOTARY), + TransactionState(Cash.State(amount `issued by` issuer.ref(depositRef), ourIdentity), Cash.PROGRAM_ID, DUMMY_NOTARY), StateRef(SecureHash.randomSHA256(), Random().nextInt(32)) ) @@ -509,12 +513,14 @@ class CashTests { return tx.toWireTransaction(serviceHub) } - private fun makeSpend(amount: Amount, dest: AbstractParty): WireTransaction { + private fun makeSpend(services: ServiceHub, amount: Amount, dest: AbstractParty): WireTransaction { + val ourIdentity = services.myInfo.singleIdentityAndCert() + val changeIdentity = services.keyManagementService.freshKeyAndCert(ourIdentity, false) val tx = TransactionBuilder(DUMMY_NOTARY) database.transaction { - Cash.generateSpend(ourServices, tx, amount, OUR_IDENTITY_AND_CERT, dest) + Cash.generateSpend(services, tx, amount, changeIdentity, dest) } - return tx.toWireTransaction(miniCorpServices) + return tx.toWireTransaction(services) } /** @@ -588,7 +594,7 @@ class CashTests { fun generateExitWithEmptyVault() { assertFailsWith { val tx = TransactionBuilder(DUMMY_NOTARY) - Cash().generateExit(tx, Amount(100, Issued(CHARLIE.ref(1), GBP)), emptyList(), OUR_IDENTITY_1) + Cash().generateExit(tx, Amount(100, Issued(CHARLIE.ref(1), GBP)), emptyList(), ourIdentity) } } @@ -596,22 +602,23 @@ class CashTests { fun generateSimpleDirectSpend() { val wtx = database.transaction { - makeSpend(100.DOLLARS, miniCorpAnonymised) + makeSpend(ourServices, 100.DOLLARS, miniCorpAnonymised) } database.transaction { val vaultState = vaultStatesUnconsumed.elementAt(0) assertEquals(vaultState.ref, wtx.inputs[0]) assertEquals(vaultState.state.data.copy(owner = miniCorpAnonymised), wtx.getOutput(0)) - assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) + assertEquals(ourIdentity.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) } } @Test fun generateSimpleSpendWithParties() { + val changeIdentity = ourServices.keyManagementService.freshKeyAndCert(ourServices.myInfo.singleIdentityAndCert(), false) database.transaction { val tx = TransactionBuilder(DUMMY_NOTARY) - Cash.generateSpend(ourServices, tx, 80.DOLLARS, OUR_IDENTITY_AND_CERT, ALICE, setOf(MINI_CORP)) + Cash.generateSpend(ourServices, tx, 80.DOLLARS, changeIdentity, ALICE, setOf(MINI_CORP)) assertEquals(vaultStatesUnconsumed.elementAt(2).ref, tx.inputStates()[0]) } @@ -621,7 +628,7 @@ class CashTests { fun generateSimpleSpendWithChange() { val wtx = database.transaction { - makeSpend(10.DOLLARS, miniCorpAnonymised) + makeSpend(ourServices, 10.DOLLARS, miniCorpAnonymised) } database.transaction { val vaultState = vaultStatesUnconsumed.elementAt(0) @@ -638,7 +645,7 @@ class CashTests { assertEquals(vaultState.ref, wtx.inputs[0]) assertEquals(vaultState.state.data.copy(owner = miniCorpAnonymised, amount = 10.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data) assertEquals(vaultState.state.data.copy(amount = changeAmount, owner = changeOwner), wtx.outputs[1].data) - assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) + assertEquals(ourIdentity.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) } } @@ -646,7 +653,7 @@ class CashTests { fun generateSpendWithTwoInputs() { val wtx = database.transaction { - makeSpend(500.DOLLARS, miniCorpAnonymised) + makeSpend(ourServices, 500.DOLLARS, miniCorpAnonymised) } database.transaction { val vaultState0 = vaultStatesUnconsumed.elementAt(0) @@ -654,7 +661,7 @@ class CashTests { assertEquals(vaultState0.ref, wtx.inputs[0]) assertEquals(vaultState1.ref, wtx.inputs[1]) assertEquals(vaultState0.state.data.copy(owner = miniCorpAnonymised, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.getOutput(0)) - assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) + assertEquals(ourIdentity.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) } } @@ -662,7 +669,7 @@ class CashTests { fun generateSpendMixedDeposits() { val wtx = database.transaction { - val wtx = makeSpend(580.DOLLARS, miniCorpAnonymised) + val wtx = makeSpend(ourServices, 580.DOLLARS, miniCorpAnonymised) assertEquals(3, wtx.inputs.size) wtx } @@ -675,7 +682,7 @@ class CashTests { assertEquals(vaultState2.ref, wtx.inputs[2]) assertEquals(vaultState0.state.data.copy(owner = miniCorpAnonymised, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[1].data) assertEquals(vaultState2.state.data.copy(owner = miniCorpAnonymised), wtx.outputs[0].data) - assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) + assertEquals(ourIdentity.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) } } @@ -684,12 +691,12 @@ class CashTests { database.transaction { val e: InsufficientBalanceException = assertFailsWith("balance") { - makeSpend(1000.DOLLARS, miniCorpAnonymised) + makeSpend(ourServices, 1000.DOLLARS, miniCorpAnonymised) } assertEquals((1000 - 580).DOLLARS, e.amountMissing) assertFailsWith(InsufficientBalanceException::class) { - makeSpend(81.SWISS_FRANCS, miniCorpAnonymised) + makeSpend(ourServices, 81.SWISS_FRANCS, miniCorpAnonymised) } } } @@ -821,7 +828,7 @@ class CashTests { fun multiSpend() { val tx = TransactionBuilder(DUMMY_NOTARY) database.transaction { - val changeIdentity = ourServices.keyManagementService.freshKeyAndCert(OUR_IDENTITY_AND_CERT, false) + val changeIdentity = ourServices.keyManagementService.freshKeyAndCert(ourServices.myInfo.singleIdentityAndCert(), false) val payments = listOf( PartyAndAmount(miniCorpAnonymised, 400.DOLLARS), PartyAndAmount(CHARLIE_ANONYMISED, 150.DOLLARS) diff --git a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt index dcbe701f4b..679e5251c4 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt @@ -99,14 +99,14 @@ class InMemoryIdentityService(identities: Iterable = emptyS override fun partyFromKey(key: PublicKey): Party? = keyToParties[key]?.party override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = principalToParties[name]?.party override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? { - // Expand the anonymous party to a full party (i.e. has a name) if possible - val candidate = party as? Party ?: keyToParties[party.owningKey]?.party + // The original version of this would return the party as-is if it was a Party (rather than AnonymousParty), + // however that means that we don't verify that we know who owns the key. As such as now enforce turning the key + // into a party, and from there figure out the well known party. + val candidate = partyFromKey(party.owningKey) // TODO: This should be done via the network map cache, which is the authoritative source of well known identities - // Look up the well known identity for that name return if (candidate != null) { - // If we have a well known identity by that name, use it in preference to the candidate. Otherwise default - // back to the candidate. - principalToParties[candidate.name]?.party ?: candidate + require(party.nameOrNull() == null || party.nameOrNull() == candidate.name) { "Candidate party ${candidate} does not match expected ${party}" } + wellKnownPartyFromX500Name(candidate.name) } else { null } diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index 2309560d65..dba40da607 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -164,15 +164,13 @@ class PersistentIdentityService(identities: Iterable = empt override fun partyFromKey(key: PublicKey): Party? = certificateFromKey(key)?.party override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = certificateFromCordaX500Name(name)?.party override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? { - // Expand the anonymous party to a full party (i.e. has a name) if possible - val candidate = party as? Party ?: partyFromKey(party.owningKey) + // The original version of this would return the party as-is if it was a Party (rather than AnonymousParty), + // however that means that we don't verify that we know who owns the key. As such as now enforce turning the key + // into a party, and from there figure out the well known party. + val candidate = partyFromKey(party.owningKey) // TODO: This should be done via the network map cache, which is the authoritative source of well known identities - // Look up the well known identity for that name return if (candidate != null) { - // If we have a well known identity by that name, use it in preference to the candidate. Otherwise default - // back to the candidate. - val res = wellKnownPartyFromX500Name(candidate.name) ?: candidate - res + wellKnownPartyFromX500Name(candidate.name) } else { null } diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt b/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt index 8dc9c857bd..81dfe1c7ec 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt @@ -23,7 +23,7 @@ class AbstractPartyToX500NameAsStringConverter(identitySvc: () -> IdentityServic if (party != null) { val partyName = identityService.wellKnownPartyFromAnonymous(party)?.toString() if (partyName != null) return partyName - log.warn("Identity service unable to resolve AbstractParty: $party") + log.warn("Identity service unable to resolve AbstractParty: $party") } return null // non resolvable anonymous parties } diff --git a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java index bf532ae053..350c6a4a3e 100644 --- a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java +++ b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java @@ -1,37 +1,57 @@ package net.corda.node.services.vault; -import com.google.common.collect.*; -import kotlin.*; +import com.google.common.collect.ImmutableSet; +import kotlin.Pair; +import kotlin.Triple; import net.corda.core.contracts.*; -import net.corda.core.identity.*; -import net.corda.core.messaging.*; -import net.corda.core.node.services.*; +import net.corda.core.identity.AbstractParty; +import net.corda.core.messaging.DataFeed; +import net.corda.core.node.services.IdentityService; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.VaultQueryException; +import net.corda.core.node.services.VaultService; import net.corda.core.node.services.vault.*; -import net.corda.core.node.services.vault.QueryCriteria.*; -import net.corda.core.utilities.*; -import net.corda.finance.contracts.*; -import net.corda.finance.contracts.asset.*; -import net.corda.finance.schemas.*; -import net.corda.node.utilities.*; -import net.corda.testing.*; -import net.corda.testing.contracts.*; -import net.corda.testing.node.*; -import org.junit.*; +import net.corda.core.node.services.vault.QueryCriteria.LinearStateQueryCriteria; +import net.corda.core.node.services.vault.QueryCriteria.VaultCustomQueryCriteria; +import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria; +import net.corda.core.utilities.EncodingUtils; +import net.corda.core.utilities.OpaqueBytes; +import net.corda.finance.contracts.DealState; +import net.corda.finance.contracts.asset.Cash; +import net.corda.finance.contracts.asset.CashUtilities; +import net.corda.finance.schemas.CashSchemaV1; +import net.corda.node.utilities.CordaPersistence; +import net.corda.node.utilities.DatabaseTransaction; +import net.corda.testing.SerializationEnvironmentRule; +import net.corda.testing.TestConstants; +import net.corda.testing.contracts.DummyLinearContract; +import net.corda.testing.contracts.VaultFiller; +import net.corda.testing.node.MockServices; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; import rx.Observable; -import java.io.*; -import java.lang.reflect.*; -import java.security.*; +import java.io.IOException; +import java.lang.reflect.Field; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPair; +import java.security.cert.CertificateException; import java.util.*; -import java.util.stream.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; -import static net.corda.core.node.services.vault.QueryCriteriaUtils.*; -import static net.corda.core.utilities.ByteArrays.*; +import static net.corda.core.node.services.vault.QueryCriteriaUtils.DEFAULT_PAGE_NUM; +import static net.corda.core.node.services.vault.QueryCriteriaUtils.MAX_PAGE_SIZE; +import static net.corda.core.utilities.ByteArrays.toHexString; import static net.corda.finance.contracts.asset.CashUtilities.*; import static net.corda.testing.CoreTestUtils.*; import static net.corda.testing.TestConstants.*; -import static net.corda.testing.node.MockServices.*; -import static org.assertj.core.api.Assertions.*; +import static net.corda.testing.node.MockServices.makeTestDatabaseAndMockServices; +import static net.corda.testing.node.MockServices.makeTestIdentityService; +import static org.assertj.core.api.Assertions.assertThat; public class VaultQueryJavaTests { @Rule @@ -42,7 +62,7 @@ public class VaultQueryJavaTests { private CordaPersistence database; @Before - public void setUp() { + public void setUp() throws CertificateException, InvalidAlgorithmParameterException { List cordappPackages = Arrays.asList("net.corda.testing.contracts", "net.corda.finance.contracts.asset", CashSchemaV1.class.getPackage().getName()); ArrayList keys = new ArrayList<>(); keys.add(getMEGA_CORP_KEY()); @@ -54,6 +74,8 @@ public class VaultQueryJavaTests { database = databaseAndServices.getFirst(); services = databaseAndServices.getSecond(); vaultService = services.getVaultService(); + services.getIdentityService().verifyAndRegisterIdentity(getDUMMY_CASH_ISSUER_IDENTITY()); + services.getIdentityService().verifyAndRegisterIdentity(getDUMMY_NOTARY_IDENTITY()); } @After diff --git a/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt index c69cf8e4c1..63d6a80e30 100644 --- a/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt @@ -169,9 +169,23 @@ class InMemoryIdentityServiceTests { * Ensure if we feed in a full identity, we get the same identity back. */ @Test - fun `deanonymising a well known identity`() { + fun `deanonymising a well known identity should return the identity`() { + val service = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT) val expected = ALICE - val actual = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT).wellKnownPartyFromAnonymous(expected) + service.verifyAndRegisterIdentity(ALICE_IDENTITY) + val actual = service.wellKnownPartyFromAnonymous(expected) assertEquals(expected, actual) } + + /** + * Ensure we don't blindly trust what an anonymous identity claims to be. + */ + @Test + fun `deanonymising a false well known identity should return null`() { + val service = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT) + val notAlice = Party(ALICE.name, generateKeyPair().public) + service.verifyAndRegisterIdentity(ALICE_IDENTITY) + val actual = service.wellKnownPartyFromAnonymous(notAlice) + assertNull(actual) + } } diff --git a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt index c01cd2be76..30c1753a43 100644 --- a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt @@ -269,11 +269,23 @@ class PersistentIdentityServiceTests { * Ensure if we feed in a full identity, we get the same identity back. */ @Test - fun `deanonymising a well known identity`() { + fun `deanonymising a well known identity should return the identity`() { + val service = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT) val expected = ALICE - val actual = database.transaction { - identityService.wellKnownPartyFromAnonymous(expected) - } + service.verifyAndRegisterIdentity(ALICE_IDENTITY) + val actual = service.wellKnownPartyFromAnonymous(expected) assertEquals(expected, actual) } + + /** + * Ensure we don't blindly trust what an anonymous identity claims to be. + */ + @Test + fun `deanonymising a false well known identity should return null`() { + val service = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT) + val notAlice = Party(ALICE.name, generateKeyPair().public) + service.verifyAndRegisterIdentity(ALICE_IDENTITY) + val actual = service.wellKnownPartyFromAnonymous(notAlice) + assertNull(actual) + } } diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt index a63846573e..a7851fc154 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt @@ -1,24 +1,26 @@ package net.corda.node.services.persistence -import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef import net.corda.core.contracts.TransactionState import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.generateKeyPair +import net.corda.core.identity.Party import net.corda.core.node.StatesToRecord -import net.corda.core.utilities.toBase58String import net.corda.core.node.services.Vault import net.corda.core.node.services.VaultService import net.corda.core.schemas.CommonSchemaV1 import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentStateRef -import net.corda.core.serialization.deserialize -import net.corda.core.serialization.SerializationDefaults import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.toBase58String import net.corda.finance.DOLLARS import net.corda.finance.POUNDS import net.corda.finance.SWISS_FRANCS -import net.corda.finance.contracts.asset.* +import net.corda.finance.contracts.asset.Cash +import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_KEY +import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_NAME +import net.corda.finance.contracts.asset.DummyFungibleContract import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.schemas.SampleCashSchemaV2 import net.corda.finance.schemas.SampleCashSchemaV3 @@ -54,7 +56,9 @@ class HibernateConfigurationTest { @JvmField val testSerialization = SerializationEnvironmentRule() lateinit var services: MockServices + lateinit var bankServices: MockServices lateinit var issuerServices: MockServices + lateinit var notaryServices: MockServices lateinit var database: CordaPersistence val vault: VaultService get() = services.vaultService @@ -65,19 +69,27 @@ class HibernateConfigurationTest { lateinit var entityManager: EntityManager lateinit var criteriaBuilder: CriteriaBuilder + // Identities used + private lateinit var identity: Party + private lateinit var issuer: Party + private lateinit var notary: Party + // test States lateinit var cashStates: List> @Before fun setUp() { val cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts.asset") - issuerServices = MockServices(cordappPackages, DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY, BOB_KEY, BOC_KEY) + bankServices = MockServices(cordappPackages, BOC.name, BOC_KEY) + issuerServices = MockServices(cordappPackages, DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY) + notaryServices = MockServices(cordappPackages, DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) val dataSourceProps = makeTestDataSourceProperties() val defaultDatabaseProperties = makeTestDatabaseProperties() database = configureDatabase(dataSourceProps, defaultDatabaseProperties, ::makeTestIdentityService) database.transaction { hibernateConfig = database.hibernateConfig - services = object : MockServices(cordappPackages, BOB_NAME, BOB_KEY, BOC_KEY, DUMMY_NOTARY_KEY) { + // `consumeCash` expects we can self-notarise transactions + services = object : MockServices(cordappPackages, BOB_NAME, generateKeyPair(), DUMMY_NOTARY_KEY) { override val vaultService = makeVaultService(database.hibernateConfig) override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { for (stx in txs) { @@ -91,7 +103,17 @@ class HibernateConfigurationTest { } hibernatePersister = services.hibernatePersister } - setUpDb() + + identity = services.myInfo.singleIdentity() + issuer = issuerServices.myInfo.singleIdentity() + notary = notaryServices.myInfo.singleIdentity() + + database.transaction { + val numStates = 10 + cashStates = services.fillWithSomeTestCash(100.DOLLARS, issuerServices, notary, numStates, numStates, Random(0L), issuedBy = issuer.ref(1)) + .states.toList() + } + sessionFactory = sessionFactoryForSchemas(VaultSchemaV1, CashSchemaV1, SampleCashSchemaV2, SampleCashSchemaV3) entityManager = sessionFactory.createEntityManager() criteriaBuilder = sessionFactory.criteriaBuilder @@ -104,12 +126,6 @@ class HibernateConfigurationTest { database.close() } - private fun setUpDb() { - database.transaction { - cashStates = services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 10, 10, Random(0L)).states.toList() - } - } - @Test fun `count rows`() { // structure query @@ -125,7 +141,7 @@ class HibernateConfigurationTest { @Test fun `consumed states`() { database.transaction { - services.consumeCash(50.DOLLARS, notary = DUMMY_NOTARY) + services.consumeCash(50.DOLLARS, notary = notary) } // structure query @@ -206,11 +222,11 @@ class HibernateConfigurationTest { fun `with sorting by state ref desc and asc`() { // generate additional state ref indexes database.transaction { - services.consumeCash(1.DOLLARS, notary = DUMMY_NOTARY) - services.consumeCash(2.DOLLARS, notary = DUMMY_NOTARY) - services.consumeCash(3.DOLLARS, notary = DUMMY_NOTARY) - services.consumeCash(4.DOLLARS, notary = DUMMY_NOTARY) - services.consumeCash(5.DOLLARS, notary = DUMMY_NOTARY) + services.consumeCash(1.DOLLARS, notary = notary) + services.consumeCash(2.DOLLARS, notary = notary) + services.consumeCash(3.DOLLARS, notary = notary) + services.consumeCash(4.DOLLARS, notary = notary) + services.consumeCash(5.DOLLARS, notary = notary) } // structure query @@ -236,11 +252,11 @@ class HibernateConfigurationTest { fun `with sorting by state ref index and txId desc and asc`() { // generate additional state ref indexes database.transaction { - services.consumeCash(1.DOLLARS, notary = DUMMY_NOTARY) - services.consumeCash(2.DOLLARS, notary = DUMMY_NOTARY) - services.consumeCash(3.DOLLARS, notary = DUMMY_NOTARY) - services.consumeCash(4.DOLLARS, notary = DUMMY_NOTARY) - services.consumeCash(5.DOLLARS, notary = DUMMY_NOTARY) + services.consumeCash(1.DOLLARS, notary = notary) + services.consumeCash(2.DOLLARS, notary = notary) + services.consumeCash(3.DOLLARS, notary = notary) + services.consumeCash(4.DOLLARS, notary = notary) + services.consumeCash(5.DOLLARS, notary = notary) } // structure query @@ -267,7 +283,7 @@ class HibernateConfigurationTest { fun `with pagination`() { // add 100 additional cash entries database.transaction { - services.fillWithSomeTestCash(1000.POUNDS, issuerServices, DUMMY_NOTARY, 100, 100, Random(0L), issuedBy = DUMMY_CASH_ISSUER) + services.fillWithSomeTestCash(1000.POUNDS, issuerServices, notary, 100, 100, Random(0L), issuedBy = issuer.ref(1)) } // structure query @@ -369,11 +385,11 @@ class HibernateConfigurationTest { fun `calculate cash balances`() { database.transaction { - services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 10, 10, Random(0L)) // +$100 = $200 - services.fillWithSomeTestCash(50.POUNDS, issuerServices, DUMMY_NOTARY, 5, 5, Random(0L)) // £50 = £50 - services.fillWithSomeTestCash(25.POUNDS, issuerServices, DUMMY_NOTARY, 5, 5, Random(0L)) // +£25 = £175 - services.fillWithSomeTestCash(500.SWISS_FRANCS, issuerServices, DUMMY_NOTARY, 10, 10, Random(0L)) // CHF500 = CHF500 - services.fillWithSomeTestCash(250.SWISS_FRANCS, issuerServices, DUMMY_NOTARY, 5, 5, Random(0L)) // +CHF250 = CHF750 + services.fillWithSomeTestCash(100.DOLLARS, issuerServices, notary, 10, issuer.ref(1)) // +$100 = $200 + services.fillWithSomeTestCash(50.POUNDS, issuerServices, notary, 5, issuer.ref(1)) // £50 = £50 + services.fillWithSomeTestCash(25.POUNDS, issuerServices, notary, 5, issuer.ref(1)) // +£25 = £175 + services.fillWithSomeTestCash(500.SWISS_FRANCS, issuerServices, notary, 10, issuer.ref(1)) // CHF500 = CHF500 + services.fillWithSomeTestCash(250.SWISS_FRANCS, issuerServices, notary, 5, issuer.ref(1)) // +CHF250 = CHF750 } // structure query @@ -402,8 +418,8 @@ class HibernateConfigurationTest { @Test fun `calculate cash balance for single currency`() { database.transaction { - services.fillWithSomeTestCash(50.POUNDS, issuerServices, DUMMY_NOTARY, 5, 5, Random(0L)) // £50 = £50 - services.fillWithSomeTestCash(25.POUNDS, issuerServices, DUMMY_NOTARY, 5, 5, Random(0L)) // +£25 = £175 + services.fillWithSomeTestCash(50.POUNDS, issuerServices, notary, 5, issuer.ref(1)) // £50 = £50 + services.fillWithSomeTestCash(25.POUNDS, issuerServices, notary, 5, issuer.ref(1)) // +£25 = £175 } // structure query @@ -432,10 +448,10 @@ class HibernateConfigurationTest { @Test fun `calculate and order by cash balance for owner and currency`() { database.transaction { - - services.fillWithSomeTestCash(200.DOLLARS, issuerServices, DUMMY_NOTARY, 2, 2, Random(0L), issuedBy = BOC.ref(1)) - services.fillWithSomeTestCash(300.POUNDS, issuerServices, DUMMY_NOTARY, 3, 3, Random(0L), issuedBy = DUMMY_CASH_ISSUER) - services.fillWithSomeTestCash(400.POUNDS, issuerServices, DUMMY_NOTARY, 4, 4, Random(0L), issuedBy = BOC.ref(2)) + val bank = bankServices.myInfo.legalIdentities.single() + services.fillWithSomeTestCash(200.DOLLARS, bankServices, notary, 2, bank.ref(1)) + services.fillWithSomeTestCash(300.POUNDS, issuerServices, notary, 3, issuer.ref(1)) + services.fillWithSomeTestCash(400.POUNDS, bankServices, notary, 4, bank.ref(2)) } // structure query @@ -622,9 +638,9 @@ class HibernateConfigurationTest { hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) } - services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 2, 2, Random(0L), ownedBy = ALICE) - val cashStates = services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 2, 2, Random(0L), - issuedBy = BOB.ref(0), ownedBy = (BOB)).states + services.fillWithSomeTestCash(100.DOLLARS, issuerServices, notary, 2, 2, Random(0L), + issuedBy = issuer.ref(1), owner = ALICE) + val cashStates = services.fillWithSomeTestCash(100.DOLLARS, services, notary, 2, identity.ref(0)).states // persist additional cash states explicitly with V3 schema cashStates.forEach { val cashState = it.state.data @@ -646,7 +662,7 @@ class HibernateConfigurationTest { // search predicate val cashStatesSchema = criteriaQuery.from(SampleCashSchemaV3.PersistentCashState::class.java) - val queryOwner = BOB.name.toString() + val queryOwner = identity.name.toString() criteriaQuery.where(criteriaBuilder.equal(cashStatesSchema.get("owner"), queryOwner)) val joinVaultStatesToCash = criteriaBuilder.equal(vaultStates.get("stateRef"), cashStatesSchema.get("stateRef")) @@ -701,8 +717,8 @@ class HibernateConfigurationTest { hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) } - val moreCash = services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 2, 2, Random(0L), - issuedBy = BOB.ref(0), ownedBy = BOB).states + val moreCash = services.fillWithSomeTestCash(100.DOLLARS, services, notary, 2, 2, Random(0L), + issuedBy = identity.ref(0), owner = identity).states // persist additional cash states explicitly with V3 schema moreCash.forEach { val cashState = it.state.data @@ -710,7 +726,7 @@ class HibernateConfigurationTest { hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) } - val cashStates = services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 2, 2, Random(0L), ownedBy = (ALICE)).states + val cashStates = services.fillWithSomeTestCash(100.DOLLARS, issuerServices, notary, 2, 2, Random(0L), owner = ALICE, issuedBy = issuer.ref(1)).states // persist additional cash states explicitly with V3 schema cashStates.forEach { val cashState = it.state.data diff --git a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt index 12689c3b30..123335a1de 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt @@ -5,7 +5,8 @@ import net.corda.core.contracts.Amount import net.corda.core.contracts.Issued import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef -import net.corda.core.crypto.* +import net.corda.core.crypto.NullKeys +import net.corda.core.crypto.generateKeyPair import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party @@ -77,7 +78,9 @@ class NodeVaultServiceTest { identity = services.myInfo.singleIdentityAndCert() issuerServices = MockServices(cordappPackages, DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY) bocServices = MockServices(cordappPackages, BOC_NAME, BOC_KEY) + services.identityService.verifyAndRegisterIdentity(DUMMY_CASH_ISSUER_IDENTITY) + services.identityService.verifyAndRegisterIdentity(BOC_IDENTITY) } @After @@ -511,10 +514,9 @@ class NodeVaultServiceTest { @Test fun `correct updates are generated for general transactions`() { - val service = vaultService val notary = identity.party val vaultSubscriber = TestSubscriber>().apply { - service.updates.subscribe(this) + vaultService.updates.subscribe(this) } val identity = services.myInfo.singleIdentityAndCert() @@ -533,15 +535,16 @@ class NodeVaultServiceTest { val signedIssuedTx = services.signInitialTransaction(issueBuilder) services.validatedTransactions.addTransaction(signedIssuedTx) - database.transaction { service.notify(StatesToRecord.ONLY_RELEVANT, issueTx) } + database.transaction { vaultService.notify(StatesToRecord.ONLY_RELEVANT, issueTx) } val expectedIssueUpdate = Vault.Update(emptySet(), setOf(cashState), null) database.transaction { val moveBuilder = TransactionBuilder(notary).apply { - Cash.generateSpend(services, this, Amount(1000, GBP), thirdPartyIdentity) + val changeIdentity = services.keyManagementService.freshKeyAndCert(identity, false) + Cash.generateSpend(services, this, Amount(1000, GBP), changeIdentity, thirdPartyIdentity) } val moveTx = moveBuilder.toWireTransaction(services) - service.notify(StatesToRecord.ONLY_RELEVANT, moveTx) + vaultService.notify(StatesToRecord.ONLY_RELEVANT, moveTx) } val expectedMoveUpdate = Vault.Update(setOf(cashState), emptySet(), null) @@ -580,6 +583,7 @@ class NodeVaultServiceTest { val initialCashState = StateAndRef(issueStx.tx.outputs.single(), StateRef(issueStx.id, 0)) // Change notary + services.identityService.verifyAndRegisterIdentity(DUMMY_NOTARY_IDENTITY) val newNotary = DUMMY_NOTARY val changeNotaryTx = NotaryChangeWireTransaction(listOf(initialCashState.ref), issueStx.notary!!, newNotary) val cashStateWithNewNotary = StateAndRef(initialCashState.state.copy(notary = newNotary), StateRef(changeNotaryTx.id, 0)) diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index 002e8ba00a..cd2d87fd0e 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -12,7 +12,10 @@ import net.corda.core.internal.packageName import net.corda.core.node.services.* import net.corda.core.node.services.vault.* import net.corda.core.node.services.vault.QueryCriteria.* -import net.corda.core.utilities.* +import net.corda.core.utilities.NonEmptySet +import net.corda.core.utilities.days +import net.corda.core.utilities.seconds +import net.corda.core.utilities.toHexString import net.corda.finance.* import net.corda.finance.contracts.CommercialPaper import net.corda.finance.contracts.Commodity @@ -31,7 +34,6 @@ import net.corda.testing.contracts.* import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties -import net.corda.testing.node.MockServices.Companion.makeTestIdentityService import net.corda.testing.schemas.DummyLinearStateSchemaV1 import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions.assertThat @@ -57,7 +59,7 @@ class VaultQueryTests { private lateinit var services: MockServices private lateinit var notaryServices: MockServices private val vaultService: VaultService get() = services.vaultService - private val identitySvc: IdentityService = makeTestIdentityService() + private lateinit var identitySvc: IdentityService private lateinit var database: CordaPersistence // test cash notary @@ -68,14 +70,16 @@ class VaultQueryTests { @Before fun setUp() { // register additional identities - identitySvc.verifyAndRegisterIdentity(CASH_NOTARY_IDENTITY) - identitySvc.verifyAndRegisterIdentity(BOC_IDENTITY) val databaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(MEGA_CORP_KEY, DUMMY_NOTARY_KEY), - createIdentityService = { identitySvc }, cordappPackages = cordappPackages) database = databaseAndServices.first services = databaseAndServices.second notaryServices = MockServices(cordappPackages, DUMMY_NOTARY.name, DUMMY_NOTARY_KEY, DUMMY_CASH_ISSUER_KEY, BOC_KEY, MEGA_CORP_KEY) + identitySvc = services.identityService + // Register all of the identities we're going to use + (notaryServices.myInfo.legalIdentitiesAndCerts + BOC_IDENTITY + CASH_NOTARY_IDENTITY + MINI_CORP_IDENTITY + MEGA_CORP_IDENTITY).forEach { identity -> + services.identityService.verifyAndRegisterIdentity(identity) + } } @After @@ -1388,20 +1392,24 @@ class VaultQueryTests { // GBP issuer val gbpCashIssuerName = CordaX500Name(organisation = "British Pounds Cash Issuer", locality = "London", country = "GB") val gbpCashIssuerServices = MockServices(cordappPackages, gbpCashIssuerName, generateKeyPair()) - val gbpCashIssuer = gbpCashIssuerServices.myInfo.singleIdentity().ref(1) + val gbpCashIssuer = gbpCashIssuerServices.myInfo.singleIdentityAndCert() // USD issuer val usdCashIssuerName = CordaX500Name(organisation = "US Dollars Cash Issuer", locality = "New York", country = "US") val usdCashIssuerServices = MockServices(cordappPackages, usdCashIssuerName, generateKeyPair()) - val usdCashIssuer = usdCashIssuerServices.myInfo.singleIdentity().ref(1) + val usdCashIssuer = usdCashIssuerServices.myInfo.singleIdentityAndCert() // CHF issuer val chfCashIssuerName = CordaX500Name(organisation = "Swiss Francs Cash Issuer", locality = "Zurich", country = "CH") val chfCashIssuerServices = MockServices(cordappPackages, chfCashIssuerName, generateKeyPair()) - val chfCashIssuer = chfCashIssuerServices.myInfo.singleIdentity().ref(1) - + val chfCashIssuer = chfCashIssuerServices.myInfo.singleIdentityAndCert() + listOf(gbpCashIssuer, usdCashIssuer, chfCashIssuer).forEach { identity -> + services.identityService.verifyAndRegisterIdentity(identity) + } + database.transaction { + services.fillWithSomeTestCash(100.POUNDS, gbpCashIssuerServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = gbpCashIssuer.party.ref(1)) + services.fillWithSomeTestCash(100.DOLLARS, usdCashIssuerServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = usdCashIssuer.party.ref(1)) + services.fillWithSomeTestCash(100.SWISS_FRANCS, chfCashIssuerServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = chfCashIssuer.party.ref(1)) + } database.transaction { - services.fillWithSomeTestCash(100.POUNDS, gbpCashIssuerServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = gbpCashIssuer) - services.fillWithSomeTestCash(100.DOLLARS, usdCashIssuerServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = usdCashIssuer) - services.fillWithSomeTestCash(100.SWISS_FRANCS, chfCashIssuerServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = chfCashIssuer) val criteria = FungibleAssetQueryCriteria(issuer = listOf(gbpCashIssuer.party, usdCashIssuer.party)) val results = vaultService.queryBy>(criteria) assertThat(results.states).hasSize(2) @@ -1413,8 +1421,9 @@ class VaultQueryTests { database.transaction { services.fillWithSomeTestCash(100.DOLLARS, notaryServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = BOC.ref(1)) services.fillWithSomeTestCash(100.DOLLARS, notaryServices, DUMMY_NOTARY, 1, 1, Random(0L), - issuedBy = MEGA_CORP.ref(0), ownedBy = (MINI_CORP)) - + issuedBy = MEGA_CORP.ref(0), owner = (MINI_CORP)) + } + database.transaction { val criteria = FungibleAssetQueryCriteria(owner = listOf(MEGA_CORP)) val results = vaultService.queryBy>(criteria) assertThat(results.states).hasSize(1) // can only be 1 owner of a node (MEGA_CORP in this MockServices setup) @@ -1426,10 +1435,11 @@ class VaultQueryTests { database.transaction { services.fillWithSomeTestCash(100.DOLLARS, notaryServices, CASH_NOTARY, 1, 1, Random(0L)) services.fillWithSomeTestCash(100.DOLLARS, notaryServices, DUMMY_NOTARY, 1, 1, Random(0L), - issuedBy = MEGA_CORP.ref(0), ownedBy = (MEGA_CORP)) + issuedBy = MEGA_CORP.ref(0), owner = (MEGA_CORP)) services.fillWithSomeTestCash(100.DOLLARS, notaryServices, DUMMY_NOTARY, 1, 1, Random(0L), - issuedBy = BOC.ref(0), ownedBy = (MINI_CORP)) // irrelevant to this vault - + issuedBy = BOC.ref(0), owner = MINI_CORP) // irrelevant to this vault + } + database.transaction { // DOCSTART VaultQueryExample5.2 val criteria = FungibleAssetQueryCriteria(owner = listOf(MEGA_CORP, BOC)) val results = vaultService.queryBy(criteria) diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt index aef17cd726..34b5672736 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt @@ -148,7 +148,7 @@ class VaultWithCashTest { database.transaction { // A tx that sends us money. - services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 10, 10, Random(0L), ownedBy = AnonymousParty(freshKey), + services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 10, 10, Random(0L), owner = AnonymousParty(freshKey), issuedBy = MEGA_CORP.ref(1)) println("Cash balance: ${services.getCashBalance(USD)}") } @@ -298,7 +298,7 @@ class VaultWithCashTest { val freshKey = services.keyManagementService.freshKey() database.transaction { - services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 3, 3, Random(0L), ownedBy = AnonymousParty(freshKey)) + services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 3, 3, Random(0L), owner = AnonymousParty(freshKey)) services.fillWithSomeTestCash(100.SWISS_FRANCS, issuerServices, DUMMY_NOTARY, 2, 2, Random(0L)) services.fillWithSomeTestCash(100.POUNDS, issuerServices, DUMMY_NOTARY, 1, 1, Random(0L)) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index d3dde4f44d..4ed9b5ef94 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -56,6 +56,7 @@ open class MockServices( vararg val keys: KeyPair ) : ServiceHub, StateLoader by stateLoader { companion object { + private val MOCK_IDENTITIES = listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY) @JvmStatic val MOCK_VERSION_INFO = VersionInfo(1, "Mock release", "Mock revision", "Mock Vendor") @@ -101,7 +102,7 @@ open class MockServices( /** * Makes database and mock services appropriate for unit tests. * @param keys a list of [KeyPair] instances to be used by [MockServices]. Defaults to [MEGA_CORP_KEY] - * @param createIdentityService a lambda function returning an instance of [IdentityService]. Defauts to [InMemoryIdentityService]. + * @param createIdentityService a lambda function returning an instance of [IdentityService]. Defaults to [InMemoryIdentityService]. * * @return a pair where the first element is the instance of [CordaPersistence] and the second is [MockServices]. */ diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index d4743c39a5..1f95993b37 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -87,9 +87,6 @@ val ALL_TEST_KEYS: List get() = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, AL val DUMMY_CASH_ISSUER_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(DUMMY_CASH_ISSUER.party as Party) -val MOCK_IDENTITIES = listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY) -val MOCK_IDENTITY_SERVICE: IdentityService get() = InMemoryIdentityService(MOCK_IDENTITIES, emptySet(), DEV_CA.certificate.cert) - val MOCK_HOST_AND_PORT = NetworkHostAndPort("mockHost", 30000) fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0) diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/VaultFiller.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/VaultFiller.kt index cad028aa20..5cdc3f1bfd 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/VaultFiller.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/VaultFiller.kt @@ -96,9 +96,26 @@ fun ServiceHub.fillWithSomeTestLinearStates(numberToCreate: Int, return Vault(states) } +/** + * Creates a random set of cash states that add up to the given amount and adds them to the vault. This is intended for + * unit tests. The cash is owned by the legal identity key from the storage service. + * + * The service hub needs to provide at least a key management service and a storage service. + * + * @param issuerServices service hub of the issuer node, which will be used to sign the transaction. + * @param outputNotary the notary to use for output states. The transaction is NOT signed by this notary. + * @return a vault object that represents the generated states (it will NOT be the full vault from the service hub!). + */ +fun ServiceHub.fillWithSomeTestCash(howMuch: Amount, + issuerServices: ServiceHub, + outputNotary: Party, + states: Int, + issuedBy: PartyAndReference): Vault + = fillWithSomeTestCash(howMuch, issuerServices, outputNotary, states, states, issuedBy = issuedBy) + /** * Creates a random set of between (by default) 3 and 10 cash states that add up to the given amount and adds them - * to the vault. This is intended for unit tests. The cash is issued by [DUMMY_CASH_ISSUER] and owned by the legal + * to the vault. This is intended for unit tests. By default the cash is issued by [DUMMY_CASH_ISSUER] and owned by the legal * identity key from the storage service. * * The service hub needs to provide at least a key management service and a storage service. @@ -113,18 +130,15 @@ fun ServiceHub.fillWithSomeTestCash(howMuch: Amount, atLeastThisManyStates: Int = 3, atMostThisManyStates: Int = 10, rng: Random = Random(), - ownedBy: AbstractParty? = null, + owner: AbstractParty? = null, issuedBy: PartyAndReference = DUMMY_CASH_ISSUER): Vault { val amounts = calculateRandomlySizedAmounts(howMuch, atLeastThisManyStates, atMostThisManyStates, rng) - val myKey = ownedBy?.owningKey ?: myInfo.chooseIdentity().owningKey - val anonParty = AnonymousParty(myKey) - // We will allocate one state to one transaction, for simplicities sake. val cash = Cash() val transactions: List = amounts.map { pennies -> val issuance = TransactionBuilder(null as Party?) - cash.generateIssue(issuance, Amount(pennies, Issued(issuedBy, howMuch.token)), anonParty, outputNotary) + cash.generateIssue(issuance, Amount(pennies, Issued(issuedBy, howMuch.token)),owner ?: myInfo.singleIdentity(), outputNotary) return@map issuerServices.signInitialTransaction(issuance, issuedBy.party.owningKey) } From 332915f08bb201596e20d4aecde04cd6c7001dff Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Fri, 17 Nov 2017 14:15:49 +0000 Subject: [PATCH 13/31] Bug fix for cash selection on H2 where the accumulated pennies amount is larger than max int --- .../cash/selection/CashSelectionH2Impl.kt | 4 +--- .../selection/CashSelectionH2ImplTest.kt} | 22 +++++++++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) rename finance/src/test/kotlin/net/corda/finance/contracts/asset/{CashSelectionH2Test.kt => cash/selection/CashSelectionH2ImplTest.kt} (59%) diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt index d8be0af698..08fa686c07 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt @@ -13,7 +13,6 @@ import java.sql.ResultSet import java.util.* class CashSelectionH2Impl : AbstractCashSelection() { - companion object { const val JDBC_DRIVER_NAME = "H2 JDBC Driver" val log = loggerFor() @@ -25,7 +24,6 @@ class CashSelectionH2Impl : AbstractCashSelection() { override fun toString() = "${this::class.java} for $JDBC_DRIVER_NAME" - // We are using an H2 specific means of selecting a minimum set of rows that match a request amount of coins: // 1) There is no standard SQL mechanism of calculating a cumulative total on a field and restricting row selection on the // running total of such an accumulator @@ -34,7 +32,7 @@ class CashSelectionH2Impl : AbstractCashSelection() { // 3) H2 does not support JOIN's in FOR UPDATE (hence we are forced to execute 2 queries) override fun executeQuery(connection: Connection, amount: Amount, lockId: UUID, notary: Party?, onlyFromIssuerParties: Set, withIssuerRefs: Set) : ResultSet { - connection.createStatement().execute("CALL SET(@t, 0);") + connection.createStatement().execute("CALL SET(@t, CAST(0 AS BIGINT));") val selectJoin = """ SELECT vs.transaction_id, vs.output_index, ccs.pennies, SET(@t, ifnull(@t,0)+ccs.pennies) total_pennies, vs.lock_id diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2ImplTest.kt similarity index 59% rename from finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt rename to finance/src/test/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2ImplTest.kt index 4f4a39abec..c044387f00 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2ImplTest.kt @@ -1,8 +1,12 @@ -package net.corda.finance.contracts.asset +package net.corda.finance.contracts.asset.cash.selection +import net.corda.core.internal.concurrent.transpose +import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow import net.corda.finance.DOLLARS +import net.corda.finance.POUNDS import net.corda.finance.flows.CashException +import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNodeParameters @@ -10,8 +14,9 @@ import net.corda.testing.startFlow import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.After import org.junit.Test +import java.util.Collections.nCopies -class CashSelectionH2Test { +class CashSelectionH2ImplTest { private val mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance")) @After @@ -19,6 +24,19 @@ class CashSelectionH2Test { mockNet.stopNodes() } + @Test + fun `selecting pennies amount larger than max int, which is split across multiple cash states`() { + val node = mockNet.createNode() + // The amount has to split across at least two states, probably to trigger the H2 accumulator variable during the + // spend operation below. + // Issuing Integer.MAX_VALUE will not cause an exception since PersistentCashState.pennies is a long + nCopies(2, Integer.MAX_VALUE).map { issueAmount -> + node.services.startFlow(CashIssueFlow(issueAmount.POUNDS, OpaqueBytes.of(1), mockNet.defaultNotaryIdentity)).resultFuture + }.transpose().getOrThrow() + // The spend must be more than the size of a single cash state to force the accumulator onto the second state. + node.services.startFlow(CashPaymentFlow((Integer.MAX_VALUE + 1L).POUNDS, node.info.legalIdentities[0])).resultFuture.getOrThrow() + } + @Test fun `check does not hold connection over retries`() { val bankA = mockNet.createNode(MockNodeParameters(configOverrides = { From 55e4688cc5598b5dfd2501787203fa117d8d4b15 Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Mon, 20 Nov 2017 10:33:13 +0000 Subject: [PATCH 14/31] CORDA-787 Split NodeMessagingClient into 3 (#2063) --- .../kotlin/net/corda/node/internal/Node.kt | 40 +++-- .../messaging/ArtemisMessagingClient.kt | 58 +++++++ ...ssagingClient.kt => P2PMessagingClient.kt} | 149 +++--------------- .../services/messaging/RPCMessagingClient.kt | 29 ++++ .../messaging/VerifierMessagingClient.kt | 73 +++++++++ .../node/services/schema/NodeSchemaService.kt | 6 +- .../OutOfProcessTransactionVerifierService.kt | 7 +- .../messaging/ArtemisMessagingTests.kt | 23 +-- 8 files changed, 227 insertions(+), 158 deletions(-) create mode 100644 node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingClient.kt rename node/src/main/kotlin/net/corda/node/services/messaging/{NodeMessagingClient.kt => P2PMessagingClient.kt} (76%) create mode 100644 node/src/main/kotlin/net/corda/node/services/messaging/RPCMessagingClient.kt create mode 100644 node/src/main/kotlin/net/corda/node/services/messaging/VerifierMessagingClient.kt diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index 534a63c344..cd0b5bb712 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -8,6 +8,7 @@ import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.RPCOps import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub +import net.corda.core.node.services.TransactionVerifierService import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.loggerFor import net.corda.core.serialization.internal.SerializationEnvironmentImpl @@ -19,9 +20,9 @@ import net.corda.node.services.RPCUserService import net.corda.node.services.RPCUserServiceImpl import net.corda.node.services.api.SchemaService import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.messaging.ArtemisMessagingServer -import net.corda.node.services.messaging.MessagingService -import net.corda.node.services.messaging.NodeMessagingClient +import net.corda.node.services.config.VerifierType +import net.corda.node.services.messaging.* +import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.utilities.AddressUtils import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.CordaPersistence @@ -80,7 +81,10 @@ open class Node(configuration: NodeConfiguration, } override val log: Logger get() = logger - override fun makeTransactionVerifierService() = (network as NodeMessagingClient).verifierService + override fun makeTransactionVerifierService(): TransactionVerifierService = when (configuration.verifierType) { + VerifierType.OutOfProcess -> verifierMessagingClient!!.verifierService + VerifierType.InMemory -> InMemoryTransactionVerifierService(numberOfWorkers = 4) + } private val sameVmNodeNumber = sameVmNodeCounter.incrementAndGet() // Under normal (non-test execution) it will always be "1" @@ -135,15 +139,18 @@ open class Node(configuration: NodeConfiguration, val advertisedAddress = info.addresses.single() printBasicNodeInfo("Incoming connection address", advertisedAddress.toString()) - - return NodeMessagingClient( + rpcMessagingClient = RPCMessagingClient(configuration, serverAddress) + verifierMessagingClient = when (configuration.verifierType) { + VerifierType.OutOfProcess -> VerifierMessagingClient(configuration, serverAddress, services.monitoringService.metrics) + VerifierType.InMemory -> null + } + return P2PMessagingClient( configuration, versionInfo, serverAddress, info.legalIdentities[0].owningKey, serverThread, database, - services.monitoringService.metrics, advertisedAddress) } @@ -198,9 +205,16 @@ open class Node(configuration: NodeConfiguration, runOnStop += this::stop start() } - - // Start up the MQ client. - (network as NodeMessagingClient).start(rpcOps, userService) + // Start up the MQ clients. + rpcMessagingClient.run { + start(rpcOps, userService) + runOnStop += this::stop + } + verifierMessagingClient?.run { + start() + runOnStop += this::stop + } + (network as P2PMessagingClient).start() } /** @@ -290,9 +304,13 @@ open class Node(configuration: NodeConfiguration, checkpointContext = KRYO_CHECKPOINT_CONTEXT.withClassLoader(classloader)) } + private lateinit var rpcMessagingClient: RPCMessagingClient + private var verifierMessagingClient: VerifierMessagingClient? = null /** Starts a blocking event loop for message dispatch. */ fun run() { - (network as NodeMessagingClient).run(messageBroker!!.serverControl) + rpcMessagingClient.start2(messageBroker!!.serverControl) + verifierMessagingClient?.start2() + (network as P2PMessagingClient).run() } private var shutdown = false diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingClient.kt new file mode 100644 index 0000000000..f377087b90 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingClient.kt @@ -0,0 +1,58 @@ +package net.corda.node.services.messaging + +import net.corda.core.serialization.internal.nodeSerializationEnv +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.loggerFor +import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER +import net.corda.nodeapi.ArtemisTcpTransport +import net.corda.nodeapi.ConnectionDirection +import net.corda.nodeapi.config.SSLConfiguration +import org.apache.activemq.artemis.api.core.client.* +import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE + +class ArtemisMessagingClient(private val config: SSLConfiguration, private val serverAddress: NetworkHostAndPort) { + companion object { + private val log = loggerFor() + } + + class Started(val sessionFactory: ClientSessionFactory, val session: ClientSession, val producer: ClientProducer) + + var started: Started? = null + private set + + fun start(): Started = synchronized(this) { + check(started == null) { "start can't be called twice" } + log.info("Connecting to message broker: $serverAddress") + // TODO Add broker CN to config for host verification in case the embedded broker isn't used + val tcpTransport = ArtemisTcpTransport.tcpTransport(ConnectionDirection.Outbound(), serverAddress, config) + val locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply { + // Never time out on our loopback Artemis connections. If we switch back to using the InVM transport this + // would be the default and the two lines below can be deleted. + connectionTTL = -1 + clientFailureCheckPeriod = -1 + minLargeMessageSize = ArtemisMessagingServer.MAX_FILE_SIZE + isUseGlobalPools = nodeSerializationEnv != null + } + val sessionFactory = locator.createSessionFactory() + // Login using the node username. The broker will authentiate us as its node (as opposed to another peer) + // using our TLS certificate. + // Note that the acknowledgement of messages is not flushed to the Artermis journal until the default buffer + // size of 1MB is acknowledged. + val session = sessionFactory!!.createSession(NODE_USER, NODE_USER, false, true, true, locator.isPreAcknowledge, DEFAULT_ACK_BATCH_SIZE) + session.start() + // Create a general purpose producer. + val producer = session.createProducer() + return Started(sessionFactory, session, producer).also { started = it } + } + + fun stop() = synchronized(this) { + started!!.run { + producer.close() + // Ensure any trailing messages are committed to the journal + session.commit() + // Closing the factory closes all the sessions it produced as well. + sessionFactory.close() + } + started = null + } +} diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt similarity index 76% rename from node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt rename to node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt index 0720bd443a..2b21ae766a 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt @@ -1,50 +1,32 @@ package net.corda.node.services.messaging -import com.codahale.metrics.MetricRegistry -import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name import net.corda.core.internal.ThreadBox import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.MessageRecipients -import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.services.PartyInfo -import net.corda.core.node.services.TransactionVerifierService import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.deserialize -import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.serialization.serialize -import net.corda.core.transactions.LedgerTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.loggerFor import net.corda.core.utilities.sequence import net.corda.core.utilities.trace import net.corda.node.VersionInfo -import net.corda.node.services.RPCUserService import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.config.VerifierType import net.corda.node.services.statemachine.StateMachineManagerImpl -import net.corda.node.services.transactions.InMemoryTransactionVerifierService -import net.corda.node.services.transactions.OutOfProcessTransactionVerifierService import net.corda.node.utilities.* -import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE import net.corda.nodeapi.internal.ArtemisMessagingComponent.ArtemisAddress import net.corda.nodeapi.internal.ArtemisMessagingComponent.NodeAddress import net.corda.nodeapi.internal.ArtemisMessagingComponent.ServiceAddress -import net.corda.nodeapi.ArtemisTcpTransport -import net.corda.nodeapi.ConnectionDirection -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 org.apache.activemq.artemis.api.core.ActiveMQObjectClosedException import org.apache.activemq.artemis.api.core.Message.* import org.apache.activemq.artemis.api.core.RoutingType import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.client.* -import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE -import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl import java.security.PublicKey import java.time.Instant import java.util.* @@ -77,18 +59,16 @@ import javax.persistence.Lob * If not provided, will default to [serverAddress]. */ @ThreadSafe -class NodeMessagingClient(private val config: NodeConfiguration, - private val versionInfo: VersionInfo, - private val serverAddress: NetworkHostAndPort, - private val myIdentity: PublicKey, - private val nodeExecutor: AffinityExecutor.ServiceAffinityExecutor, - private val database: CordaPersistence, - private val metrics: MetricRegistry, - advertisedAddress: NetworkHostAndPort = serverAddress +class P2PMessagingClient(config: NodeConfiguration, + private val versionInfo: VersionInfo, + serverAddress: NetworkHostAndPort, + private val myIdentity: PublicKey, + private val nodeExecutor: AffinityExecutor.ServiceAffinityExecutor, + private val database: CordaPersistence, + advertisedAddress: NetworkHostAndPort = serverAddress ) : SingletonSerializeAsToken(), MessagingService { companion object { - private val log = loggerFor() - + private val log = loggerFor() // This is a "property" attached to an Artemis MQ message object, which contains our own notion of "topic". // We should probably try to unify our notion of "topic" (really, just a string that identifies an endpoint // that will handle messages, like a URL) with the terminology used by underlying MQ libraries, to avoid @@ -99,8 +79,6 @@ class NodeMessagingClient(private val config: NodeConfiguration, private val releaseVersionProperty = SimpleString("release-version") private val platformVersionProperty = SimpleString("platform-version") private val amqDelayMillis = System.getProperty("amq.delivery.delay.ms", "0").toInt() - private val verifierResponseAddress = "$VERIFICATION_RESPONSES_QUEUE_NAME_PREFIX.${random63BitValue()}" - private val messageMaxRetryCount: Int = 3 fun createProcessedMessage(): AppendOnlyPersistentMap { @@ -144,15 +122,8 @@ class NodeMessagingClient(private val config: NodeConfiguration, } private class InnerState { - var started = false var running = false - var producer: ClientProducer? = null var p2pConsumer: ClientConsumer? = null - var session: ClientSession? = null - var sessionFactory: ClientSessionFactory? = null - var rpcServer: RPCServer? = null - // Consumer for inbound client RPC messages. - var verificationResponseConsumer: ClientConsumer? = null } private val messagesToRedeliver = database.transaction { @@ -161,11 +132,6 @@ class NodeMessagingClient(private val config: NodeConfiguration, private val scheduledMessageRedeliveries = ConcurrentHashMap>() - val verifierService = when (config.verifierType) { - VerifierType.InMemory -> InMemoryTransactionVerifierService(numberOfWorkers = 4) - VerifierType.OutOfProcess -> createOutOfProcessVerifierService() - } - /** A registration to handle messages of different types */ data class Handler(val topicSession: TopicSession, val callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit) : MessageHandlerRegistration @@ -176,7 +142,8 @@ class NodeMessagingClient(private val config: NodeConfiguration, private val messagingExecutor = AffinityExecutor.ServiceAffinityExecutor("Messaging", 1) override val myAddress: SingleMessageRecipient = NodeAddress(myIdentity, advertisedAddress) - + private val messageRedeliveryDelaySeconds = config.messageRedeliveryDelaySeconds.toLong() + private val artemis = ArtemisMessagingClient(config, serverAddress) private val state = ThreadBox(InnerState()) private val handlers = CopyOnWriteArrayList() @@ -209,54 +176,11 @@ class NodeMessagingClient(private val config: NodeConfiguration, var recipients: ByteArray = ByteArray(0) ) - fun start(rpcOps: RPCOps, userService: RPCUserService) { + fun start() { state.locked { - check(!started) { "start can't be called twice" } - started = true - - log.info("Connecting to message broker: $serverAddress") - // TODO Add broker CN to config for host verification in case the embedded broker isn't used - val tcpTransport = ArtemisTcpTransport.tcpTransport(ConnectionDirection.Outbound(), serverAddress, config) - val locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply { - // Never time out on our loopback Artemis connections. If we switch back to using the InVM transport this - // would be the default and the two lines below can be deleted. - connectionTTL = -1 - clientFailureCheckPeriod = -1 - minLargeMessageSize = ArtemisMessagingServer.MAX_FILE_SIZE - isUseGlobalPools = nodeSerializationEnv != null - } - sessionFactory = locator.createSessionFactory() - - // Login using the node username. The broker will authentiate us as its node (as opposed to another peer) - // using our TLS certificate. - // Note that the acknowledgement of messages is not flushed to the Artermis journal until the default buffer - // size of 1MB is acknowledged. - val session = sessionFactory!!.createSession(NODE_USER, NODE_USER, false, true, true, locator.isPreAcknowledge, DEFAULT_ACK_BATCH_SIZE) - this.session = session - session.start() - - // Create a general purpose producer. - val producer = session.createProducer() - this.producer = producer - + val session = artemis.start().session // Create a queue, consumer and producer for handling P2P network messages. p2pConsumer = session.createConsumer(P2P_QUEUE) - - val myCert = loadKeyStore(config.sslKeystore, config.keyStorePassword).getX509Certificate(X509Utilities.CORDA_CLIENT_TLS) - rpcServer = RPCServer(rpcOps, NODE_USER, NODE_USER, locator, userService, CordaX500Name.build(myCert.subjectX500Principal)) - - fun checkVerifierCount() { - if (session.queueQuery(SimpleString(VERIFICATION_REQUESTS_QUEUE_NAME)).consumerCount == 0) { - log.warn("No connected verifier listening on $VERIFICATION_REQUESTS_QUEUE_NAME!") - } - } - - if (config.verifierType == VerifierType.OutOfProcess) { - createQueueIfAbsent(VerifierApi.VERIFICATION_REQUESTS_QUEUE_NAME) - createQueueIfAbsent(verifierResponseAddress) - verificationResponseConsumer = session.createConsumer(verifierResponseAddress) - messagingExecutor.scheduleAtFixedRate(::checkVerifierCount, 0, 10, TimeUnit.SECONDS) - } } resumeMessageRedelivery() @@ -309,14 +233,12 @@ class NodeMessagingClient(private val config: NodeConfiguration, /** * Starts the p2p event loop: this method only returns once [stop] has been called. */ - fun run(serverControl: ActiveMQServerControl) { + fun run() { try { val consumer = state.locked { - check(started) { "start must be called first" } + check(artemis.started != null) { "start must be called first" } check(!running) { "run can't be called twice" } running = true - rpcServer!!.start(serverControl) - (verifierService as? OutOfProcessTransactionVerifierService)?.start(verificationResponseConsumer!!) // If it's null, it means we already called stop, so return immediately. p2pConsumer ?: return } @@ -404,7 +326,7 @@ class NodeMessagingClient(private val config: NodeConfiguration, override fun stop() { val running = state.locked { // We allow stop() to be called without a run() in between, but it must have at least been started. - check(started) + check(artemis.started != null) val prevRunning = running running = false val c = p2pConsumer ?: throw IllegalStateException("stop can't be called twice") @@ -423,13 +345,7 @@ class NodeMessagingClient(private val config: NodeConfiguration, // Only first caller to gets running true to protect against double stop, which seems to happen in some integration tests. if (running) { state.locked { - producer?.close() - producer = null - // Ensure any trailing messages are committed to the journal - session!!.commit() - // Closing the factory closes all the sessions it produced as well. - sessionFactory!!.close() - sessionFactory = null + artemis.stop() } } } @@ -440,7 +356,8 @@ class NodeMessagingClient(private val config: NodeConfiguration, messagingExecutor.fetchFrom { state.locked { val mqAddress = getMQAddress(target) - val artemisMessage = session!!.createMessage(true).apply { + val artemis = artemis.started!! + val artemisMessage = artemis.session.createMessage(true).apply { putStringProperty(cordaVendorProperty, cordaVendor) putStringProperty(releaseVersionProperty, releaseVersion) putIntProperty(platformVersionProperty, versionInfo.platformVersion) @@ -459,15 +376,14 @@ class NodeMessagingClient(private val config: NodeConfiguration, "Send to: $mqAddress topic: ${message.topicSession.topic} " + "sessionID: ${message.topicSession.sessionID} uuid: ${message.uniqueMessageId}" } - producer!!.send(mqAddress, artemisMessage) - + artemis.producer.send(mqAddress, artemisMessage) retryId?.let { database.transaction { messagesToRedeliver.computeIfAbsent(it, { Pair(message, target) }) } scheduledMessageRedeliveries[it] = messagingExecutor.schedule({ sendWithRetry(0, mqAddress, artemisMessage, it) - }, config.messageRedeliveryDelaySeconds.toLong(), TimeUnit.SECONDS) + }, messageRedeliveryDelaySeconds, TimeUnit.SECONDS) } } @@ -498,12 +414,12 @@ class NodeMessagingClient(private val config: NodeConfiguration, state.locked { log.trace { "Retry #$retryCount sending message $message to $address for $retryId" } - producer!!.send(address, message) + artemis.started!!.producer.send(address, message) } scheduledMessageRedeliveries[retryId] = messagingExecutor.schedule({ sendWithRetry(retryCount + 1, address, message, retryId) - }, config.messageRedeliveryDelaySeconds.toLong(), TimeUnit.SECONDS) + }, messageRedeliveryDelaySeconds, TimeUnit.SECONDS) } override fun cancelRedelivery(retryId: Long) { @@ -533,10 +449,11 @@ class NodeMessagingClient(private val config: NodeConfiguration, /** Attempts to create a durable queue on the broker which is bound to an address of the same name. */ private fun createQueueIfAbsent(queueName: String) { state.alreadyLocked { - val queueQuery = session!!.queueQuery(SimpleString(queueName)) + val session = artemis.started!!.session + val queueQuery = session.queueQuery(SimpleString(queueName)) if (!queueQuery.isExists) { log.info("Create fresh queue $queueName bound on same address") - session!!.createQueue(queueName, RoutingType.MULTICAST, queueName, true) + session.createQueue(queueName, RoutingType.MULTICAST, queueName, true) } } } @@ -564,22 +481,6 @@ class NodeMessagingClient(private val config: NodeConfiguration, return NodeClientMessage(topicSession, data, uuid) } - private fun createOutOfProcessVerifierService(): TransactionVerifierService { - return object : OutOfProcessTransactionVerifierService(metrics) { - override fun sendRequest(nonce: Long, transaction: LedgerTransaction) { - messagingExecutor.fetchFrom { - state.locked { - val message = session!!.createMessage(false) - val request = VerifierApi.VerificationRequest(nonce, transaction, SimpleString(verifierResponseAddress)) - request.writeToClientMessage(message) - producer!!.send(VERIFICATION_REQUESTS_QUEUE_NAME, message) - } - } - } - - } - } - // TODO Rethink PartyInfo idea and merging PeerAddress/ServiceAddress (the only difference is that Service address doesn't hold host and port) override fun getAddressOfParty(partyInfo: PartyInfo): MessageRecipients { return when (partyInfo) { diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCMessagingClient.kt new file mode 100644 index 0000000000..4aeb8339fa --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCMessagingClient.kt @@ -0,0 +1,29 @@ +package net.corda.node.services.messaging + +import net.corda.core.identity.CordaX500Name +import net.corda.core.messaging.RPCOps +import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.node.services.RPCUserService +import net.corda.node.utilities.* +import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER +import net.corda.nodeapi.config.SSLConfiguration +import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl + +class RPCMessagingClient(private val config: SSLConfiguration, serverAddress: NetworkHostAndPort) : SingletonSerializeAsToken() { + private val artemis = ArtemisMessagingClient(config, serverAddress) + private var rpcServer: RPCServer? = null + fun start(rpcOps: RPCOps, userService: RPCUserService) = synchronized(this) { + val locator = artemis.start().sessionFactory.serverLocator + val myCert = loadKeyStore(config.sslKeystore, config.keyStorePassword).getX509Certificate(X509Utilities.CORDA_CLIENT_TLS) + rpcServer = RPCServer(rpcOps, NODE_USER, NODE_USER, locator, userService, CordaX500Name.build(myCert.subjectX500Principal)) + } + + fun start2(serverControl: ActiveMQServerControl) = synchronized(this) { + rpcServer!!.start(serverControl) + } + + fun stop() = synchronized(this) { + artemis.stop() + } +} diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/VerifierMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/VerifierMessagingClient.kt new file mode 100644 index 0000000000..d916e63cc5 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/messaging/VerifierMessagingClient.kt @@ -0,0 +1,73 @@ +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.* +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.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.* +import java.util.concurrent.* + +class VerifierMessagingClient(config: SSLConfiguration, serverAddress: NetworkHostAndPort, metrics: MetricRegistry) : SingletonSerializeAsToken() { + companion object { + private val log = loggerFor() + private val verifierResponseAddress = "$VERIFICATION_RESPONSES_QUEUE_NAME_PREFIX.${random63BitValue()}" + } + + private val artemis = ArtemisMessagingClient(config, serverAddress) + /** 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.MULTICAST, 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) + } +} diff --git a/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt b/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt index 00cb1b5f79..99282681b8 100644 --- a/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt +++ b/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt @@ -14,7 +14,7 @@ import net.corda.node.services.api.SchemaService import net.corda.node.services.events.NodeSchedulerService import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.keys.PersistentKeyManagementService -import net.corda.node.services.messaging.NodeMessagingClient +import net.corda.node.services.messaging.P2PMessagingClient import net.corda.node.services.persistence.DBCheckpointStorage import net.corda.node.services.persistence.DBTransactionMappingStorage import net.corda.node.services.persistence.DBTransactionStorage @@ -47,8 +47,8 @@ class NodeSchemaService(cordappLoader: CordappLoader?) : SchemaService, Singleto PersistentUniquenessProvider.PersistentNotaryCommit::class.java, NodeSchedulerService.PersistentScheduledState::class.java, NodeAttachmentService.DBAttachment::class.java, - NodeMessagingClient.ProcessedMessage::class.java, - NodeMessagingClient.RetryMessage::class.java, + P2PMessagingClient.ProcessedMessage::class.java, + P2PMessagingClient.RetryMessage::class.java, NodeAttachmentService.DBAttachment::class.java, RaftUniquenessProvider.RaftState::class.java, BFTNonValidatingNotaryService.PersistedCommittedState::class.java, diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/OutOfProcessTransactionVerifierService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/OutOfProcessTransactionVerifierService.kt index 4819672f9f..3fefbf9822 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/OutOfProcessTransactionVerifierService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/OutOfProcessTransactionVerifierService.kt @@ -16,8 +16,9 @@ import net.corda.nodeapi.VerifierApi import org.apache.activemq.artemis.api.core.client.ClientConsumer import java.util.concurrent.ConcurrentHashMap -abstract class OutOfProcessTransactionVerifierService( - private val metrics: MetricRegistry +class OutOfProcessTransactionVerifierService( + private val metrics: MetricRegistry, + private val sendRequest: (Long, LedgerTransaction) -> Unit ) : SingletonSerializeAsToken(), TransactionVerifierService { companion object { val log = loggerFor() @@ -60,8 +61,6 @@ abstract class OutOfProcessTransactionVerifierService( } } - abstract fun sendRequest(nonce: Long, transaction: LedgerTransaction) - override fun verify(transaction: LedgerTransaction): CordaFuture<*> { log.info("Verifying ${transaction.id}") val future = openFuture() diff --git a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt index ebc9e59b74..225ea6952f 100644 --- a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt @@ -1,11 +1,9 @@ package net.corda.node.services.messaging -import com.codahale.metrics.MetricRegistry import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.generateKeyPair import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.openFuture -import net.corda.core.messaging.RPCOps import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.RPCUserService import net.corda.node.services.RPCUserServiceImpl @@ -58,16 +56,10 @@ class ArtemisMessagingTests { private lateinit var database: CordaPersistence private lateinit var userService: RPCUserService private lateinit var networkMapRegistrationFuture: CordaFuture - - private var messagingClient: NodeMessagingClient? = null + private var messagingClient: P2PMessagingClient? = null private var messagingServer: ArtemisMessagingServer? = null private lateinit var networkMapCache: NetworkMapCacheImpl - - private val rpcOps = object : RPCOps { - override val protocolVersion: Int get() = throw UnsupportedOperationException() - } - @Before fun setUp() { val baseDirectory = temporaryFolder.root.toPath() @@ -186,10 +178,10 @@ class ArtemisMessagingTests { } private fun startNodeMessagingClient() { - messagingClient!!.start(rpcOps, userService) + messagingClient!!.start() } - private fun createAndStartClientAndServer(receivedMessages: LinkedBlockingQueue): NodeMessagingClient { + private fun createAndStartClientAndServer(receivedMessages: LinkedBlockingQueue): P2PMessagingClient { createMessagingServer().start() val messagingClient = createMessagingClient() @@ -198,20 +190,19 @@ class ArtemisMessagingTests { receivedMessages.add(message) } // Run after the handlers are added, otherwise (some of) the messages get delivered and discarded / dead-lettered. - thread { messagingClient.run(messagingServer!!.serverControl) } + thread { messagingClient.run() } return messagingClient } - private fun createMessagingClient(server: NetworkHostAndPort = NetworkHostAndPort("localhost", serverPort)): NodeMessagingClient { + private fun createMessagingClient(server: NetworkHostAndPort = NetworkHostAndPort("localhost", serverPort)): P2PMessagingClient { return database.transaction { - NodeMessagingClient( + P2PMessagingClient( config, MOCK_VERSION_INFO, server, identity.public, ServiceAffinityExecutor("ArtemisMessagingTests", 1), - database, - MetricRegistry()).apply { + database).apply { config.configureWithDevSSLCertificate() messagingClient = this } From b685537c52ad6177ad426c2cf6026e1c7b05ef98 Mon Sep 17 00:00:00 2001 From: josecoll Date: Mon, 20 Nov 2017 10:36:37 +0000 Subject: [PATCH 15/31] Reverse types for Issuer attributes. (#2083) --- .../asset/cash/selection/CashSelectionPostgreSQLImpl.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt index d76fde536d..f6fdac5738 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt @@ -63,13 +63,13 @@ class CashSelectionPostgreSQLImpl : AbstractCashSelection() { paramOffset += 1 } if (onlyFromIssuerParties.isNotEmpty()) { - val issuerKeys = connection.createArrayOf("BYTEA", onlyFromIssuerParties.map + val issuerKeys = connection.createArrayOf("VARCHAR", onlyFromIssuerParties.map { it.owningKey.toBase58String() }.toTypedArray()) statement.setArray(3 + paramOffset, issuerKeys) paramOffset += 1 } if (withIssuerRefs.isNotEmpty()) { - val issuerRefs = connection.createArrayOf("VARCHAR", withIssuerRefs.map + val issuerRefs = connection.createArrayOf("BYTEA", withIssuerRefs.map { it.bytes }.toTypedArray()) statement.setArray(3 + paramOffset, issuerRefs) paramOffset += 1 @@ -79,5 +79,4 @@ class CashSelectionPostgreSQLImpl : AbstractCashSelection() { return statement.executeQuery() } - } From adacbfc7d1dc3f59d331aa7a2de5e9b1919b0a0a Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 17 Nov 2017 18:41:23 +0100 Subject: [PATCH 16/31] Upgrade to Kotlin 1.1.60 --- constants.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/constants.properties b/constants.properties index a780da9214..214be59311 100644 --- a/constants.properties +++ b/constants.properties @@ -1,5 +1,5 @@ gradlePluginsVersion=2.0.8 -kotlinVersion=1.1.50 +kotlinVersion=1.1.60 guavaVersion=21.0 bouncycastleVersion=1.57 typesafeConfigVersion=1.3.1 \ No newline at end of file From 49cdad58c7492b6b3ad4419e9258b0c8205f1edf Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 17 Nov 2017 18:41:44 +0100 Subject: [PATCH 17/31] Minor: Add a brief comment to the RPC role setup code that explains the security justification for it. --- .../corda/node/services/messaging/ArtemisMessagingServer.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt index 8166d75080..8c5aa07e39 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt @@ -235,8 +235,10 @@ class ArtemisMessagingServer(private val config: NodeConfiguration, 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[RPCApi.RPC_SERVER_QUEUE_NAME] = setOf(nodeInternalRole, restrictedRole(RPC_ROLE, send = true)) - // TODO remove the NODE_USER role once the webserver doesn't need it + // TODO: remove the NODE_USER role below once the webserver doesn't need it anymore. securityRoles["${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$NODE_USER.#"] = setOf(nodeInternalRole) + // Each RPC user must have its own role and its own queue. This prevents users accessing each other's queues + // and stealing RPC responses. for ((username) in userService.users) { securityRoles["${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username.#"] = setOf( nodeInternalRole, From 273965f17aeb4581b2c20252185238b27db86e18 Mon Sep 17 00:00:00 2001 From: Alberto Arri <30873160+al-r3@users.noreply.github.com> Date: Mon, 20 Nov 2017 11:24:19 +0000 Subject: [PATCH 18/31] remove cordacon code (#2082) --- .../src/main/kotlin/net/corda/node/internal/NodeStartup.kt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index a8e23eeb5b..930dbe7db8 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -23,7 +23,6 @@ import java.lang.management.ManagementFactory import java.net.InetAddress import java.nio.file.Path import java.nio.file.Paths -import java.time.LocalDate import java.util.* import kotlin.system.exitProcess @@ -305,11 +304,6 @@ open class NodeStartup(val args: Array) { "Computers are useless. They can only\ngive you answers. -- Picasso" ) - // TODO: Delete this after CordaCon. - val cordaCon2017date = LocalDate.of(2017, 9, 12) - val cordaConBanner = if (LocalDate.now() < cordaCon2017date) - "${Emoji.soon} Register for our Free CordaCon event : see https://goo.gl/Z15S8W" else "" - if (Emoji.hasEmojiTerminal) messages += "Kind of like a regular database but\nwith emojis, colours and ascii art. ${Emoji.coolGuy}" val (msg1, msg2) = messages.randomOrNull()!!.split('\n') @@ -323,7 +317,6 @@ open class NodeStartup(val args: Array) { a("--- ${versionInfo.vendor} ${versionInfo.releaseVersion} (${versionInfo.revision.take(7)}) -----------------------------------------------"). newline(). newline(). - a(cordaConBanner). newline(). reset()) } From f26aa33553464d924774a8870337f7c34b92033d Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Mon, 20 Nov 2017 11:31:08 +0000 Subject: [PATCH 19/31] Introduce contextLogger (#2085) * Revert with comment, probably lazy for a reason. --- .../client/jackson/StringToMethodCallParser.kt | 4 ++-- .../net/corda/client/rpc/internal/RPCClient.kt | 7 ++----- .../client/rpc/internal/RPCClientProxyHandler.kt | 7 ++----- .../corda/kotlin/rpc/StandaloneCordaRPClientTest.kt | 7 ++----- .../kotlin/net/corda/core/internal/ToggleField.kt | 8 ++++++-- .../core/internal/concurrent/CordaFutureImpl.kt | 4 ++-- .../net/corda/core/node/services/NotaryService.kt | 7 +++++-- .../kotlin/net/corda/core/utilities/KotlinUtils.kt | 11 +++++++++-- .../main/kotlin/net/corda/docs/CustomVaultQuery.kt | 7 ++----- .../corda/finance/contracts/asset/OnLedgerAsset.kt | 5 ++--- .../asset/cash/selection/AbstractCashSelection.kt | 5 +---- .../asset/cash/selection/CashSelectionH2Impl.kt | 4 ++-- .../cash/selection/CashSelectionPostgreSQLImpl.kt | 7 ++----- .../kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt | 4 ++-- .../internal/serialization/CordaClassResolver.kt | 4 ++-- .../internal/serialization/amqp/ObjectSerializer.kt | 6 ++++-- .../serialization/amqp/PropertySerializer.kt | 5 ++--- .../amqp/custom/ThrowableSerializer.kt | 4 ++-- .../corda/node/services/AttachmentLoadingTests.kt | 4 ++-- .../net/corda/node/internal/CordaRPCOpsImpl.kt | 4 ++-- .../src/main/kotlin/net/corda/node/internal/Node.kt | 8 ++++---- .../kotlin/net/corda/node/internal/NodeStartup.kt | 2 +- .../corda/node/internal/cordapp/CordappLoader.kt | 5 ++--- .../corda/node/services/api/ServiceHubInternal.kt | 4 ++-- .../corda/node/services/config/ConfigUtilities.kt | 5 ++--- .../node/services/events/NodeSchedulerService.kt | 5 ++--- .../services/identity/InMemoryIdentityService.kt | 5 ++--- .../services/identity/PersistentIdentityService.kt | 5 ++--- .../services/messaging/ArtemisMessagingServer.kt | 12 ++++-------- .../node/services/messaging/P2PMessagingClient.kt | 4 ++-- .../net/corda/node/services/messaging/RPCServer.kt | 13 +++++-------- .../corda/node/services/network/NetworkMapClient.kt | 8 ++------ .../corda/node/services/network/NodeInfoWatcher.kt | 5 ++--- .../services/network/PersistentNetworkMapCache.kt | 4 ++-- .../services/persistence/AbstractPartyDescriptor.kt | 4 ++-- .../AbstractPartyToX500NameAsStringConverter.kt | 4 ++-- .../services/persistence/HibernateConfiguration.kt | 5 ++--- .../services/persistence/NodeAttachmentService.kt | 5 ++--- .../corda/node/services/schema/HibernateObserver.kt | 4 ++-- .../statemachine/FlowStackSnapshotFactory.kt | 5 ++--- .../statemachine/StateMachineManagerImpl.kt | 7 ++----- .../transactions/BFTNonValidatingNotaryService.kt | 3 +-- .../corda/node/services/transactions/BFTSMaRt.kt | 6 +++--- .../node/services/transactions/BFTSMaRtConfig.kt | 4 ++-- .../transactions/DistributedImmutableMap.kt | 4 ++-- .../OutOfProcessTransactionVerifierService.kt | 4 ++-- .../transactions/PersistentUniquenessProvider.kt | 8 ++------ .../services/transactions/RaftUniquenessProvider.kt | 5 ++--- .../services/vault/HibernateQueryCriteriaParser.kt | 6 +++--- .../corda/node/services/vault/NodeVaultService.kt | 2 +- .../node/services/vault/VaultSoftLockManager.kt | 7 ++----- .../kotlin/net/corda/node/shell/InteractiveShell.kt | 4 ++-- .../net/corda/node/utilities/AffinityExecutor.kt | 5 ----- .../corda/node/utilities/AppendOnlyPersistentMap.kt | 4 ++-- .../net/corda/node/utilities/PersistentMap.kt | 4 ++-- .../node/utilities/ServiceIdentityGenerator.kt | 5 ++--- .../kotlin/net/corda/bank/api/BankOfCordaWebApi.kt | 4 ++-- .../kotlin/net/corda/irs/IRSDemoTest.kt | 4 ++-- .../kotlin/net/corda/test/spring/SpringDriver.kt | 4 ++-- .../net/corda/irs/web/api/InterestSwapRestAPI.kt | 7 ++++--- .../corda/irs/web/api/InterestRatesSwapDemoAPI.kt | 4 ++-- .../net/corda/vega/analytics/AnalyticsEngine.kt | 4 ++-- .../main/kotlin/net/corda/traderdemo/TraderDemo.kt | 5 ++--- .../net/corda/testing/driver/ShutdownManager.kt | 8 ++------ .../main/kotlin/net/corda/testing/node/MockNode.kt | 11 ++++++----- .../kotlin/net/corda/smoketesting/NodeProcess.kt | 4 ++-- .../main/kotlin/net/corda/testing/http/HttpUtils.kt | 4 ++-- .../kotlin/net/corda/demobench/explorer/Explorer.kt | 4 ++-- .../main/kotlin/net/corda/demobench/pty/R3Pty.kt | 4 ++-- .../main/kotlin/net/corda/demobench/rpc/NodeRPC.kt | 4 ++-- .../main/kotlin/net/corda/demobench/web/DBViewer.kt | 4 ++-- .../kotlin/net/corda/demobench/web/WebServer.kt | 4 ++-- .../kotlin/net/corda/explorer/ExplorerSimulation.kt | 2 +- .../src/main/kotlin/net/corda/explorer/Main.kt | 4 ++-- .../kotlin/net/corda/loadtest/ConnectionManager.kt | 4 ++-- .../main/kotlin/net/corda/loadtest/Disruption.kt | 3 ++- .../src/main/kotlin/net/corda/loadtest/LoadTest.kt | 4 ++-- .../kotlin/net/corda/loadtest/NodeConnection.kt | 4 ++-- .../net/corda/loadtest/tests/StabilityTest.kt | 5 ++--- .../kotlin/net/corda/verifier/VerifierDriver.kt | 4 ++-- .../src/main/kotlin/net/corda/verifier/Verifier.kt | 8 ++------ .../kotlin/net/corda/webserver/WebArgsParser.kt | 2 +- .../corda/webserver/internal/AllExceptionMapper.kt | 2 +- .../net/corda/webserver/internal/NodeWebServer.kt | 4 ++-- .../webserver/servlets/AttachmentDownloadServlet.kt | 6 ++++-- .../corda/webserver/servlets/DataUploadServlet.kt | 6 ++++-- 86 files changed, 198 insertions(+), 243 deletions(-) diff --git a/client/jackson/src/main/kotlin/net/corda/client/jackson/StringToMethodCallParser.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/StringToMethodCallParser.kt index a79d14f591..5ff3c3b08e 100644 --- a/client/jackson/src/main/kotlin/net/corda/client/jackson/StringToMethodCallParser.kt +++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/StringToMethodCallParser.kt @@ -7,7 +7,7 @@ import com.google.common.collect.HashMultimap import com.google.common.collect.Multimap import net.corda.client.jackson.StringToMethodCallParser.ParsedMethodCall import net.corda.core.CordaException -import org.slf4j.LoggerFactory +import net.corda.core.utilities.contextLogger import java.lang.reflect.Constructor import java.lang.reflect.Method import java.util.concurrent.Callable @@ -90,7 +90,7 @@ open class StringToMethodCallParser @JvmOverloads constructor( return result } - private val log = LoggerFactory.getLogger(StringToMethodCallParser::class.java)!! + private val log = contextLogger() } /** The methods that can be invoked via this parser. */ diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt index 12fcb8bcd7..f7318b34b3 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt @@ -11,10 +11,7 @@ import net.corda.core.messaging.RPCOps import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.internal.nodeSerializationEnv -import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.minutes -import net.corda.core.utilities.seconds +import net.corda.core.utilities.* import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.RPCApi @@ -96,7 +93,7 @@ class RPCClient( ) : this(tcpTransport(ConnectionDirection.Outbound(), hostAndPort, sslConfiguration), configuration, serializationContext) companion object { - private val log = loggerFor>() + private val log = contextLogger() } fun start( diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt index e3e2e07d84..a85a6342b2 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt @@ -22,10 +22,7 @@ import net.corda.core.internal.ThreadBox import net.corda.core.messaging.RPCOps import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.serialize -import net.corda.core.utilities.Try -import net.corda.core.utilities.debug -import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.* import net.corda.nodeapi.ArtemisConsumer import net.corda.nodeapi.ArtemisProducer import net.corda.nodeapi.RPCApi @@ -91,7 +88,7 @@ class RPCClientProxyHandler( private val lifeCycle = LifeCycle(State.UNSTARTED) private companion object { - val log = loggerFor() + private val log = contextLogger() // To check whether toString() is being invoked val toStringMethod: Method = Object::toString.javaMethod!! diff --git a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt index d6d305f542..36323ec068 100644 --- a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt +++ b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt @@ -11,10 +11,7 @@ import net.corda.core.messaging.* import net.corda.core.node.NodeInfo import net.corda.core.node.services.Vault import net.corda.core.node.services.vault.* -import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.seconds +import net.corda.core.utilities.* import net.corda.finance.DOLLARS import net.corda.finance.POUNDS import net.corda.finance.SWISS_FRANCS @@ -45,7 +42,7 @@ import kotlin.test.assertTrue class StandaloneCordaRPClientTest { private companion object { - val log = loggerFor() + private val log = contextLogger() val user = User("user1", "test", permissions = setOf("ALL")) val port = AtomicInteger(15200) const val attachmentSize = 2116 diff --git a/core/src/main/kotlin/net/corda/core/internal/ToggleField.kt b/core/src/main/kotlin/net/corda/core/internal/ToggleField.kt index 36ae18d05c..032d0d8802 100644 --- a/core/src/main/kotlin/net/corda/core/internal/ToggleField.kt +++ b/core/src/main/kotlin/net/corda/core/internal/ToggleField.kt @@ -1,6 +1,6 @@ package net.corda.core.internal -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import org.slf4j.Logger import java.util.concurrent.atomic.AtomicReference import kotlin.reflect.KProperty @@ -47,8 +47,12 @@ class ThreadLeakException : RuntimeException("Leaked thread detected: ${Thread.c /** @param isAGlobalThreadBeingCreated whether a global thread (that should not inherit any value) is being created. */ class InheritableThreadLocalToggleField(name: String, - private val log: Logger = loggerFor>(), + private val log: Logger = staticLog, private val isAGlobalThreadBeingCreated: (Array) -> Boolean) : ToggleField(name) { + companion object { + private val staticLog = contextLogger() + } + private inner class Holder(value: T) : AtomicReference(value) { fun valueOrDeclareLeak() = get() ?: throw ThreadLeakException() fun childValue(): Holder? { diff --git a/core/src/main/kotlin/net/corda/core/internal/concurrent/CordaFutureImpl.kt b/core/src/main/kotlin/net/corda/core/internal/concurrent/CordaFutureImpl.kt index a97eff4c56..6ff8ce4f2c 100644 --- a/core/src/main/kotlin/net/corda/core/internal/concurrent/CordaFutureImpl.kt +++ b/core/src/main/kotlin/net/corda/core/internal/concurrent/CordaFutureImpl.kt @@ -3,8 +3,8 @@ package net.corda.core.internal.concurrent import net.corda.core.internal.VisibleForTesting import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.match +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import org.slf4j.Logger import java.time.Duration import java.util.concurrent.CompletableFuture @@ -118,7 +118,7 @@ interface OpenFuture : ValueOrException, CordaFuture @VisibleForTesting internal class CordaFutureImpl(private val impl: CompletableFuture = CompletableFuture()) : Future by impl, OpenFuture { companion object { - private val defaultLog = loggerFor>() + private val defaultLog = contextLogger() internal val listenerFailedMessage = "Future listener failed:" } diff --git a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt index 8241fcd13c..46227d0027 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt @@ -9,7 +9,7 @@ import net.corda.core.identity.Party import net.corda.core.node.ServiceHub import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.serialize -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import org.slf4j.Logger import java.security.PublicKey @@ -45,8 +45,11 @@ abstract class NotaryService : SingletonSerializeAsToken() { * of the cluster is sufficient for transaction notarisation. For example, a single-node or a Raft notary. */ abstract class TrustedAuthorityNotaryService : NotaryService() { - protected open val log: Logger = loggerFor() + companion object { + private val staticLog = contextLogger() + } + protected open val log: Logger get() = staticLog // TODO: specify the valid time window in config, and convert TimeWindowChecker to a utility method protected abstract val timeWindowChecker: TimeWindowChecker protected abstract val uniquenessProvider: UniquenessProvider diff --git a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt index dd76afb644..ce0370319c 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt @@ -24,12 +24,19 @@ infix fun Int.exactAdd(b: Int): Int = Math.addExact(this, b) infix fun Long.exactAdd(b: Long): Long = Math.addExact(this, b) /** - * Get the [Logger] for a class using the syntax + * Usually you won't need this method: + * * If you're in a companion object, use [contextLogger] + * * If you're in an object singleton, use [LoggerFactory.getLogger] directly on javaClass * - * `val logger = loggerFor()` + * Otherwise, this gets the [Logger] for a class using the syntax + * + * `private val log = loggerFor()` */ inline fun loggerFor(): Logger = LoggerFactory.getLogger(T::class.java) +/** When called from a companion object, returns the logger for the enclosing class. */ +fun Any.contextLogger(): Logger = LoggerFactory.getLogger(javaClass.enclosingClass) + /** Log a TRACE level message produced by evaluating the given lamdba, but only if TRACE logging is enabled. */ inline fun Logger.trace(msg: () -> String) { if (isTraceEnabled) trace(msg()) diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomVaultQuery.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomVaultQuery.kt index 1ae88a6015..506dc8bc02 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomVaultQuery.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomVaultQuery.kt @@ -9,10 +9,7 @@ import net.corda.core.node.services.CordaService import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.transactions.SignedTransaction -import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.ProgressTracker -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.unwrap +import net.corda.core.utilities.* import net.corda.finance.flows.AbstractCashFlow import net.corda.finance.flows.CashException import net.corda.finance.flows.CashIssueFlow @@ -25,7 +22,7 @@ object CustomVaultQuery { @CordaService class Service(val services: AppServiceHub) : SingletonSerializeAsToken() { private companion object { - val log = loggerFor() + private val log = contextLogger() } fun rebalanceCurrencyReserves(): List> { val nativeQuery = """ diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/OnLedgerAsset.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/OnLedgerAsset.kt index d64bb817b2..eb0078d8ad 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/OnLedgerAsset.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/OnLedgerAsset.kt @@ -4,7 +4,7 @@ import net.corda.core.contracts.* import net.corda.core.contracts.Amount.Companion.sumOrThrow import net.corda.core.identity.AbstractParty import net.corda.core.transactions.TransactionBuilder -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.trace import java.security.PublicKey import java.util.* @@ -32,8 +32,7 @@ data class PartyAndAmount(val party: AbstractParty, val amount: Amount< */ abstract class OnLedgerAsset> : Contract { companion object { - val log = loggerFor>() - + private val log = contextLogger() /** * Generate a transaction that moves an amount of currency to the given pubkey. * diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/AbstractCashSelection.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/AbstractCashSelection.kt index 34dfb3b3ea..b5d2113f8d 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/AbstractCashSelection.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/AbstractCashSelection.kt @@ -4,15 +4,12 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.Amount import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef -import net.corda.core.contracts.TransactionState import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowLogic import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.node.ServiceHub import net.corda.core.node.services.StatesNotAvailableException -import net.corda.core.serialization.SerializationDefaults -import net.corda.core.serialization.deserialize import net.corda.core.utilities.* import net.corda.finance.contracts.asset.Cash import java.sql.* @@ -44,7 +41,7 @@ abstract class AbstractCashSelection { }.invoke() } - val log = loggerFor() + private val log = contextLogger() } // coin selection retry loop counter, sleep (msecs) and lock for selecting states diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt index 08fa686c07..440047f19d 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt @@ -5,8 +5,8 @@ import net.corda.core.crypto.toStringShort import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor import java.sql.Connection import java.sql.DatabaseMetaData import java.sql.ResultSet @@ -15,7 +15,7 @@ import java.util.* class CashSelectionH2Impl : AbstractCashSelection() { companion object { const val JDBC_DRIVER_NAME = "H2 JDBC Driver" - val log = loggerFor() + private val log = contextLogger() } override fun isCompatible(metadata: DatabaseMetaData): Boolean { diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt index f6fdac5738..f96ef6f441 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt @@ -3,10 +3,7 @@ package net.corda.finance.contracts.asset.cash.selection import net.corda.core.contracts.Amount import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party -import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.toBase58String +import net.corda.core.utilities.* import java.sql.Connection import java.sql.DatabaseMetaData import java.sql.ResultSet @@ -16,7 +13,7 @@ class CashSelectionPostgreSQLImpl : AbstractCashSelection() { companion object { val JDBC_DRIVER_NAME = "PostgreSQL JDBC Driver" - val log = loggerFor() + private val log = contextLogger() } override fun isCompatible(metadata: DatabaseMetaData): Boolean { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt b/node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt index aadc49f4c3..7017e89a56 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt @@ -5,7 +5,7 @@ import net.corda.core.internal.ThreadBox import net.corda.core.internal.createDirectories import net.corda.core.internal.isRegularFile import net.corda.core.internal.list -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import rx.Observable import rx.Scheduler import rx.Subscription @@ -28,7 +28,7 @@ import java.util.concurrent.TimeUnit class NodeInfoFilesCopier(scheduler: Scheduler = Schedulers.io()) : AutoCloseable { companion object { - private val log = loggerFor() + private val log = contextLogger() const val NODE_INFO_FILE_NAME_PREFIX = "nodeInfo-" } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt index 52690a1780..d4c98fc1e1 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt @@ -10,7 +10,7 @@ import net.corda.nodeapi.internal.AttachmentsClassLoader import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializationContext -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.nodeapi.internal.serialization.amqp.hasAnnotationInHierarchy import net.corda.nodeapi.internal.serialization.kryo.ThrowableSerializer import java.io.PrintWriter @@ -187,7 +187,7 @@ class TransientClassWhiteList(delegate: ClassWhitelist) : AbstractMutableClassWh @Suppress("unused") class LoggingWhitelist(val delegate: ClassWhitelist, val global: Boolean = true) : MutableClassWhitelist { companion object { - val log = loggerFor() + private val log = contextLogger() val globallySeen: MutableSet = Collections.synchronizedSet(mutableSetOf()) val journalWriter: PrintWriter? = openOptionalDynamicWhitelistJournal() diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt index 7dcb450803..433f8731ba 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt @@ -1,7 +1,7 @@ package net.corda.nodeapi.internal.serialization.amqp +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType import org.apache.qpid.proton.amqp.Symbol import org.apache.qpid.proton.codec.Data @@ -17,7 +17,9 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS open val kotlinConstructor = constructorForDeserialization(clazz) val javaConstructor by lazy { kotlinConstructor?.javaConstructor } - private val logger = loggerFor() + companion object { + private val logger = contextLogger() + } open internal val propertySerializers: Collection by lazy { propertiesForSerialization(kotlinConstructor, clazz, factory) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt index 3d9fa8b0a5..59c8b983fa 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt @@ -1,6 +1,6 @@ package net.corda.nodeapi.internal.serialization.amqp -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import org.apache.qpid.proton.amqp.Binary import org.apache.qpid.proton.codec.Data import java.lang.reflect.Method @@ -61,8 +61,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r } companion object { - private val logger = loggerFor() - + private val logger = contextLogger() fun make(name: String, readMethod: Method?, resolvedType: Type, factory: SerializerFactory): PropertySerializer { readMethod?.isAccessible = true if (SerializerFactory.isPrimitive(resolvedType)) { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ThrowableSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ThrowableSerializer.kt index 8697cd98c7..e78cf2b3d8 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ThrowableSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ThrowableSerializer.kt @@ -3,14 +3,14 @@ package net.corda.nodeapi.internal.serialization.amqp.custom import net.corda.core.CordaRuntimeException import net.corda.core.CordaThrowable import net.corda.core.serialization.SerializationFactory -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.nodeapi.internal.serialization.amqp.* import java.io.NotSerializableException class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy(Throwable::class.java, ThrowableProxy::class.java, factory) { companion object { - private val logger = loggerFor() + private val logger = contextLogger() } override val revealSubclassesInSchema: Boolean = true diff --git a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt index 0b09f55804..4a2a894154 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt @@ -13,8 +13,8 @@ import net.corda.core.internal.div import net.corda.core.internal.toLedgerTransaction import net.corda.core.serialization.SerializationFactory import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.testing.* @@ -41,7 +41,7 @@ class AttachmentLoadingTests { } private companion object { - val logger = loggerFor() + private val logger = contextLogger() val isolatedJAR = AttachmentLoadingTests::class.java.getResource("isolated.jar")!! val ISOLATED_CONTRACT_ID = "net.corda.finance.contracts.isolated.AnotherDummyContract" diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt index 0f73300772..d28c8b67ab 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -20,8 +20,8 @@ import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.Vault import net.corda.core.node.services.vault.* import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.messaging.context @@ -296,6 +296,6 @@ internal class CordaRPCOpsImpl( } companion object { - private val log = loggerFor() + private val log = contextLogger() } } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index cd0b5bb712..99af1e7965 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -10,9 +10,9 @@ import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub import net.corda.core.node.services.TransactionVerifierService import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.nodeSerializationEnv +import net.corda.core.utilities.contextLogger import net.corda.node.VersionInfo import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.serialization.KryoServerSerializationScheme @@ -50,7 +50,7 @@ open class Node(configuration: NodeConfiguration, cordappLoader: CordappLoader = makeCordappLoader(configuration) ) : AbstractNode(configuration, createClock(configuration), versionInfo, cordappLoader) { companion object { - private val logger = loggerFor() + private val staticLog = contextLogger() var renderBasicInfoToConsole = true /** Used for useful info that we always want to show, even when not logging to the console */ @@ -80,7 +80,7 @@ open class Node(configuration: NodeConfiguration, } } - override val log: Logger get() = logger + override val log: Logger get() = staticLog override fun makeTransactionVerifierService(): TransactionVerifierService = when (configuration.verifierType) { VerifierType.OutOfProcess -> verifierMessagingClient!!.verifierService VerifierType.InMemory -> InMemoryTransactionVerifierService(numberOfWorkers = 4) @@ -283,7 +283,7 @@ open class Node(configuration: NodeConfiguration, _startupComplete.set(Unit) } }, - { th -> logger.error("Unexpected exception", th) } + { th -> staticLog.error("Unexpected exception", th) } // XXX: Why not use log? ) shutdownHook = addShutdownHook { stop() diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index 930dbe7db8..4ed9546140 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -29,7 +29,7 @@ import kotlin.system.exitProcess /** This class is responsible for starting a Node from command line arguments. */ open class NodeStartup(val args: Array) { companion object { - private val logger by lazy { loggerFor() } + private val logger by lazy { loggerFor() } // I guess this is lazy to allow for logging init, but why Node? val LOGS_DIRECTORY_NAME = "logs" val LOGS_CAN_BE_FOUND_IN_STRING = "Logs can be found in" } diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt index 025d0bc94e..0e5c186af1 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt @@ -12,7 +12,7 @@ import net.corda.core.node.services.CordaService import net.corda.core.schemas.MappedSchema import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializeAsToken -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.node.internal.classloading.requireAnnotation import net.corda.node.services.config.NodeConfiguration import net.corda.nodeapi.internal.serialization.DefaultWhitelist @@ -53,8 +53,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List() - + private val logger = contextLogger() /** * Default cordapp dir name */ diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index 52ced3c7f0..a5999d2026 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -16,7 +16,7 @@ import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NetworkMapCacheBase import net.corda.core.node.services.TransactionStorage import net.corda.core.transactions.SignedTransaction -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.internal.cordapp.CordappProviderInternal import net.corda.node.services.config.NodeConfiguration @@ -43,7 +43,7 @@ interface NetworkMapCacheBaseInternal : NetworkMapCacheBase { interface ServiceHubInternal : ServiceHub { companion object { - private val log = loggerFor() + private val log = contextLogger() } override val vaultService: VaultServiceInternal diff --git a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt index f8d68d3146..9ba6a09c9f 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt @@ -8,12 +8,12 @@ import net.corda.core.crypto.Crypto import net.corda.core.crypto.SignatureScheme import net.corda.core.identity.CordaX500Name import net.corda.core.internal.* -import net.corda.core.utilities.loggerFor import net.corda.node.utilities.* import net.corda.nodeapi.config.SSLConfiguration import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralSubtree import org.bouncycastle.asn1.x509.NameConstraints +import org.slf4j.LoggerFactory import java.nio.file.Path import java.security.KeyStore @@ -21,8 +21,7 @@ fun configOf(vararg pairs: Pair): Config = ConfigFactory.parseMap( operator fun Config.plus(overrides: Map): Config = ConfigFactory.parseMap(overrides).withFallback(this) object ConfigHelper { - private val log = loggerFor() - + private val log = LoggerFactory.getLogger(javaClass) fun loadConfig(baseDirectory: Path, configFile: Path = baseDirectory / "node.conf", allowMissingConfig: Boolean = false, diff --git a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt index 27ca5d579b..ec4fdd855d 100644 --- a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt +++ b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt @@ -17,7 +17,7 @@ import net.corda.core.internal.until import net.corda.core.node.StateLoader import net.corda.core.schemas.PersistentStateRef import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.trace import net.corda.node.internal.MutableClock import net.corda.node.services.api.FlowStarter @@ -65,8 +65,7 @@ class NodeSchedulerService(private val clock: Clock, : SchedulerService, SingletonSerializeAsToken() { companion object { - private val log = loggerFor() - + private val log = contextLogger() /** * Wait until the given [Future] is complete or the deadline is reached, with support for [MutableClock] implementations * used in demos or testing. This will substitute a Fiber compatible Future so the current diff --git a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt index 679e5251c4..77997cd721 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt @@ -8,7 +8,7 @@ import net.corda.core.internal.toX509CertHolder import net.corda.core.node.services.IdentityService import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.trace import org.bouncycastle.cert.X509CertificateHolder import java.security.InvalidAlgorithmParameterException @@ -16,7 +16,6 @@ import java.security.PublicKey import java.security.cert.* import java.util.concurrent.ConcurrentHashMap import javax.annotation.concurrent.ThreadSafe -import javax.security.auth.x500.X500Principal /** * Simple identity service which caches parties and provides functionality for efficient lookup. @@ -33,7 +32,7 @@ class InMemoryIdentityService(identities: Iterable = emptyS trustRoot: X509CertificateHolder) : this(wellKnownIdentities, confidentialIdentities, trustRoot.cert) companion object { - private val log = loggerFor() + private val log = contextLogger() } /** diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index dba40da607..960cfb84fc 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -10,11 +10,10 @@ import net.corda.core.node.services.IdentityService import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor import net.corda.core.utilities.MAX_HASH_HEX_SIZE +import net.corda.core.utilities.contextLogger import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.node.utilities.NODE_DATABASE_PREFIX -import net.corda.node.utilities.X509Utilities import org.bouncycastle.cert.X509CertificateHolder import java.io.ByteArrayInputStream import java.security.InvalidAlgorithmParameterException @@ -36,7 +35,7 @@ class PersistentIdentityService(identities: Iterable = empt trustRoot: X509CertificateHolder) : this(wellKnownIdentities, confidentialIdentities, trustRoot.cert) companion object { - private val log = loggerFor() + private val log = contextLogger() private val certFactory: CertificateFactory = CertificateFactory.getInstance("X.509") fun createPKMap(): AppendOnlyPersistentMap { diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt index 8c5aa07e39..68e9b0f79c 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt @@ -12,10 +12,7 @@ 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.serialization.SingletonSerializeAsToken -import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.parsePublicKeyBase58 +import net.corda.core.utilities.* import net.corda.node.internal.Node import net.corda.node.services.RPCUserService import net.corda.node.services.config.NodeConfiguration @@ -99,7 +96,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration, val networkMapCache: NetworkMapCache, val userService: RPCUserService) : SingletonSerializeAsToken() { companion object { - private val log = loggerFor() + private val log = contextLogger() /** 10 MiB maximum allowed file size for attachments, including message headers. TODO: acquire this value from Network Map when supported. */ @JvmStatic val MAX_FILE_SIZE = 10485760 @@ -419,7 +416,7 @@ private class VerifyingNettyConnector(configuration: MutableMap, protocolManager: ClientProtocolManager?) : NettyConnector(configuration, handler, listener, closeExecutor, threadPool, scheduledThreadPool, protocolManager) { companion object { - private val log = loggerFor() + private val log = contextLogger() } private val sslEnabled = ConfigurationHelper.getBooleanProperty(TransportConstants.SSL_ENABLED_PROP_NAME, TransportConstants.DEFAULT_SSL_ENABLED, configuration) @@ -542,8 +539,7 @@ class NodeLoginModule : LoginModule { const val VERIFIER_ROLE = "SystemRoles/Verifier" const val CERT_CHAIN_CHECKS_OPTION_NAME = "CertChainChecks" - - val log = loggerFor() + private val log = contextLogger() } private var loginSucceeded: Boolean = false diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt index 2b21ae766a..e78dfb54af 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt @@ -10,8 +10,8 @@ import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize +import net.corda.core.utilities.* import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor import net.corda.core.utilities.sequence import net.corda.core.utilities.trace import net.corda.node.VersionInfo @@ -68,7 +68,7 @@ class P2PMessagingClient(config: NodeConfiguration, advertisedAddress: NetworkHostAndPort = serverAddress ) : SingletonSerializeAsToken(), MessagingService { companion object { - private val log = loggerFor() + private val log = contextLogger() // This is a "property" attached to an Artemis MQ message object, which contains our own notion of "topic". // We should probably try to unify our notion of "topic" (really, just a string that identifies an endpoint // that will handle messages, like a URL) with the terminology used by underlying MQ libraries, to avoid diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt index 9de779a9a9..2f2716dd76 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt @@ -24,10 +24,7 @@ import net.corda.core.messaging.RPCOps import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationDefaults.RPC_SERVER_CONTEXT import net.corda.core.serialization.deserialize -import net.corda.core.utilities.Try -import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.seconds +import net.corda.core.utilities.* import net.corda.node.services.RPCUserService import net.corda.node.services.logging.pushToLoggingContext import net.corda.nodeapi.* @@ -42,6 +39,7 @@ import org.apache.activemq.artemis.api.core.client.ServerLocator import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl import org.apache.activemq.artemis.api.core.management.CoreNotificationType import org.apache.activemq.artemis.api.core.management.ManagementHelper +import org.slf4j.LoggerFactory import org.slf4j.MDC import rx.Notification import rx.Observable @@ -90,7 +88,7 @@ class RPCServer( private val rpcConfiguration: RPCServerConfiguration = RPCServerConfiguration.default ) { private companion object { - val log = loggerFor() + private val log = contextLogger() } private enum class State { @@ -442,7 +440,7 @@ class ObservableContext( val observationSendExecutor: ExecutorService ) { private companion object { - val log = loggerFor() + private val log = contextLogger() } private val serializationContextWithObservableContext = RpcServerObservableSerializer.createContext(this) @@ -465,8 +463,7 @@ class ObservableContext( object RpcServerObservableSerializer : Serializer>() { private object RpcObservableContextKey - private val log = loggerFor() - + private val log = LoggerFactory.getLogger(javaClass) fun createContext(observableContext: ObservableContext): SerializationContext { return RPC_SERVER_CONTEXT.withProperty(RpcServerObservableSerializer.RpcObservableContextKey, observableContext) } diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt index 24a3b92fe0..02f4310061 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt @@ -8,7 +8,7 @@ import net.corda.core.internal.openHttpConnection import net.corda.core.node.NodeInfo import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.minutes import net.corda.core.utilities.seconds import net.corda.node.services.api.NetworkMapCacheInternal @@ -25,10 +25,6 @@ import java.util.concurrent.Executors import java.util.concurrent.TimeUnit class NetworkMapClient(compatibilityZoneURL: URL) { - companion object { - val logger = loggerFor() - } - private val networkMapUrl = URL("$compatibilityZoneURL/network-map") fun publish(signedNodeInfo: SignedData) { @@ -73,7 +69,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, private val fileWatcher: NodeInfoWatcher, private val networkMapClient: NetworkMapClient?) : Closeable { companion object { - private val logger = loggerFor() + private val logger = contextLogger() private val retryInterval = 1.minutes } diff --git a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt index 579c91afe4..76fcf6c472 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt @@ -7,7 +7,7 @@ import net.corda.core.internal.* import net.corda.core.node.NodeInfo import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.seconds import net.corda.nodeapi.NodeInfoFilesCopier import rx.Observable @@ -40,8 +40,7 @@ class NodeInfoWatcher(private val nodePath: Path, val processedNodeInfoHashes: Set get() = _processedNodeInfoHashes.toSet() companion object { - private val logger = loggerFor() - + private val logger = contextLogger() /** * Saves the given [NodeInfo] to a path. * The node is 'encoded' as a SignedData, signed with the owning key of its first identity. diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index 35da4d013b..982f4da070 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -19,7 +19,7 @@ import net.corda.core.node.services.PartyInfo import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.node.services.api.NetworkMapCacheBaseInternal import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.utilities.CordaPersistence @@ -63,7 +63,7 @@ class NetworkMapCacheImpl( @ThreadSafe open class PersistentNetworkMapCache(private val database: CordaPersistence) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal { companion object { - val logger = loggerFor() + private val logger = contextLogger() } // TODO Small explanation, partyNodes and registeredNodes is left in memory as it was before, because it will be removed in diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyDescriptor.kt b/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyDescriptor.kt index 7ae975368e..7201c7df56 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyDescriptor.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyDescriptor.kt @@ -3,7 +3,7 @@ package net.corda.node.services.persistence import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.node.services.IdentityService -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import org.hibernate.type.descriptor.WrapperOptions import org.hibernate.type.descriptor.java.AbstractTypeDescriptor import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan @@ -11,7 +11,7 @@ import org.hibernate.type.descriptor.java.MutabilityPlan class AbstractPartyDescriptor(identitySvc: () -> IdentityService) : AbstractTypeDescriptor(AbstractParty::class.java) { companion object { - private val log = loggerFor() + private val log = contextLogger() } private val identityService: IdentityService by lazy(identitySvc) diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt b/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt index 81dfe1c7ec..dcaf9f82d3 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt @@ -3,7 +3,7 @@ package net.corda.node.services.persistence import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.node.services.IdentityService -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import javax.persistence.AttributeConverter import javax.persistence.Converter @@ -14,7 +14,7 @@ import javax.persistence.Converter @Converter(autoApply = true) class AbstractPartyToX500NameAsStringConverter(identitySvc: () -> IdentityService) : AttributeConverter { companion object { - private val log = loggerFor() + private val log = contextLogger() } private val identityService: IdentityService by lazy(identitySvc) diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt index cd6b93de57..7d5f20b248 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt @@ -3,8 +3,7 @@ package net.corda.node.services.persistence import net.corda.core.internal.castIfPossible import net.corda.core.node.services.IdentityService import net.corda.core.schemas.MappedSchema -import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.toHexString import net.corda.node.services.api.SchemaService import net.corda.node.utilities.DatabaseTransactionManager @@ -29,7 +28,7 @@ import java.util.concurrent.ConcurrentHashMap class HibernateConfiguration(val schemaService: SchemaService, private val databaseProperties: Properties, private val createIdentityService: () -> IdentityService) { companion object { - val logger = loggerFor() + private val logger = contextLogger() } // TODO: make this a guava cache or similar to limit ability for this to grow forever. diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt index 4e5fcf4530..26f251b73e 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt @@ -14,13 +14,12 @@ import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.vault.* import net.corda.core.serialization.* -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.node.services.vault.HibernateAttachmentQueryCriteriaParser import net.corda.node.utilities.DatabaseTransactionManager import net.corda.node.utilities.NODE_DATABASE_PREFIX import net.corda.node.utilities.currentDBSession import java.io.* -import java.lang.Exception import java.nio.file.Paths import java.time.Instant import java.util.jar.JarInputStream @@ -57,7 +56,7 @@ class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, Single ) : Serializable companion object { - private val log = loggerFor() + private val log = contextLogger() } @VisibleForTesting diff --git a/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt b/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt index 2babcc1d70..41e6b6d641 100644 --- a/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt +++ b/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt @@ -7,8 +7,8 @@ import net.corda.core.internal.VisibleForTesting import net.corda.core.node.services.Vault import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentStateRef +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor import net.corda.node.services.persistence.HibernateConfiguration import net.corda.node.utilities.DatabaseTransactionManager import org.hibernate.FlushMode @@ -20,7 +20,7 @@ import rx.Observable // TODO: Manage version evolution of the schemas via additional tooling. class HibernateObserver private constructor(private val config: HibernateConfiguration) { companion object { - private val log = loggerFor() + private val log = contextLogger() @JvmStatic fun install(vaultUpdates: Observable>, config: HibernateConfiguration): HibernateObserver { val observer = HibernateObserver(config) diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStackSnapshotFactory.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStackSnapshotFactory.kt index 72a7926eb2..7360954d31 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStackSnapshotFactory.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStackSnapshotFactory.kt @@ -3,7 +3,7 @@ package net.corda.node.services.statemachine import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowStackSnapshot import net.corda.core.flows.StateMachineRunId -import net.corda.core.utilities.loggerFor +import org.slf4j.LoggerFactory import java.nio.file.Path import java.util.* @@ -26,8 +26,7 @@ interface FlowStackSnapshotFactory { fun persistAsJsonFile(flowClass: Class>, baseDir: Path, flowId: StateMachineRunId) private object DefaultFlowStackSnapshotFactory : FlowStackSnapshotFactory { - private val log = loggerFor() - + private val log = LoggerFactory.getLogger(javaClass) override fun getFlowStackSnapshot(flowClass: Class>): FlowStackSnapshot? { log.warn("Flow stack snapshot are not supposed to be used in a production deployment") return null diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt index 29c261aa7a..73fe043ee8 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt @@ -27,10 +27,7 @@ import net.corda.core.serialization.SerializationDefaults.SERIALIZATION_FACTORY import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.core.utilities.Try -import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.trace +import net.corda.core.utilities.* import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.services.api.Checkpoint import net.corda.node.services.api.CheckpointStorage @@ -67,7 +64,7 @@ class StateMachineManagerImpl( inner class FiberScheduler : FiberExecutorScheduler("Same thread scheduler", executor) companion object { - private val logger = loggerFor() + private val logger = contextLogger() internal val sessionTopic = TopicSession("platform.session") init { diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt index 8266aaa0c0..adf1afd69d 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt @@ -16,7 +16,6 @@ import net.corda.core.node.services.NotaryService import net.corda.core.node.services.TimeWindowChecker import net.corda.core.node.services.UniquenessProvider import net.corda.core.schemas.PersistentStateRef -import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.transactions.FilteredTransaction @@ -41,7 +40,7 @@ class BFTNonValidatingNotaryService(override val services: ServiceHubInternal, cluster: BFTSMaRt.Cluster) : NotaryService() { companion object { val id = constructId(validating = false, bft = true) - private val log = loggerFor() + private val log = contextLogger() } private val client: BFTSMaRt.Client diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt index b1ea728694..67e49887ea 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt @@ -29,8 +29,8 @@ import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.transactions.FilteredTransaction import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.transactions.BFTSMaRt.Client import net.corda.node.services.transactions.BFTSMaRt.Replica @@ -77,7 +77,7 @@ object BFTSMaRt { class Client(config: BFTSMaRtConfig, private val clientId: Int, private val cluster: Cluster, private val notaryService: BFTNonValidatingNotaryService) : SingletonSerializeAsToken() { companion object { - private val log = loggerFor() + private val log = contextLogger() } /** A proxy for communicating with the BFT cluster */ @@ -181,7 +181,7 @@ object BFTSMaRt { protected val notaryIdentityKey: PublicKey, private val timeWindowChecker: TimeWindowChecker) : DefaultRecoverable() { companion object { - private val log = loggerFor() + private val log = contextLogger() } private val stateManagerOverride = run { diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRtConfig.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRtConfig.kt index 827c3356ae..1ba8515fee 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRtConfig.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRtConfig.kt @@ -2,8 +2,8 @@ package net.corda.node.services.transactions import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor import java.io.FileWriter import java.io.PrintWriter import java.net.InetAddress @@ -19,7 +19,7 @@ import java.util.concurrent.TimeUnit.MILLISECONDS */ class BFTSMaRtConfig(private val replicaAddresses: List, debug: Boolean, val exposeRaces: Boolean) : PathManager(Files.createTempDirectory("bft-smart-config")) { companion object { - private val log = loggerFor() + private val log = contextLogger() internal val portIsClaimedFormat = "Port %s is claimed by another replica: %s" } diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/DistributedImmutableMap.kt b/node/src/main/kotlin/net/corda/node/services/transactions/DistributedImmutableMap.kt index 0b8b00983f..5a67023df9 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/DistributedImmutableMap.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/DistributedImmutableMap.kt @@ -4,7 +4,7 @@ import io.atomix.copycat.Command import io.atomix.copycat.Query import io.atomix.copycat.server.Commit import io.atomix.copycat.server.StateMachine -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.node.utilities.* import java.util.LinkedHashMap @@ -17,7 +17,7 @@ import java.util.LinkedHashMap */ class DistributedImmutableMap(val db: CordaPersistence, createMap: () -> AppendOnlyPersistentMap, E, EK>) : StateMachine() { companion object { - private val log = loggerFor>() + private val log = contextLogger() } object Commands { diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/OutOfProcessTransactionVerifierService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/OutOfProcessTransactionVerifierService.kt index 3fefbf9822..fbd7db4f93 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/OutOfProcessTransactionVerifierService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/OutOfProcessTransactionVerifierService.kt @@ -11,7 +11,7 @@ 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.loggerFor +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 @@ -21,7 +21,7 @@ class OutOfProcessTransactionVerifierService( private val sendRequest: (Long, LedgerTransaction) -> Unit ) : SingletonSerializeAsToken(), TransactionVerifierService { companion object { - val log = loggerFor() + private val log = contextLogger() } private data class VerificationHandle( diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt index d3cfaaa877..dd0e527ba1 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt @@ -9,11 +9,8 @@ import net.corda.core.internal.ThreadBox import net.corda.core.node.services.UniquenessException import net.corda.core.node.services.UniquenessProvider import net.corda.core.schemas.PersistentStateRef -import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.core.serialization.deserialize -import net.corda.core.serialization.serialize -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.node.utilities.NODE_DATABASE_PREFIX import java.io.Serializable @@ -62,8 +59,7 @@ class PersistentUniquenessProvider : UniquenessProvider, SingletonSerializeAsTok private val mutex = ThreadBox(InnerState()) companion object { - private val log = loggerFor() - + private val log = contextLogger() fun createMap(): AppendOnlyPersistentMap = AppendOnlyPersistentMap( toPersistentEntityKey = { PersistentStateRef(it.txhash.toString(), it.index) }, diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt index 655d19d108..1976c0e82c 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt @@ -26,7 +26,7 @@ import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.node.services.config.RaftConfig import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.node.utilities.CordaPersistence @@ -49,8 +49,7 @@ import javax.persistence.* @ThreadSafe class RaftUniquenessProvider(private val transportConfiguration: NodeSSLConfiguration, private val db: CordaPersistence, private val metrics: MetricRegistry, private val raftConfig: RaftConfig) : UniquenessProvider, SingletonSerializeAsToken() { companion object { - private val log = loggerFor() - + private val log = contextLogger() fun createMap(): AppendOnlyPersistentMap, RaftState, String> = AppendOnlyPersistentMap( toPersistentEntityKey = { it }, diff --git a/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt b/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt index 02af1f4b1b..042ff71c01 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt @@ -11,7 +11,7 @@ import net.corda.core.node.services.vault.QueryCriteria.CommonQueryCriteria import net.corda.core.schemas.PersistentState import net.corda.core.schemas.PersistentStateRef import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.trace import net.corda.node.services.persistence.NodeAttachmentService import org.hibernate.query.criteria.internal.expression.LiteralExpression @@ -107,7 +107,7 @@ class HibernateAttachmentQueryCriteriaParser(override val criteriaBuilder: Crite AbstractQueryCriteriaParser(), AttachmentsQueryCriteriaParser { private companion object { - val log = loggerFor() + private val log = contextLogger() } init { @@ -170,7 +170,7 @@ class HibernateQueryCriteriaParser(val contractStateType: Class, val vaultStates: Root) : AbstractQueryCriteriaParser(), IQueryCriteriaParser { private companion object { - val log = loggerFor() + private val log = contextLogger() } // incrementally build list of join predicates diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt index 13023a6c68..c3c2e8e8b7 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt @@ -57,7 +57,7 @@ class NodeVaultService( hibernateConfig: HibernateConfiguration ) : SingletonSerializeAsToken(), VaultServiceInternal { private companion object { - val log = loggerFor() + private val log = contextLogger() } private class InnerState { diff --git a/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt b/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt index 8bf233589b..b57ac3a45c 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt @@ -4,17 +4,14 @@ import net.corda.core.contracts.FungibleAsset import net.corda.core.contracts.StateRef import net.corda.core.flows.FlowLogic import net.corda.core.node.services.VaultService -import net.corda.core.utilities.NonEmptySet -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.toNonEmptySet -import net.corda.core.utilities.trace +import net.corda.core.utilities.* import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.statemachine.StateMachineManager import java.util.* class VaultSoftLockManager private constructor(private val vault: VaultService) { companion object { - private val log = loggerFor() + private val log = contextLogger() @JvmStatic fun install(vault: VaultService, smm: StateMachineManager) { val manager = VaultSoftLockManager(vault) diff --git a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt index 9a7bf0630e..9e66fd4195 100644 --- a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt +++ b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt @@ -20,7 +20,6 @@ import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.DataFeed import net.corda.core.messaging.StateMachineUpdate import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import net.corda.node.internal.Node import net.corda.node.internal.StartedNode import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT @@ -48,6 +47,7 @@ import org.crsh.util.Utils import org.crsh.vfs.FS import org.crsh.vfs.spi.file.FileMountFactory import org.crsh.vfs.spi.url.ClassPathMountFactory +import org.slf4j.LoggerFactory import rx.Observable import rx.Subscriber import java.io.* @@ -73,7 +73,7 @@ import kotlin.concurrent.thread // TODO: Make it notice new shell commands added after the node started. object InteractiveShell { - private val log = loggerFor() + private val log = LoggerFactory.getLogger(javaClass) private lateinit var node: StartedNode @VisibleForTesting internal lateinit var database: CordaPersistence diff --git a/node/src/main/kotlin/net/corda/node/utilities/AffinityExecutor.kt b/node/src/main/kotlin/net/corda/node/utilities/AffinityExecutor.kt index 7a188044d1..89c7e68716 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/AffinityExecutor.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/AffinityExecutor.kt @@ -2,7 +2,6 @@ package net.corda.node.utilities import com.google.common.util.concurrent.SettableFuture import com.google.common.util.concurrent.Uninterruptibles -import net.corda.core.utilities.loggerFor import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.Executor @@ -55,10 +54,6 @@ interface AffinityExecutor : Executor { */ open class ServiceAffinityExecutor(threadName: String, numThreads: Int) : AffinityExecutor, ScheduledThreadPoolExecutor(numThreads) { - companion object { - val logger = loggerFor() - } - private val threads = Collections.synchronizedSet(HashSet()) init { diff --git a/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt b/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt index a90f77d171..90f2d018bb 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt @@ -1,6 +1,6 @@ package net.corda.node.utilities -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import java.util.* @@ -18,7 +18,7 @@ class AppendOnlyPersistentMap( ) { //TODO determine cacheBound based on entity class later or with node config allowing tuning, or using some heuristic based on heap size private companion object { - val log = loggerFor>() + private val log = contextLogger() } private val cache = NonInvalidatingCache>( diff --git a/node/src/main/kotlin/net/corda/node/utilities/PersistentMap.kt b/node/src/main/kotlin/net/corda/node/utilities/PersistentMap.kt index 11ace024ef..b0dbd2208b 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/PersistentMap.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/PersistentMap.kt @@ -4,7 +4,7 @@ package net.corda.node.utilities import com.google.common.cache.RemovalCause import com.google.common.cache.RemovalListener import com.google.common.cache.RemovalNotification -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import java.util.* @@ -19,7 +19,7 @@ class PersistentMap( ) : MutableMap, AbstractMap() { private companion object { - val log = loggerFor>() + private val log = contextLogger() } private val cache = NonInvalidatingUnboundCache( diff --git a/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt b/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt index 872892161b..051f68c63c 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt @@ -7,13 +7,12 @@ import net.corda.core.identity.Party import net.corda.core.internal.cert import net.corda.core.internal.createDirectories import net.corda.core.internal.div -import net.corda.core.utilities.loggerFor import net.corda.core.utilities.trace +import org.slf4j.LoggerFactory import java.nio.file.Path object ServiceIdentityGenerator { - private val log = loggerFor() - + private val log = LoggerFactory.getLogger(javaClass) /** * Generates signing key pairs and a common distributed service identity for a set of nodes. * The key pairs and the group identity get serialized to disk in the corresponding node directories. diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt index 8180350c74..30614d0cdf 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt @@ -5,8 +5,8 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import net.corda.finance.flows.CashIssueAndPaymentFlow import java.time.LocalDateTime import java.util.* @@ -23,7 +23,7 @@ class BankOfCordaWebApi(val rpc: CordaRPCOps) { val notaryName: CordaX500Name) private companion object { - val logger = loggerFor() + private val logger = contextLogger() } @GET diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt index ca9b841d9f..d924e41287 100644 --- a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt @@ -14,8 +14,8 @@ import net.corda.core.contracts.UniqueIdentifier import net.corda.core.identity.Party import net.corda.core.messaging.vaultTrackBy import net.corda.core.toFuture +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import net.corda.core.utilities.seconds import net.corda.finance.plugin.registerFinanceJSONMappers import net.corda.irs.contract.InterestRateSwap @@ -35,7 +35,7 @@ import java.time.LocalDate class IRSDemoTest { companion object { - val log = loggerFor() + private val log = contextLogger() } private val rpcUsers = listOf(User("user", "password", setOf("ALL"))) diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt index 204e981c70..4de01a0d78 100644 --- a/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt @@ -2,7 +2,7 @@ package net.corda.test.spring import net.corda.core.concurrent.CordaFuture import net.corda.core.internal.concurrent.map -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.testing.driver.* import net.corda.testing.internal.ProcessUtilities import net.corda.testing.node.NotarySpec @@ -61,7 +61,7 @@ fun springDriver( data class SpringBootDriverDSL(private val driverDSL: DriverDSL) : DriverDSLInternalInterface by driverDSL, SpringDriverInternalDSLInterface { companion object { - val log = loggerFor() + private val log = contextLogger() } override fun startSpringBootWebapp(clazz: Class<*>, handle: NodeHandle, checkUrl: String): CordaFuture { diff --git a/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/api/InterestSwapRestAPI.kt b/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/api/InterestSwapRestAPI.kt index 1c1a564d19..f242edace4 100644 --- a/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/api/InterestSwapRestAPI.kt +++ b/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/api/InterestSwapRestAPI.kt @@ -4,8 +4,8 @@ import net.corda.core.contracts.filterStatesOfType import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow import net.corda.core.messaging.vaultQueryBy +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import net.corda.irs.contract.InterestRateSwap import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.bind.annotation.* @@ -33,8 +33,9 @@ import net.corda.irs.flows.AutoOfferFlow @RestController @RequestMapping("/api/irs") class InterestRateSwapAPI { - - private val logger = loggerFor() + companion object { + private val logger = contextLogger() + } private fun generateDealLink(deal: InterestRateSwap.State) = "/api/irs/deals/" + deal.common.tradeID diff --git a/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/api/InterestRatesSwapDemoAPI.kt b/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/api/InterestRatesSwapDemoAPI.kt index 6a54266e28..c66ab8ec2d 100644 --- a/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/api/InterestRatesSwapDemoAPI.kt +++ b/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/api/InterestRatesSwapDemoAPI.kt @@ -2,8 +2,8 @@ package net.corda.irs.web.api import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import net.corda.irs.api.NodeInterestRates import net.corda.irs.flows.UpdateBusinessDayFlow import org.springframework.beans.factory.annotation.Autowired @@ -23,7 +23,7 @@ import java.time.ZoneId @RequestMapping("/api/irs") class InterestRatesSwapDemoAPI { companion object { - private val logger = loggerFor() + private val logger = contextLogger() } @Autowired diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/analytics/AnalyticsEngine.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/analytics/AnalyticsEngine.kt index a0bea3ea32..009049f754 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/analytics/AnalyticsEngine.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/analytics/AnalyticsEngine.kt @@ -16,7 +16,7 @@ import com.opengamma.strata.market.param.CurrencyParameterSensitivities import com.opengamma.strata.pricer.rate.ImmutableRatesProvider import com.opengamma.strata.pricer.swap.DiscountingSwapProductPricer import com.opengamma.strata.product.swap.ResolvedSwapTrade -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.vega.flows.toCordaCompatible import java.time.LocalDate @@ -63,7 +63,7 @@ abstract class AnalyticsEngine { class OGSIMMAnalyticsEngine : AnalyticsEngine() { private companion object { - val log = loggerFor() + private val log = contextLogger() } override fun curveGroup(): CurveGroupDefinition { diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemo.kt b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemo.kt index 43c82f5565..e519481e3a 100644 --- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemo.kt +++ b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemo.kt @@ -3,11 +3,10 @@ package net.corda.traderdemo import joptsimple.OptionParser import net.corda.client.rpc.CordaRPCClient import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.finance.DOLLARS import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_B -import org.slf4j.Logger import kotlin.system.exitProcess /** @@ -24,7 +23,7 @@ private class TraderDemo { } companion object { - val logger: Logger = loggerFor() + private val logger = contextLogger() val buyerName = DUMMY_BANK_A.name val sellerName = DUMMY_BANK_B.name val sellerRpcPort = 10009 diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/ShutdownManager.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/ShutdownManager.kt index 9ca4875c4c..0e8752aeb6 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/ShutdownManager.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/ShutdownManager.kt @@ -3,10 +3,7 @@ package net.corda.testing.driver import net.corda.core.concurrent.CordaFuture import net.corda.core.internal.ThreadBox import net.corda.core.internal.concurrent.doneFuture -import net.corda.core.utilities.Try -import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.seconds +import net.corda.core.utilities.* import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.TimeoutException @@ -21,8 +18,7 @@ class ShutdownManager(private val executorService: ExecutorService) { private val state = ThreadBox(State()) companion object { - private val log = loggerFor() - + private val log = contextLogger() inline fun run(providedExecutorService: ExecutorService? = null, block: ShutdownManager.() -> A): A { val executorService = providedExecutorService ?: Executors.newScheduledThreadPool(1) val shutdownManager = ShutdownManager(executorService) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 588eed3711..7aed47e4c6 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -20,8 +20,8 @@ import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SerializationWhitelist import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import net.corda.node.internal.AbstractNode import net.corda.node.internal.StartedNode import net.corda.node.internal.cordapp.CordappLoader @@ -43,7 +43,6 @@ import net.corda.testing.node.MockServices.Companion.makeTestDataSourcePropertie import net.corda.testing.setGlobalSerialization import net.corda.testing.testNodeConfiguration import org.apache.activemq.artemis.utils.ReusableLatch -import org.slf4j.Logger import java.io.Closeable import java.math.BigInteger import java.nio.file.Path @@ -214,13 +213,15 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete CordappLoader.createDefaultWithTestPackages(args.config, args.network.cordappPackages), args.network.busyLatch ) { + companion object { + private val staticLog = contextLogger() + } + val mockNet = args.network val id = args.id private val entropyRoot = args.entropyRoot var counter = entropyRoot - - override val log: Logger = loggerFor() - + override val log get() = staticLog override val serverThread: AffinityExecutor = if (mockNet.threadPerNode) { ServiceAffinityExecutor("Mock node $id thread", 1) diff --git a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt index 34c5a9d8cc..b6656977b7 100644 --- a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt +++ b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt @@ -6,7 +6,7 @@ import net.corda.core.internal.copyTo import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import java.nio.file.Path import java.nio.file.Paths import java.time.Instant @@ -22,7 +22,7 @@ class NodeProcess( private val client: CordaRPCClient ) : AutoCloseable { private companion object { - val log = loggerFor() + private val log = contextLogger() } fun connect(): CordaRPCConnection { diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpUtils.kt index b07d55db86..327f85193b 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpUtils.kt @@ -1,11 +1,11 @@ package net.corda.testing.http import com.fasterxml.jackson.databind.ObjectMapper -import net.corda.core.utilities.loggerFor import okhttp3.MediaType import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody +import org.slf4j.LoggerFactory import java.net.URL import java.util.concurrent.TimeUnit @@ -13,7 +13,7 @@ import java.util.concurrent.TimeUnit * A small set of utilities for making HttpCalls, aimed at demos and tests. */ object HttpUtils { - private val logger = loggerFor() + private val logger = LoggerFactory.getLogger(javaClass) private val client by lazy { OkHttpClient.Builder() .connectTimeout(5, TimeUnit.SECONDS) diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/explorer/Explorer.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/explorer/Explorer.kt index 286ad1e4c6..3c4aaebe84 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/explorer/Explorer.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/explorer/Explorer.kt @@ -3,7 +3,7 @@ package net.corda.demobench.explorer import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.internal.list -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.demobench.model.JVMConfig import net.corda.demobench.model.NodeConfig import net.corda.demobench.model.NodeConfigWrapper @@ -16,7 +16,7 @@ import java.util.concurrent.Executors class Explorer internal constructor(private val explorerController: ExplorerController) : AutoCloseable { private companion object { - val log = loggerFor() + private val log = contextLogger() } private val executor = Executors.newSingleThreadExecutor() diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/pty/R3Pty.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/pty/R3Pty.kt index cd82181367..fff015a4f2 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/pty/R3Pty.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/pty/R3Pty.kt @@ -5,7 +5,7 @@ import com.jediterm.terminal.ui.UIUtil import com.jediterm.terminal.ui.settings.SettingsProvider import com.pty4j.PtyProcess import net.corda.core.identity.CordaX500Name -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import java.awt.Dimension import java.io.IOException import java.nio.charset.StandardCharsets.UTF_8 @@ -14,7 +14,7 @@ import java.util.concurrent.TimeUnit.SECONDS class R3Pty(val name: CordaX500Name, settings: SettingsProvider, dimension: Dimension, val onExit: (Int) -> Unit) : AutoCloseable { private companion object { - val log = loggerFor() + private val log = contextLogger() } private val executor = Executors.newSingleThreadExecutor() diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/rpc/NodeRPC.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/rpc/NodeRPC.kt index a92dbd4a3d..c07dd6cef6 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/rpc/NodeRPC.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/rpc/NodeRPC.kt @@ -4,14 +4,14 @@ import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCConnection import net.corda.core.messaging.CordaRPCOps import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.demobench.model.NodeConfigWrapper import java.util.* import java.util.concurrent.TimeUnit.SECONDS class NodeRPC(config: NodeConfigWrapper, start: (NodeConfigWrapper, CordaRPCOps) -> Unit, invoke: (CordaRPCOps) -> Unit) : AutoCloseable { private companion object { - val log = loggerFor() + private val log = contextLogger() val oneSecond = SECONDS.toMillis(1) } diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/web/DBViewer.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/web/DBViewer.kt index 01cd406252..94908d6a31 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/web/DBViewer.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/web/DBViewer.kt @@ -1,6 +1,6 @@ package net.corda.demobench.web -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import org.h2.Driver import org.h2.server.web.LocalWebServer import org.h2.tools.Server @@ -11,7 +11,7 @@ import kotlin.reflect.jvm.jvmName class DBViewer : AutoCloseable { private companion object { - val log = loggerFor() + private val log = contextLogger() } private val webServer: Server diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt index 5fe5f96fe1..f3bfefdd17 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt @@ -4,7 +4,7 @@ import com.google.common.util.concurrent.RateLimiter import net.corda.core.concurrent.CordaFuture import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.until -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.minutes import net.corda.demobench.model.NodeConfigWrapper import net.corda.demobench.readErrorLines @@ -18,7 +18,7 @@ import kotlin.concurrent.thread class WebServer internal constructor(private val webServerController: WebServerController) : AutoCloseable { private companion object { - val log = loggerFor() + private val log = contextLogger() } private val executor = Executors.newSingleThreadExecutor() diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt index 1e3d0a2707..ee1495ebd6 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt @@ -124,7 +124,7 @@ class ExplorerSimulation(private val options: OptionSet) { fun FlowHandle.log(seq: Int, name: String) { val out = "[$seq] $name $id :" returnValue.thenMatch({ (stx) -> - Main.log.info("$out ${stx.id} ${(stx.tx.outputs.first().data as Cash.State).amount}") + Main.log.info("$out ${stx.id} ${(stx.tx.outputs.first().data as Cash.State).amount}") // XXX: Why Main's log? }, { Main.log.info("$out ${it.message}") }) diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt index 01346891aa..7e3f7fe7d0 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt @@ -11,7 +11,7 @@ import jfxtras.resources.JFXtrasFontRoboto import joptsimple.OptionParser import net.corda.client.jfx.model.Models import net.corda.client.jfx.model.observableValue -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.explorer.model.CordaViewModel import net.corda.explorer.model.SettingsModel import net.corda.explorer.views.* @@ -30,7 +30,7 @@ class Main : App(MainView::class) { private val fullscreen by observableValue(SettingsModel::fullscreenProperty) companion object { - val log = loggerFor
() + internal val log = contextLogger() } override fun start(stage: Stage) { diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/ConnectionManager.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/ConnectionManager.kt index fe3e4fc621..8e8cd14e0f 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/ConnectionManager.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/ConnectionManager.kt @@ -8,12 +8,12 @@ import com.jcraft.jsch.agentproxy.AgentProxy import com.jcraft.jsch.agentproxy.connector.SSHAgentConnector import com.jcraft.jsch.agentproxy.usocket.JNAUSocketFactory import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.loggerFor import net.corda.testing.driver.PortAllocation -import org.slf4j.LoggerFactory import java.util.* import kotlin.streams.toList -private val log = LoggerFactory.getLogger(ConnectionManager::class.java) +private val log = loggerFor() /** * Creates a new [JSch] instance with identities loaded from the running SSH agent. diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/Disruption.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/Disruption.kt index b704f50acd..e70a1f6621 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/Disruption.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/Disruption.kt @@ -1,12 +1,13 @@ package net.corda.loadtest import net.corda.client.mock.* +import net.corda.core.utilities.loggerFor import org.slf4j.LoggerFactory import java.util.* import java.util.concurrent.Callable import java.util.concurrent.Executors -private val log = LoggerFactory.getLogger(Disruption::class.java) +private val log = loggerFor() /** * A [Disruption] puts strain on the passed in node in some way. Each disruption runs in its own thread in a tight loop diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/LoadTest.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/LoadTest.kt index ed88325174..62fd304866 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/LoadTest.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/LoadTest.kt @@ -2,15 +2,15 @@ package net.corda.loadtest import com.google.common.util.concurrent.RateLimiter import net.corda.client.mock.Generator +import net.corda.core.utilities.loggerFor import net.corda.core.utilities.toBase58String import net.corda.testing.driver.PortAllocation -import org.slf4j.LoggerFactory import java.util.* import java.util.concurrent.Callable import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executors -private val log = LoggerFactory.getLogger(LoadTest::class.java) +private val log = loggerFor>() /** * @param T The type of generated object in the load test. This should describe the basic unit of execution, for example diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/NodeConnection.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/NodeConnection.kt index 9406fe0dad..620cc92d99 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/NodeConnection.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/NodeConnection.kt @@ -11,7 +11,7 @@ import net.corda.core.internal.concurrent.fork import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NodeInfo import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.nodeapi.internal.addShutdownHook import java.io.ByteArrayOutputStream import java.io.Closeable @@ -27,7 +27,7 @@ import java.util.concurrent.ForkJoinPool */ class NodeConnection(val remoteNode: RemoteNode, private val jSchSession: Session, private val localTunnelAddress: NetworkHostAndPort) : Closeable { companion object { - val log = loggerFor() + private val log = contextLogger() } init { diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/StabilityTest.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/StabilityTest.kt index 66379f6e16..e3460316cc 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/StabilityTest.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/StabilityTest.kt @@ -7,7 +7,6 @@ import net.corda.core.internal.concurrent.thenMatch import net.corda.core.messaging.startFlow import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import net.corda.finance.USD import net.corda.finance.flows.CashExitFlow import net.corda.finance.flows.CashExitFlow.ExitRequest @@ -16,10 +15,10 @@ import net.corda.finance.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest import net.corda.finance.flows.CashPaymentFlow import net.corda.finance.flows.CashPaymentFlow.PaymentRequest import net.corda.loadtest.LoadTest - +import org.slf4j.LoggerFactory object StabilityTest { - private val log = loggerFor() + private val log = LoggerFactory.getLogger(javaClass) fun crossCashTest(replication: Int) = LoadTest( "Creating Cash transactions", generate = { _, _ -> diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt index 1a53162b8b..bb4099000c 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt @@ -14,7 +14,7 @@ import net.corda.core.internal.div import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.transactions.LedgerTransaction import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.node.services.config.configureDevKeyAndTrustStores import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.ArtemisTcpTransport @@ -149,7 +149,7 @@ data class VerifierDriverDSL( val verifierCount = AtomicInteger(0) companion object { - private val log = loggerFor() + private val log = contextLogger() fun createConfiguration(baseDirectory: Path, nodeHostAndPort: NetworkHostAndPort): Config { return ConfigFactory.parseMap( mapOf( diff --git a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt index dea5bdcadf..7c04bf1ce9 100644 --- a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt +++ b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt @@ -7,10 +7,7 @@ import net.corda.core.internal.div import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.nodeSerializationEnv -import net.corda.core.utilities.ByteSequence -import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.* import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.VerifierApi @@ -39,8 +36,7 @@ data class VerifierConfiguration( class Verifier { companion object { - private val log = loggerFor() - + private val log = contextLogger() fun loadConfiguration(baseDirectory: Path, configPath: Path): VerifierConfiguration { val defaultConfig = ConfigFactory.parseResources("verifier-reference.conf", ConfigParseOptions.defaults().setAllowMissing(false)) val customConfig = ConfigFactory.parseFile(configPath.toFile(), ConfigParseOptions.defaults().setAllowMissing(false)) diff --git a/webserver/src/main/kotlin/net/corda/webserver/WebArgsParser.kt b/webserver/src/main/kotlin/net/corda/webserver/WebArgsParser.kt index 718678bb73..868ee24d18 100644 --- a/webserver/src/main/kotlin/net/corda/webserver/WebArgsParser.kt +++ b/webserver/src/main/kotlin/net/corda/webserver/WebArgsParser.kt @@ -75,7 +75,7 @@ data class CmdLineOptions(val baseDirectory: Path, .withFallback(appConfig) .withFallback(defaultConfig) .resolve() - val log = loggerFor() + val log = loggerFor() // I guess this is lazy so it happens after logging init. log.info("Config:\n${finalConfig.root().render(ConfigRenderOptions.defaults())}") return finalConfig } diff --git a/webserver/src/main/kotlin/net/corda/webserver/internal/AllExceptionMapper.kt b/webserver/src/main/kotlin/net/corda/webserver/internal/AllExceptionMapper.kt index 3a8687fcf5..3530b74e56 100644 --- a/webserver/src/main/kotlin/net/corda/webserver/internal/AllExceptionMapper.kt +++ b/webserver/src/main/kotlin/net/corda/webserver/internal/AllExceptionMapper.kt @@ -9,7 +9,7 @@ import javax.ws.rs.ext.Provider @Provider class AllExceptionMapper : ExceptionMapper { companion object { - val logger = loggerFor() + private val logger = loggerFor() // XXX: Really? } override fun toResponse(exception: Exception?): Response { diff --git a/webserver/src/main/kotlin/net/corda/webserver/internal/NodeWebServer.kt b/webserver/src/main/kotlin/net/corda/webserver/internal/NodeWebServer.kt index 8d5bf05a03..46bdf5a7ef 100644 --- a/webserver/src/main/kotlin/net/corda/webserver/internal/NodeWebServer.kt +++ b/webserver/src/main/kotlin/net/corda/webserver/internal/NodeWebServer.kt @@ -4,7 +4,7 @@ import com.google.common.html.HtmlEscapers.htmlEscaper import net.corda.client.jackson.JacksonSupport import net.corda.client.rpc.CordaRPCClient import net.corda.core.messaging.CordaRPCOps -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.webserver.WebServerConfig import net.corda.webserver.converters.CordaConverterProvider import net.corda.webserver.services.WebServerPluginRegistry @@ -32,7 +32,7 @@ import javax.ws.rs.core.MediaType class NodeWebServer(val config: WebServerConfig) { private companion object { - val log = loggerFor() + private val log = contextLogger() const val retryDelay = 1000L // Milliseconds } diff --git a/webserver/src/main/kotlin/net/corda/webserver/servlets/AttachmentDownloadServlet.kt b/webserver/src/main/kotlin/net/corda/webserver/servlets/AttachmentDownloadServlet.kt index e23d420059..e76d6633d2 100644 --- a/webserver/src/main/kotlin/net/corda/webserver/servlets/AttachmentDownloadServlet.kt +++ b/webserver/src/main/kotlin/net/corda/webserver/servlets/AttachmentDownloadServlet.kt @@ -3,7 +3,7 @@ package net.corda.webserver.servlets import net.corda.core.internal.extractFile import net.corda.core.crypto.SecureHash import net.corda.core.messaging.CordaRPCOps -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import java.io.FileNotFoundException import java.io.IOException import java.util.jar.JarInputStream @@ -25,7 +25,9 @@ import javax.ws.rs.core.MediaType * TODO: Provide an endpoint that exposes attachment file listings, to make attachments browsable. */ class AttachmentDownloadServlet : HttpServlet() { - private val log = loggerFor() + companion object { + private val log = contextLogger() + } @Throws(IOException::class) override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) { diff --git a/webserver/src/main/kotlin/net/corda/webserver/servlets/DataUploadServlet.kt b/webserver/src/main/kotlin/net/corda/webserver/servlets/DataUploadServlet.kt index e2c4c49e3d..d860d5d5a5 100644 --- a/webserver/src/main/kotlin/net/corda/webserver/servlets/DataUploadServlet.kt +++ b/webserver/src/main/kotlin/net/corda/webserver/servlets/DataUploadServlet.kt @@ -1,7 +1,7 @@ package net.corda.webserver.servlets import net.corda.core.messaging.CordaRPCOps -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import org.apache.commons.fileupload.servlet.ServletFileUpload import java.io.IOException import java.util.* @@ -13,7 +13,9 @@ import javax.servlet.http.HttpServletResponse * Uploads to the node via the [CordaRPCOps] uploadFile interface. */ class DataUploadServlet : HttpServlet() { - private val log = loggerFor() + companion object { + private val log = contextLogger() + } @Throws(IOException::class) override fun doPost(req: HttpServletRequest, resp: HttpServletResponse) { From 943fbd220bfae414fbd69ec22283b62a9e46358f Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Fri, 17 Nov 2017 14:34:20 +0000 Subject: [PATCH 20/31] RELEASE: Merge release notes for V2 back into master --- docs/source/release-notes.rst | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/docs/source/release-notes.rst b/docs/source/release-notes.rst index d0a7fa14fd..14d9a494cc 100644 --- a/docs/source/release-notes.rst +++ b/docs/source/release-notes.rst @@ -6,8 +6,24 @@ Here are release notes for each snapshot release from M9 onwards. Unreleased ---------- -Support for observer/regulator nodes has returned. Read :doc:`tutorial-observer-nodes` to learn more or examine the -interest rate swaps demo. +Release 2.0 +---------- +Following quickly on the heels of the release of Corda 1.0, Corda version 2.0 consolidates +a number of security updates for our dependent libraries alongside the reintroduction of the Observer node functionality. +This was absent from version 1 but based on user feedback its re-introduction removes the need for complicated "isRelevant()" checks. + +In addition the fix for a small bug present in the coin selection code of V1.0 is integrated from master. + +* **Version Bump** + +Due to the introduction of new APIs, Corda 2.0 has a platform version of 2. This will be advertised in the network map structures +and via the versioning APIs. + +* **Observer Nodes** + +Adds the facility for transparent forwarding of transactions to some third party observer, such as a regulator. By having +that entity simply run an Observer node they can simply recieve a stream of digitally signed, de-duplicated reports that +can be used for reporting. Release 1.0 ----------- @@ -32,8 +48,8 @@ unless an incompatible change is required for security reasons: Utilities and serialisers for working with JSON representations of basic types. Our extensive testing frameworks will continue to evolve alongside future Corda APIs. As part of our commitment to ease of use and modularity -we have introduced a new test node driver module to encapsulate all test functionality in support of building standalone node integration -tests using our DSL driver. +we have introduced a new test node driver module to encapsulate all test functionality in support of building standalone node integration +tests using our DSL driver. Please read :doc:`corda-api` for complete details. From 6413c66798cb1a0a7fa61214d81826ecb9c6c6da Mon Sep 17 00:00:00 2001 From: Viktor Kolomeyko Date: Mon, 20 Nov 2017 12:33:21 +0000 Subject: [PATCH 21/31] Change default driver permissions such that NodeExplorer works. (#2086) Also fix-up Kotlin doc. --- .../kotlin/net/corda/testing/driver/Driver.kt | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index db770d8524..269db1b6d0 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -71,10 +71,21 @@ private val DEFAULT_POLL_INTERVAL = 500.millis private const val DEFAULT_WARN_COUNT = 120 +/** + * A sub-set of permissions that grant most of the essential operations used in the unit/integration tests as well as + * in demo application like NodeExplorer. + */ private val DRIVER_REQUIRED_PERMISSIONS = setOf( invokeRpc(CordaRPCOps::nodeInfo), invokeRpc(CordaRPCOps::networkMapFeed), - invokeRpc(CordaRPCOps::networkMapSnapshot) + invokeRpc(CordaRPCOps::networkMapSnapshot), + invokeRpc(CordaRPCOps::notaryIdentities), + invokeRpc(CordaRPCOps::stateMachinesFeed), + invokeRpc(CordaRPCOps::stateMachineRecordedTransactionMappingFeed), + invokeRpc(CordaRPCOps::nodeInfoFromParty), + invokeRpc(CordaRPCOps::internalVerifiedTransactionsFeed), + invokeRpc("vaultQueryBy"), + invokeRpc("vaultTrackBy") ) /** @@ -145,7 +156,7 @@ interface DriverDSLExposedInterface : CordformContext { * Helper function for starting a [Node] with custom parameters from Java. * * @param parameters The default parameters for the driver. - * @return The value returned in the [dsl] closure. + * @return [NodeHandle] that will be available sometime in the future. */ fun startNode(parameters: NodeParameters): CordaFuture = startNode(defaultParameters = parameters) @@ -314,8 +325,8 @@ data class NodeParameters( * @param useTestClock If true the test clock will be used in Node. * @param startNodesInProcess Provides the default behaviour of whether new nodes should start inside this process or * not. Note that this may be overridden in [DriverDSLExposedInterface.startNode]. - * @param notarySpecs The notaries advertised in the [NetworkParameters] for this network. These nodes will be started - * automatically and will be available from [DriverDSLExposedInterface.notaryHandles]. Defaults to a simple validating notary. + * @param notarySpecs The notaries advertised for this network. These nodes will be started automatically and will be + * available from [DriverDSLExposedInterface.notaryHandles]. Defaults to a simple validating notary. * @param dsl The dsl itself. * @return The value returned in the [dsl] closure. */ From d3c7f909955b17c839a5ce094d339132e132c4bc Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Mon, 20 Nov 2017 16:06:49 +0000 Subject: [PATCH 22/31] RELEASE: docs dropdown update (#2092) --- docs/source/_static/versions | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/_static/versions b/docs/source/_static/versions index 1152a8e46a..b7932ed452 100644 --- a/docs/source/_static/versions +++ b/docs/source/_static/versions @@ -9,5 +9,6 @@ "https://docs.corda.net/releases/release-M13.0": "M13.0", "https://docs.corda.net/releases/release-M14.0": "M14.0", "https://docs.corda.net/releases/release-V1.0": "V1.0", + "https://docs.corda.net/releases/release-V2.0": "V2.0", "https://docs.corda.net/head/": "Master" } From d84e9aab7b7550bad078dc5f44f833e074f670bd Mon Sep 17 00:00:00 2001 From: Richard Green Date: Tue, 14 Nov 2017 16:57:59 +0000 Subject: [PATCH 23/31] Added exception if same attachment uploaded. Added test --- .../core/node/services/AttachmentStorage.kt | 14 +++ .../internal/cordapp/CordappProviderImpl.kt | 12 ++- .../persistence/NodeAttachmentService.kt | 102 +++++++++++------- .../net/corda/node/CordaRPCOpsImplTest.kt | 12 +++ .../testing/node/MockAttachmentStorage.kt | 35 ++++-- 5 files changed, 124 insertions(+), 51 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt b/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt index e7da510f23..9332587f7a 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt @@ -45,6 +45,13 @@ interface AttachmentStorage { @Throws(FileAlreadyExistsException::class, IOException::class) fun importAttachment(jar: InputStream, uploader: String, filename: String): AttachmentId + /** + * Inserts or returns Attachment Id of attachment. Does not throw an exception if already uploaded. + * @param jar [InputStream] of Jar file + * @return [AttachmentId] of uploaded attachment + */ + fun importOrGetAttachment(jar: InputStream): AttachmentId + /** * Searches attachment using given criteria and optional sort rules * @param criteria Query criteria to use as a filter @@ -53,5 +60,12 @@ interface AttachmentStorage { * @return List of AttachmentId of attachment matching criteria, sorted according to given sorting parameter */ fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort? = null): List + + /** + * Searches for an attachment already in the store + * @param attachmentId The attachment Id + * @return true if it's in there + */ + fun hasAttachment(attachmentId: AttachmentId): Boolean } diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt index ba305283f2..cfed6d0cd2 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt @@ -2,18 +2,24 @@ package net.corda.node.internal.cordapp import com.google.common.collect.HashBiMap import net.corda.core.contracts.ContractClassName -import net.corda.core.crypto.SecureHash -import net.corda.core.node.services.AttachmentStorage import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.CordappContext +import net.corda.core.crypto.SecureHash import net.corda.core.node.services.AttachmentId +import net.corda.core.node.services.AttachmentStorage import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.core.utilities.loggerFor import java.net.URL /** * Cordapp provider and store. For querying CorDapps for their attachment and vice versa. */ open class CordappProviderImpl(private val cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage) : SingletonSerializeAsToken(), CordappProviderInternal { + + companion object { + private val log = loggerFor() + } + override fun getAppContext(): CordappContext { // TODO: Use better supported APIs in Java 9 Exception().stackTrace.forEach { stackFrame -> @@ -45,7 +51,7 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader, attachm private fun loadContractsIntoAttachmentStore(attachmentStorage: AttachmentStorage): Map { val cordappsWithAttachments = cordapps.filter { !it.contractClassNames.isEmpty() }.map { it.jarPath } - val attachmentIds = cordappsWithAttachments.map { it.openStream().use { attachmentStorage.importAttachment(it) } } + val attachmentIds = cordappsWithAttachments.map { it.openStream().use { attachmentStorage.importOrGetAttachment(it) }} return attachmentIds.zip(cordappsWithAttachments).toMap() } diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt index 26f251b73e..6c4d4a9e75 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt @@ -1,18 +1,19 @@ package net.corda.node.services.persistence import com.codahale.metrics.MetricRegistry -import net.corda.core.internal.VisibleForTesting import com.google.common.hash.HashCode import com.google.common.hash.Hashing import com.google.common.hash.HashingInputStream import com.google.common.io.CountingInputStream import net.corda.core.CordaRuntimeException -import net.corda.core.contracts.* -import net.corda.core.internal.AbstractAttachment +import net.corda.core.contracts.Attachment import net.corda.core.crypto.SecureHash +import net.corda.core.internal.AbstractAttachment +import net.corda.core.internal.VisibleForTesting import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentStorage -import net.corda.core.node.services.vault.* +import net.corda.core.node.services.vault.AttachmentQueryCriteria +import net.corda.core.node.services.vault.AttachmentSort import net.corda.core.serialization.* import net.corda.core.utilities.contextLogger import net.corda.node.services.vault.HibernateAttachmentQueryCriteriaParser @@ -25,7 +26,6 @@ import java.time.Instant import java.util.jar.JarInputStream import javax.annotation.concurrent.ThreadSafe import javax.persistence.* -import javax.persistence.Column /** * Stores attachments using Hibernate to database. @@ -33,6 +33,29 @@ import javax.persistence.Column @ThreadSafe class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, SingletonSerializeAsToken() { + companion object { + private val log = contextLogger() + + // Just iterate over the entries with verification enabled: should be good enough to catch mistakes. + // Note that JarInputStream won't throw any kind of error at all if the file stream is in fact not + // a ZIP! It'll just pretend it's an empty archive, which is kind of stupid but that's how it works. + // So we have to check to ensure we found at least one item. + private fun checkIsAValidJAR(stream: InputStream) { + val jar = JarInputStream(stream, true) + var count = 0 + while (true) { + val cursor = jar.nextJarEntry ?: break + val entryPath = Paths.get(cursor.name) + // Security check to stop zips trying to escape their rightful place. + require(!entryPath.isAbsolute) { "Path $entryPath is absolute" } + require(entryPath.normalize() == entryPath) { "Path $entryPath is not normalised" } + require(!('\\' in cursor.name || cursor.name == "." || cursor.name == "..")) { "Bad character in $entryPath" } + count++ + } + require(count > 0) { "Stream is either empty or not a JAR/ZIP" } + } + } + @Entity @Table(name = "${NODE_DATABASE_PREFIX}attachments", indexes = arrayOf(Index(name = "att_id_idx", columnList = "att_id"))) @@ -55,10 +78,6 @@ class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, Single var filename: String? = null ) : Serializable - companion object { - private val log = contextLogger() - } - @VisibleForTesting var checkAttachmentsOnLoad = true @@ -170,6 +189,24 @@ class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, Single return import(jar, uploader, filename) } + fun getAttachmentIdAndBytes(jar: InputStream): Pair { + val hs = HashingInputStream(Hashing.sha256(), jar) + val bytes = hs.readBytes() + checkIsAValidJAR(ByteArrayInputStream(bytes)) + val id = SecureHash.SHA256(hs.hash().asBytes()) + return Pair(id, bytes) + } + + override fun hasAttachment(attachmentId: AttachmentId): Boolean { + val session = currentDBSession() + val criteriaBuilder = session.criteriaBuilder + val criteriaQuery = criteriaBuilder.createQuery(Long::class.java) + val attachments = criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java) + criteriaQuery.select(criteriaBuilder.count(criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java))) + criteriaQuery.where(criteriaBuilder.equal(attachments.get(DBAttachment::attId.name), attachmentId.toString())) + return (session.createQuery(criteriaQuery).singleResult > 0) + } + // TODO: PLT-147: The attachment should be randomised to prevent brute force guessing and thus privacy leaks. private fun import(jar: InputStream, uploader: String?, filename: String?): AttachmentId { require(jar !is JarInputStream) @@ -179,27 +216,28 @@ class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, Single // To do this we must pipe stream into the database without knowing its hash, which we will learn only once // the insert/upload is complete. We can then query to see if it's a duplicate and if so, erase, and if not // set the hash field of the new attachment record. - val hs = HashingInputStream(Hashing.sha256(), jar) - val bytes = hs.readBytes() - checkIsAValidJAR(ByteArrayInputStream(bytes)) - val id = SecureHash.SHA256(hs.hash().asBytes()) - val session = currentDBSession() - val criteriaBuilder = session.criteriaBuilder - val criteriaQuery = criteriaBuilder.createQuery(Long::class.java) - val attachments = criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java) - criteriaQuery.select(criteriaBuilder.count(criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java))) - criteriaQuery.where(criteriaBuilder.equal(attachments.get(DBAttachment::attId.name), id.toString())) - val count = session.createQuery(criteriaQuery).singleResult - if (count == 0L) { + val (id, bytes) = getAttachmentIdAndBytes(jar) + if (!hasAttachment(id)) { + checkIsAValidJAR(ByteArrayInputStream(bytes)) + val session = currentDBSession() val attachment = NodeAttachmentService.DBAttachment(attId = id.toString(), content = bytes, uploader = uploader, filename = filename) session.save(attachment) - attachmentCount.inc() log.info("Stored new attachment $id") + return id + } else { + throw java.nio.file.FileAlreadyExistsException(id.toString()) } + } - return id + override fun importOrGetAttachment(jar: InputStream): AttachmentId { + try { + return importAttachment(jar) + } + catch (faee: java.nio.file.FileAlreadyExistsException) { + return AttachmentId.parse(faee.message!!) + } } override fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort?): List { @@ -226,22 +264,4 @@ class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, Single } - private fun checkIsAValidJAR(stream: InputStream) { - // Just iterate over the entries with verification enabled: should be good enough to catch mistakes. - // Note that JarInputStream won't throw any kind of error at all if the file stream is in fact not - // a ZIP! It'll just pretend it's an empty archive, which is kind of stupid but that's how it works. - // So we have to check to ensure we found at least one item. - val jar = JarInputStream(stream, true) - var count = 0 - while (true) { - val cursor = jar.nextJarEntry ?: break - val entryPath = Paths.get(cursor.name) - // Security check to stop zips trying to escape their rightful place. - require(!entryPath.isAbsolute) { "Path $entryPath is absolute" } - require(entryPath.normalize() == entryPath) { "Path $entryPath is not normalised" } - require(!('\\' in cursor.name || cursor.name == "." || cursor.name == "..")) { "Bad character in $entryPath" } - count++ - } - require(count > 0) { "Stream is either empty or not a JAR/ZIP" } - } } diff --git a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt index 02e2f80dc4..5ddff0c2d5 100644 --- a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt @@ -241,6 +241,18 @@ class CordaRPCOpsImplTest { } } + @Test + fun `can't upload the same attachment`() { + withPermissions(invokeRpc(CordaRPCOps::uploadAttachment), invokeRpc(CordaRPCOps::attachmentExists)) { + assertThatExceptionOfType(java.nio.file.FileAlreadyExistsException::class.java).isThrownBy { + val inputJar1 = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar) + val inputJar2 = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar) + val secureHash1 = rpc.uploadAttachment(inputJar1) + val secureHash2 = rpc.uploadAttachment(inputJar2) + } + } + } + @Test fun `can download an uploaded attachment`() { withPermissions(invokeRpc(CordaRPCOps::uploadAttachment), invokeRpc(CordaRPCOps::openAttachment)) { diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockAttachmentStorage.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockAttachmentStorage.kt index 0550e1722e..c12e202b33 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockAttachmentStorage.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockAttachmentStorage.kt @@ -11,21 +11,25 @@ import net.corda.core.node.services.vault.AttachmentSort import net.corda.core.serialization.SingletonSerializeAsToken import java.io.ByteArrayOutputStream import java.io.InputStream -import java.util.HashMap +import java.util.* import java.util.jar.JarInputStream class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() { - - override fun importAttachment(jar: InputStream): AttachmentId { - // JIS makes read()/readBytes() return bytes of the current file, but we want to hash the entire container here. - require(jar !is JarInputStream) - - val bytes = run { + companion object { + fun getBytes(jar: InputStream) = run { val s = ByteArrayOutputStream() jar.copyTo(s) s.close() s.toByteArray() } + } + + override fun importAttachment(jar: InputStream): AttachmentId { + // JIS makes read()/readBytes() return bytes of the current file, but we want to hash the entire container here. + require(jar !is JarInputStream) + + val bytes = getBytes(jar) + val sha256 = bytes.sha256() if (!files.containsKey(sha256)) { files[sha256] = bytes @@ -49,4 +53,21 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() { override fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort?): List { throw NotImplementedError("Querying for attachments not implemented") } + + override fun hasAttachment(attachmentId: AttachmentId) = files.containsKey(attachmentId) + + fun getAttachmentIdAndBytes(jar: InputStream): Pair { + val bytes = getBytes(jar) + return Pair(bytes.sha256(), bytes) + } + + override fun importOrGetAttachment(jar: InputStream): AttachmentId { + try { + return importAttachment(jar) + } + catch (faee: java.nio.file.FileAlreadyExistsException) { + return AttachmentId.parse(faee.message!!) + } + } + } From 6a2c170b8202bb67f05357a31a85f2e9f521083d Mon Sep 17 00:00:00 2001 From: Rick Parker Date: Mon, 20 Nov 2017 16:49:17 +0000 Subject: [PATCH 24/31] Additional index to vault_states table to stop a table scan. (#2093) --- .../main/kotlin/net/corda/node/services/vault/VaultSchema.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt b/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt index e54f2cba23..bd1912ac22 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt @@ -29,7 +29,8 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio mappedTypes = listOf(VaultStates::class.java, VaultLinearStates::class.java, VaultFungibleStates::class.java, VaultTxnNote::class.java)) { @Entity @Table(name = "vault_states", - indexes = arrayOf(Index(name = "state_status_idx", columnList = "state_status"))) + indexes = arrayOf(Index(name = "state_status_idx", columnList = "state_status"), + Index(name = "lock_id_idx", columnList = "lock_id, state_status"))) class VaultStates( /** NOTE: serialized transaction state (including contract state) is now resolved from transaction store */ // TODO: create a distinct table to hold serialized state data (once DBTransactionStore is encrypted) From e63b6d13869547f99c0f5d3b40feaa280491237a Mon Sep 17 00:00:00 2001 From: Maksymilian Pawlak <120831+m4ksio@users.noreply.github.com> Date: Mon, 20 Nov 2017 17:41:38 +0000 Subject: [PATCH 25/31] CORDA-311 Shell via SSH server (#2087) * SSH server integration --- build.gradle | 1 + constants.properties | 2 +- .../kotlin/net/corda/core/flows/FlowLogic.kt | 14 + .../net/corda/core/messaging/FlowHandle.kt | 14 +- .../corda/core/utilities/ProgressTracker.kt | 88 +++-- .../core/utilities/ProgressTrackerTest.kt | 123 +++++++ docs/source/changelog.rst | 3 + docs/source/corda-configuration-file.rst | 5 + docs/source/hello-world-running.rst | 8 +- docs/source/node-administration.rst | 5 + docs/source/shell.rst | 42 ++- .../finance/flows/CashIssueAndPaymentFlow.kt | 2 +- .../src/main/kotlin/net/corda/plugins/Node.kt | 10 +- node/build.gradle | 11 +- .../kotlin/net/corda/node/SSHServerTest.kt | 170 ++++++++++ .../corda/node/shell/FlowShellCommand.java | 14 +- .../corda/node/shell/StartShellCommand.java | 5 +- .../net/corda/node/internal/AbstractNode.kt | 11 + .../corda/node/internal/CordaRPCOpsImpl.kt | 4 +- .../kotlin/net/corda/node/internal/Node.kt | 1 - .../net/corda/node/internal/NodeStartup.kt | 14 +- .../node/services/config/NodeConfiguration.kt | 7 +- .../node/services/messaging/RpcAuthContext.kt | 1 + .../node/shell/CordaAuthenticationPlugin.kt | 35 ++ .../net/corda/node/shell/CordaSSHAuthInfo.kt | 9 + .../net/corda/node/shell/InteractiveShell.kt | 127 ++++---- .../node/shell/InteractiveShellCommand.kt | 4 +- .../net/corda/node/shell/RPCOpsWithContext.kt | 210 ++++++++++++ .../node/utilities/ANSIProgressRenderer.kt | 302 +++++++++++------- .../net/corda/node/InteractiveShellTest.kt | 24 +- .../kotlin/net/corda/testing/driver/Driver.kt | 3 +- .../kotlin/net/corda/testing/node/MockNode.kt | 14 + 32 files changed, 1048 insertions(+), 235 deletions(-) create mode 100644 node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt create mode 100644 node/src/main/kotlin/net/corda/node/shell/CordaAuthenticationPlugin.kt create mode 100644 node/src/main/kotlin/net/corda/node/shell/CordaSSHAuthInfo.kt create mode 100644 node/src/main/kotlin/net/corda/node/shell/RPCOpsWithContext.kt diff --git a/build.gradle b/build.gradle index 878df9405e..58c7c31ec2 100644 --- a/build.gradle +++ b/build.gradle @@ -48,6 +48,7 @@ buildscript { ext.dependency_checker_version = '3.0.1' ext.commons_collections_version = '4.1' ext.beanutils_version = '1.9.3' + ext.crash_version = 'faba68332800f21278c5b600bf14ad55cef5989e' // Update 121 is required for ObjectInputFilter and at time of writing 131 was latest: ext.java8_minUpdateVersion = '131' diff --git a/constants.properties b/constants.properties index 214be59311..f278032d63 100644 --- a/constants.properties +++ b/constants.properties @@ -1,4 +1,4 @@ -gradlePluginsVersion=2.0.8 +gradlePluginsVersion=2.0.9 kotlinVersion=1.1.60 guavaVersion=21.0 bouncycastleVersion=1.57 diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt index 322a9f9925..8c9fba60de 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt @@ -348,6 +348,20 @@ abstract class FlowLogic { } } + fun trackStepsTreeIndex(): DataFeed? { + // TODO this is not threadsafe, needs an atomic get-step-and-subscribe + return progressTracker?.let { + DataFeed(it.stepsTreeIndex, it.stepsTreeIndexChanges) + } + } + + fun trackStepsTree(): DataFeed>, List>>? { + // TODO this is not threadsafe, needs an atomic get-step-and-subscribe + return progressTracker?.let { + DataFeed(it.allStepsLabels, it.stepsTreeChanges) + } + } + /** * Suspends the flow until the transaction with the specified ID is received, successfully verified and * sent to the vault for processing. Note that this call suspends until the transaction is considered diff --git a/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt b/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt index ee241d1924..90ba7e2596 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt @@ -31,6 +31,9 @@ interface FlowHandle : AutoCloseable { interface FlowProgressHandle : FlowHandle { val progress: Observable + val stepsTreeIndexFeed: DataFeed? + + val stepsTreeFeed: DataFeed>, List>>? /** * Use this function for flows whose returnValue and progress are not going to be used or tracked, so as to free up * server resources. @@ -52,10 +55,17 @@ data class FlowHandleImpl( } @CordaSerializable -data class FlowProgressHandleImpl( +data class FlowProgressHandleImpl @JvmOverloads constructor( override val id: StateMachineRunId, override val returnValue: CordaFuture, - override val progress: Observable) : FlowProgressHandle { + override val progress: Observable, + override val stepsTreeIndexFeed: DataFeed? = null, + override val stepsTreeFeed: DataFeed>, List>>? = null) : FlowProgressHandle { + + // For API compatibility + fun copy(id: StateMachineRunId, returnValue: CordaFuture, progress: Observable): FlowProgressHandleImpl { + return copy(id = id, returnValue = returnValue, progress = progress, stepsTreeFeed = null, stepsTreeIndexFeed = null) + } // Remember to add @Throws to FlowProgressHandle.close() if this throws an exception. override fun close() { diff --git a/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt b/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt index 6cd9526795..22661eeb7b 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt @@ -6,9 +6,6 @@ import rx.Subscription import rx.subjects.PublishSubject import java.util.* -// TODO: Expose the concept of errors. -// TODO: It'd be helpful if this class was at least partly thread safe. - /** * A progress tracker helps surface information about the progress of an operation to a user interface or API of some * kind. It lets you define a set of _steps_ that represent an operation. A step is represented by an object (typically @@ -34,16 +31,16 @@ import java.util.* @CordaSerializable class ProgressTracker(vararg steps: Step) { @CordaSerializable - sealed class Change { - data class Position(val tracker: ProgressTracker, val newStep: Step) : Change() { + sealed class Change(val progressTracker: ProgressTracker) { + data class Position(val tracker: ProgressTracker, val newStep: Step) : Change(tracker) { override fun toString() = newStep.label } - data class Rendering(val tracker: ProgressTracker, val ofStep: Step) : Change() { + data class Rendering(val tracker: ProgressTracker, val ofStep: Step) : Change(tracker) { override fun toString() = ofStep.label } - data class Structural(val tracker: ProgressTracker, val parent: Step) : Change() { + data class Structural(val tracker: ProgressTracker, val parent: Step) : Change(tracker) { override fun toString() = "Structural step change in child of ${parent.label}" } } @@ -70,17 +67,23 @@ class ProgressTracker(vararg steps: Step) { override fun equals(other: Any?) = other is DONE } - /** The steps in this tracker, same as the steps passed to the constructor but with UNSTARTED and DONE inserted. */ - val steps = arrayOf(UNSTARTED, *steps, DONE) - - // This field won't be serialized. - private val _changes by transient { PublishSubject.create() } - @CordaSerializable private data class Child(val tracker: ProgressTracker, @Transient val subscription: Subscription?) private val childProgressTrackers = mutableMapOf() + /** The steps in this tracker, same as the steps passed to the constructor but with UNSTARTED and DONE inserted. */ + val steps = arrayOf(UNSTARTED, *steps, DONE) + + private var _allStepsCache: List> = _allSteps() + + // This field won't be serialized. + private val _changes by transient { PublishSubject.create() } + private val _stepsTreeChanges by transient { PublishSubject.create>>() } + private val _stepsTreeIndexChanges by transient { PublishSubject.create() } + + + init { steps.forEach { val childTracker = it.childProgressTracker() @@ -92,7 +95,15 @@ class ProgressTracker(vararg steps: Step) { /** The zero-based index of the current step in the [steps] array (i.e. with UNSTARTED and DONE) */ var stepIndex: Int = 0 - private set + private set(value) { + field = value + } + + var stepsTreeIndex: Int = -1 + private set(value) { + field = value + _stepsTreeIndexChanges.onNext(value) + } /** * Reading returns the value of steps[stepIndex], writing moves the position of the current tracker. Once moved to @@ -118,22 +129,39 @@ class ProgressTracker(vararg steps: Step) { curChangeSubscription?.unsubscribe() stepIndex = index _changes.onNext(Change.Position(this, steps[index])) - curChangeSubscription = currentStep.changes.subscribe({ _changes.onNext(it) }, { _changes.onError(it) }) + recalculateStepsTreeIndex() + curChangeSubscription = currentStep.changes.subscribe({ + _changes.onNext(it) + if (it is Change.Structural || it is Change.Rendering) rebuildStepsTree() else recalculateStepsTreeIndex() + }, { _changes.onError(it) }) - if (currentStep == DONE) _changes.onCompleted() + if (currentStep == DONE) { + _changes.onCompleted() + _stepsTreeIndexChanges.onCompleted() + _stepsTreeChanges.onCompleted() + } } /** Returns the current step, descending into children to find the deepest step we are up to. */ val currentStepRecursive: Step get() = getChildProgressTracker(currentStep)?.currentStepRecursive ?: currentStep + private fun currentStepRecursiveWithoutUnstarted(): Step { + val stepRecursive = getChildProgressTracker(currentStep)?.currentStepRecursive + return if (stepRecursive == null || stepRecursive == UNSTARTED) currentStep else stepRecursive + } + fun getChildProgressTracker(step: Step): ProgressTracker? = childProgressTrackers[step]?.tracker fun setChildProgressTracker(step: ProgressTracker.Step, childProgressTracker: ProgressTracker) { - val subscription = childProgressTracker.changes.subscribe({ _changes.onNext(it) }, { _changes.onError(it) }) + val subscription = childProgressTracker.changes.subscribe({ + _changes.onNext(it) + if (it is Change.Structural || it is Change.Rendering) rebuildStepsTree() else recalculateStepsTreeIndex() + }, { _changes.onError(it) }) childProgressTrackers[step] = Child(childProgressTracker, subscription) childProgressTracker.parent = this _changes.onNext(Change.Structural(this, step)) + rebuildStepsTree() } private fun removeChildProgressTracker(step: ProgressTracker.Step) { @@ -142,6 +170,7 @@ class ProgressTracker(vararg steps: Step) { it.subscription?.unsubscribe() } _changes.onNext(Change.Structural(this, step)) + rebuildStepsTree() } /** @@ -166,6 +195,18 @@ class ProgressTracker(vararg steps: Step) { return cursor } + private fun rebuildStepsTree() { + _allStepsCache = _allSteps() + _stepsTreeChanges.onNext(allStepsLabels) + + recalculateStepsTreeIndex() + } + + private fun recalculateStepsTreeIndex() { + val step = currentStepRecursiveWithoutUnstarted() + stepsTreeIndex = _allStepsCache.indexOfFirst { it.second == step } + } + private fun _allSteps(level: Int = 0): List> { val result = ArrayList>() for (step in steps) { @@ -177,11 +218,15 @@ class ProgressTracker(vararg steps: Step) { return result } + private fun _allStepsLabels(level: Int = 0): List> = _allSteps(level).map { Pair(it.first, it.second.label) } + /** * A list of all steps in this ProgressTracker and the children, with the indent level provided starting at zero. * Note that UNSTARTED is never counted, and DONE is only counted at the calling level. */ - val allSteps: List> get() = _allSteps() + val allSteps: List> get() = _allStepsCache + + val allStepsLabels: List> get() = _allStepsLabels() private var curChangeSubscription: Subscription? = null @@ -200,8 +245,15 @@ class ProgressTracker(vararg steps: Step) { */ val changes: Observable get() = _changes + val stepsTreeChanges: Observable>> get() = _stepsTreeChanges + + val stepsTreeIndexChanges: Observable get() = _stepsTreeIndexChanges + /** Returns true if the progress tracker has ended, either by reaching the [DONE] step or prematurely with an error */ val hasEnded: Boolean get() = _changes.hasCompleted() || _changes.hasThrowable() } +// TODO: Expose the concept of errors. +// TODO: It'd be helpful if this class was at least partly thread safe. + diff --git a/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt b/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt index f307a2375e..9bcf5c0fb5 100644 --- a/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt +++ b/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt @@ -5,6 +5,7 @@ import org.junit.Test import java.util.* import kotlin.test.assertEquals import kotlin.test.assertFails +import org.assertj.core.api.Assertions.* class ProgressTrackerTest { object SimpleSteps { @@ -24,13 +25,23 @@ class ProgressTrackerTest { fun tracker() = ProgressTracker(AYY, BEE, SEA) } + object BabySteps { + object UNOS : ProgressTracker.Step("unos") + object DOES : ProgressTracker.Step("does") + object TRES : ProgressTracker.Step("tres") + + fun tracker() = ProgressTracker(UNOS, DOES, TRES) + } + lateinit var pt: ProgressTracker lateinit var pt2: ProgressTracker + lateinit var pt3: ProgressTracker @Before fun before() { pt = SimpleSteps.tracker() pt2 = ChildSteps.tracker() + pt3 = BabySteps.tracker() } @Test @@ -81,6 +92,118 @@ class ProgressTrackerTest { assertEquals(ChildSteps.BEE, pt2.nextStep()) } + @Test + fun `steps tree index counts children steps`() { + pt.setChildProgressTracker(SimpleSteps.TWO, pt2) + + val allSteps = pt.allSteps + + //capture notifications + val stepsIndexNotifications = LinkedList() + pt.stepsTreeIndexChanges.subscribe { + stepsIndexNotifications += it + } + val stepsTreeNotification = LinkedList>>() + pt.stepsTreeChanges.subscribe { + stepsTreeNotification += it + } + + fun assertCurrentStepsTree(index:Int, step: ProgressTracker.Step) { + assertEquals(index, pt.stepsTreeIndex) + assertEquals(step, allSteps[pt.stepsTreeIndex].second) + } + + //travel tree + pt.currentStep = SimpleSteps.ONE + assertCurrentStepsTree(0, SimpleSteps.ONE) + + pt.currentStep = SimpleSteps.TWO + assertCurrentStepsTree(1, SimpleSteps.TWO) + + pt2.currentStep = ChildSteps.BEE + assertCurrentStepsTree(3, ChildSteps.BEE) + + pt.currentStep = SimpleSteps.THREE + assertCurrentStepsTree(5, SimpleSteps.THREE) + + //assert no structure changes and proper steps propagation + assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(0, 1, 3, 5)) + assertThat(stepsTreeNotification).isEmpty() + } + + @Test + fun `structure changes are pushed down when progress trackers are added`() { + pt.setChildProgressTracker(SimpleSteps.TWO, pt2) + + //capture notifications + val stepsIndexNotifications = LinkedList() + pt.stepsTreeIndexChanges.subscribe { + stepsIndexNotifications += it + } + + //put current state as a first change for simplicity when asserting + val stepsTreeNotification = mutableListOf(pt.allStepsLabels) + println(pt.allStepsLabels) + pt.stepsTreeChanges.subscribe { + stepsTreeNotification += it + } + + fun assertCurrentStepsTree(index:Int, step: ProgressTracker.Step) { + assertEquals(index, pt.stepsTreeIndex) + assertEquals(step.label, stepsTreeNotification.last()[pt.stepsTreeIndex].second) + } + + pt.currentStep = SimpleSteps.TWO + assertCurrentStepsTree(1, SimpleSteps.TWO) + + pt.currentStep = SimpleSteps.FOUR + assertCurrentStepsTree(6, SimpleSteps.FOUR) + + + pt.setChildProgressTracker(SimpleSteps.THREE, pt3) + + assertCurrentStepsTree(9, SimpleSteps.FOUR) + + //assert no structure changes and proper steps propagation + assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(1, 6, 9)) + assertThat(stepsTreeNotification).hasSize(2) // 1 change + 1 our initial state + } + + @Test + fun `structure changes are pushed down when progress trackers are removed`() { + pt.setChildProgressTracker(SimpleSteps.TWO, pt2) + + //capture notifications + val stepsIndexNotifications = LinkedList() + pt.stepsTreeIndexChanges.subscribe { + stepsIndexNotifications += it + } + + //put current state as a first change for simplicity when asserting + val stepsTreeNotification = mutableListOf(pt.allStepsLabels) + pt.stepsTreeChanges.subscribe { + stepsTreeNotification += it + } + + fun assertCurrentStepsTree(index:Int, step: ProgressTracker.Step) { + assertEquals(index, pt.stepsTreeIndex) + assertEquals(step.label, stepsTreeNotification.last()[pt.stepsTreeIndex].second) + } + + pt.currentStep = SimpleSteps.TWO + pt2.currentStep = ChildSteps.SEA + pt3.currentStep = BabySteps.UNOS + assertCurrentStepsTree(4, ChildSteps.SEA) + + pt.setChildProgressTracker(SimpleSteps.TWO, pt3) + + assertCurrentStepsTree(2, BabySteps.UNOS) + + //assert no structure changes and proper steps propagation + assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(1, 4, 2)) + assertThat(stepsTreeNotification).hasSize(2) // 1 change + 1 our initial state + } + @Test fun `can be rewound`() { pt.setChildProgressTracker(SimpleSteps.TWO, pt2) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 11b2632071..a8986d15eb 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -6,9 +6,12 @@ from the previous milestone release. UNRELEASED ---------- + * ``AttachmentStorage`` now allows providing metadata on attachments upload - username and filename, currently as plain strings. Those can be then used for querying, utilizing ``queryAttachments`` method of the same interface. +* ``SSH Server`` - The node can now expose shell via SSH server with proper authorization and permissioning built in. + * ``CordaRPCOps`` implementation now checks permissions for any function invocation, rather than just when starting flows. * ``wellKnownPartyFromAnonymous()`` now always resolve the key to a ``Party``, then the party to the well known party. diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index 4df92c64b4..0531431cc5 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -152,3 +152,8 @@ path to the node's base directory. :jarDirs: An optional list of file system directories containing JARs to include in the classpath when launching via ``corda.jar`` only. Each should be a string. Only the JARs in the directories are added, not the directories themselves. This is useful for including JDBC drivers and the like. e.g. ``jarDirs = [ 'lib' ]`` + +:sshd: If provided, node will start internal SSH server which will provide a management shell. It uses the same credentials + and permissions as RPC subsystem. It has one required parameter. + + :port: - the port to start SSH server on diff --git a/docs/source/hello-world-running.rst b/docs/source/hello-world-running.rst index 5504b83b65..17d506471d 100644 --- a/docs/source/hello-world-running.rst +++ b/docs/source/hello-world-running.rst @@ -33,15 +33,16 @@ service. rpcPort 10006 webPort 10007 cordapps = ["net.corda:corda-finance:$corda_release_version"] - rpcUsers = [[ user: "user1", "password": "test", "permissions": []]] + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL]]] } node { name "O=PartyB,L=New York,C=US" p2pPort 10008 rpcPort 10009 webPort 10010 + sshdPort 10024 cordapps = ["net.corda:corda-finance:$corda_release_version"] - rpcUsers = [[ user: "user1", "password": "test", "permissions": []]] + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] } } @@ -101,6 +102,9 @@ node via its built-in CRaSH shell. Go to the terminal window displaying the CRaSH shell of PartyA. Typing ``help`` will display a list of the available commands. +.. note:: Local terminal shell is available only in a development mode. In production environment SSH server can be enabled. + More about SSH and how to connect can be found on :doc:`Shell` page. + We want to create an IOU of 100 with PartyB. We start the ``IOUFlow`` by typing: .. code:: bash diff --git a/docs/source/node-administration.rst b/docs/source/node-administration.rst index 1b69ad9eae..7a677c34d2 100644 --- a/docs/source/node-administration.rst +++ b/docs/source/node-administration.rst @@ -53,6 +53,11 @@ reserve the right to move and rename it as it's not part of the public API as ye logging name construction. If you can't find what you need to refer to, use the ``--logging-level`` option as above and then determine the logging module name from the console output. +SSH access +---------- + +Node can be configured to run SSH server. See :doc:`shell` for details. + Database access --------------- diff --git a/docs/source/shell.rst b/docs/source/shell.rst index cdddc4548e..fe74e44f29 100644 --- a/docs/source/shell.rst +++ b/docs/source/shell.rst @@ -18,11 +18,47 @@ Some of its features include: * View JMX metrics and monitoring exports. * UNIX style pipes for both text and objects, an ``egrep`` command and a command for working with columnular data. -.. note:: A future version of Corda will add SSH access to the node. - It is based on the popular `CRaSH`_ shell used in various other projects and supports many of the same features. -The shell may be disabled by passing the ``--no-local-shell`` flag to the node. +Local terminal shell runs only in development mode. It may be disabled by passing the ``--no-local-shell`` flag to the node. + +SSH server +---------- + +Shell can also be accessible via SSH. By default SSH server is *disabled*. To enable it port must be configured - in ``node.conf`` file + +.. code:: bash + + sshd { + port = 2222 + } + +Authentication and authorization +-------------------------------- +SSH require user to login first - using the same users as RPC system. In fact, shell serves as a proxy to RPC and communicates +with node using RPC calls. This also means that RPC permissions are enforced. No permissions are required to allow the connection +and login in. +Watching flows (``flow watch``) requires ``InvokeRpc.stateMachinesFeed`` while starting flows requires +``InvokeRpc.startTrackedFlowDynamic`` and ``InvokeRpc.registeredFlows`` in addition to a permission for a particular flow. + +Host key +-------- + +The host key is loaded from ``sshkey/hostkey.pem`` file. If the file does not exist, it will be generated randomly, however +in the development mode seed may be tuned to give the same results on the same computer - in order to avoid host checking +errors. + +Connecting +---------- + +Linux and MacOS computers usually come with SSH client preinstalled. On Windows it usually require extra download. +Usual connection syntax is ``ssh user@host -p 2222`` - where ``user`` is a RPC username, and ``-p`` specifies a port parameters - +it's the same as setup in ``node.conf`` file. ``host`` should point to a node hostname, usually ``localhost`` if connecting and +running node on the same computer. Password will be asked after establishing connection. + +:note: While developing, checking multiple samples or simply restarting a node frequently host key may be regenerated. SSH usually + saved once trusted hosts and will refuse to connect in case of a change. Then check may be disabled with extra options + ``ssh -o StrictHostKeyChecking=no user@host -p2222``. This option should never be used in production environment! Getting help ------------ diff --git a/finance/src/main/kotlin/net/corda/finance/flows/CashIssueAndPaymentFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/CashIssueAndPaymentFlow.kt index 95a58e8cfb..04a3c1cd90 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/CashIssueAndPaymentFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/CashIssueAndPaymentFlow.kt @@ -27,7 +27,7 @@ class CashIssueAndPaymentFlow(val amount: Amount, val anonymous: Boolean, val notary: Party, progressTracker: ProgressTracker) : AbstractCashFlow(progressTracker) { - constructor(amount: Amount, + constructor(amount: Amount, issueRef: OpaqueBytes, recipient: Party, anonymous: Boolean, diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt index f03e650772..9358a293b5 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt @@ -96,15 +96,15 @@ class Node(private val project: Project) : CordformNode() { } /** - * Set the SSHD port for this node. + * Enables SSH access on given port * - * @param sshdPort The SSHD port. + * @param sshdPort The port for SSH server to listen on */ - fun sshdPort(sshdPort: Int) { - config = config.withValue("sshdAddress", - ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$sshdPort")) + fun sshdPort(sshdPort: Int?) { + config = config.withValue("sshd.port", ConfigValueFactory.fromAnyRef(sshdPort)) } + internal fun build() { configureProperties() installCordaJar() diff --git a/node/build.gradle b/node/build.gradle index e1896cfd28..7e9adec300 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -139,8 +139,14 @@ dependencies { compile "io.netty:netty-all:$netty_version" // CRaSH: An embeddable monitoring and admin shell with support for adding new commands written in Groovy. - compile("com.github.corda.crash:crash.shell:d5da86ba1b38e9c33af2a621dd15ba286307bec4") { + compile("com.github.corda.crash:crash.shell:$crash_version") { exclude group: "org.slf4j", module: "slf4j-jdk14" + exclude group: "org.bouncycastle" + } + + compile("com.github.corda.crash:crash.connectors.ssh:$crash_version") { + exclude group: "org.slf4j", module: "slf4j-jdk14" + exclude group: "org.bouncycastle" } // OkHTTP: Simple HTTP library. @@ -157,6 +163,9 @@ dependencies { integrationTestCompile "junit:junit:$junit_version" integrationTestCompile "org.assertj:assertj-core:${assertj_version}" + // Jsh: Testing SSH server + integrationTestCompile group: 'com.jcraft', name: 'jsch', version: '0.1.54' + // Jetty dependencies for NetworkMapClient test. // Web stuff: for HTTP[S] servlets testCompile "org.eclipse.jetty:jetty-servlet:${jetty_version}" diff --git a/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt b/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt new file mode 100644 index 0000000000..e7346a1932 --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt @@ -0,0 +1,170 @@ +package net.corda.node + +import co.paralleluniverse.fibers.Suspendable +import com.jcraft.jsch.ChannelExec +import com.jcraft.jsch.JSch +import com.jcraft.jsch.JSchException +import net.corda.core.flows.* +import net.corda.core.identity.Party +import net.corda.core.utilities.ProgressTracker +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.unwrap +import net.corda.nodeapi.User +import net.corda.testing.ALICE +import net.corda.testing.driver.driver +import org.bouncycastle.util.io.Streams +import org.junit.Test +import net.corda.node.services.Permissions.Companion.startFlow +import java.net.ConnectException +import kotlin.test.assertTrue +import kotlin.test.fail +import org.assertj.core.api.Assertions.assertThat +import java.util.regex.Pattern + +class SSHServerTest { + + @Test() + fun `ssh server does not start be default`() { + val user = User("u", "p", setOf()) + // The driver will automatically pick up the annotated flows below + driver() { + val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user)) + node.getOrThrow() + + val session = JSch().getSession("u", "localhost", 2222) + session.setConfig("StrictHostKeyChecking", "no") + session.setPassword("p") + + try { + session.connect() + fail() + } catch (e:JSchException) { + assertTrue(e.cause is ConnectException) + } + } + } + + @Test + fun `ssh server starts when configured`() { + val user = User("u", "p", setOf()) + // The driver will automatically pick up the annotated flows below + driver { + val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user), + customOverrides = mapOf("sshd" to mapOf("port" to 2222))) + node.getOrThrow() + + val session = JSch().getSession("u", "localhost", 2222) + session.setConfig("StrictHostKeyChecking", "no") + session.setPassword("p") + + session.connect() + + assertTrue(session.isConnected) + } + } + + + @Test + fun `ssh server verify credentials`() { + val user = User("u", "p", setOf()) + // The driver will automatically pick up the annotated flows below + driver { + val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user), + customOverrides = mapOf("sshd" to mapOf("port" to 2222))) + node.getOrThrow() + + val session = JSch().getSession("u", "localhost", 2222) + session.setConfig("StrictHostKeyChecking", "no") + session.setPassword("p_is_bad_password") + + try { + session.connect() + fail("Server should reject invalid credentials") + } catch (e: JSchException) { + //There is no specialized exception for this + assertTrue(e.message == "Auth fail") + } + } + } + + @Test + fun `ssh respects permissions`() { + val user = User("u", "p", setOf(startFlow())) + // The driver will automatically pick up the annotated flows below + driver(isDebug = true) { + val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user), + customOverrides = mapOf("sshd" to mapOf("port" to 2222))) + node.getOrThrow() + + val session = JSch().getSession("u", "localhost", 2222) + session.setConfig("StrictHostKeyChecking", "no") + session.setPassword("p") + session.connect() + + assertTrue(session.isConnected) + + val channel = session.openChannel("exec") as ChannelExec + channel.setCommand("start FlowICannotRun otherParty: \"O=Alice Corp,L=Madrid,C=ES\"") + channel.connect() + val response = String(Streams.readAll(channel.inputStream)) + + val flowNameEscaped = Pattern.quote("StartFlow.${SSHServerTest::class.qualifiedName}$${FlowICannotRun::class.simpleName}") + + channel.disconnect() + session.disconnect() + + assertThat(response).matches("(?s)User not permissioned with any of \\[[^]]*${flowNameEscaped}.*") + } + } + + @Test + fun `ssh runs flows`() { + val user = User("u", "p", setOf(startFlow())) + // The driver will automatically pick up the annotated flows below + driver(isDebug = true) { + val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user), + customOverrides = mapOf("sshd" to mapOf("port" to 2222))) + node.getOrThrow() + + val session = JSch().getSession("u", "localhost", 2222) + session.setConfig("StrictHostKeyChecking", "no") + session.setPassword("p") + session.connect() + + assertTrue(session.isConnected) + + val channel = session.openChannel("exec") as ChannelExec + channel.setCommand("start FlowICanRun") + channel.connect() + + val response = String(Streams.readAll(channel.inputStream)) + + //There are ANSI control characters involved, so we want to avoid direct byte to byte matching + assertThat(response.lines()).filteredOn( { it.contains("✓") && it.contains("Done")}).hasSize(1) + } + } + + @StartableByRPC + @InitiatingFlow + class FlowICanRun : FlowLogic() { + + private val HELLO_STEP = ProgressTracker.Step("Hello") + + @Suspendable + override fun call(): String { + progressTracker?.currentStep = HELLO_STEP + return "bambam" + } + + override val progressTracker: ProgressTracker? = ProgressTracker(HELLO_STEP) + } + + @StartableByRPC + @InitiatingFlow + class FlowICannotRun(val otherParty: Party) : FlowLogic() { + @Suspendable + override fun call(): String = initiateFlow(otherParty).receive().unwrap { it } + + override val progressTracker: ProgressTracker? = ProgressTracker() + } +} \ No newline at end of file diff --git a/node/src/main/java/net/corda/node/shell/FlowShellCommand.java b/node/src/main/java/net/corda/node/shell/FlowShellCommand.java index 4bac0ce5b5..d3ed752531 100644 --- a/node/src/main/java/net/corda/node/shell/FlowShellCommand.java +++ b/node/src/main/java/net/corda/node/shell/FlowShellCommand.java @@ -2,6 +2,9 @@ package net.corda.node.shell; // See the comments at the top of run.java +import net.corda.core.messaging.CordaRPCOps; +import net.corda.node.utilities.ANSIProgressRenderer; +import net.corda.node.utilities.CRaSHNSIProgressRenderer; import org.crsh.cli.*; import org.crsh.command.*; import org.crsh.text.*; @@ -9,6 +12,7 @@ import org.crsh.text.ui.TableElement; import java.util.*; +import static net.corda.node.services.messaging.RPCServerKt.CURRENT_RPC_CONTEXT; import static net.corda.node.shell.InteractiveShell.*; @Man( @@ -25,25 +29,27 @@ public class FlowShellCommand extends InteractiveShellCommand { @Usage("The class name of the flow to run, or an unambiguous substring") @Argument String name, @Usage("The data to pass as input") @Argument(unquote = false) List input ) { - startFlow(name, input, out); + startFlow(name, input, out, ops(), ansiProgressRenderer()); } // TODO Limit number of flows shown option? @Command @Usage("watch information about state machines running on the node with result information") public void watch(InvocationContext context) throws Exception { - runStateMachinesView(out); + runStateMachinesView(out, ops()); } static void startFlow(@Usage("The class name of the flow to run, or an unambiguous substring") @Argument String name, @Usage("The data to pass as input") @Argument(unquote = false) List input, - RenderPrintWriter out) { + RenderPrintWriter out, + CordaRPCOps rpcOps, + ANSIProgressRenderer ansiProgressRenderer) { if (name == null) { out.println("You must pass a name for the flow, see 'man flow'", Color.red); return; } String inp = input == null ? "" : String.join(" ", input).trim(); - runFlowByNameFragment(name, inp, out); + runFlowByNameFragment(name, inp, out, rpcOps, ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHNSIProgressRenderer(out) ); } @Command diff --git a/node/src/main/java/net/corda/node/shell/StartShellCommand.java b/node/src/main/java/net/corda/node/shell/StartShellCommand.java index 3ec2e8e2ee..e1c91ebb75 100644 --- a/node/src/main/java/net/corda/node/shell/StartShellCommand.java +++ b/node/src/main/java/net/corda/node/shell/StartShellCommand.java @@ -2,6 +2,8 @@ package net.corda.node.shell; // A simple forwarder to the "flow start" command, for easier typing. +import net.corda.node.utilities.ANSIProgressRenderer; +import net.corda.node.utilities.CRaSHNSIProgressRenderer; import org.crsh.cli.*; import java.util.*; @@ -11,6 +13,7 @@ public class StartShellCommand extends InteractiveShellCommand { @Man("An alias for 'flow start'. Example: \"start Yo target: Some other company\"") public void main(@Usage("The class name of the flow to run, or an unambiguous substring") @Argument String name, @Usage("The data to pass as input") @Argument(unquote = false) List input) { - FlowShellCommand.startFlow(name, input, out); + ANSIProgressRenderer ansiProgressRenderer = ansiProgressRenderer(); + FlowShellCommand.startFlow(name, input, out, ops(), ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHNSIProgressRenderer(out)); } } diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 14a8179d19..d6aad5cf45 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -36,6 +36,7 @@ import net.corda.node.internal.cordapp.CordappProviderInternal import net.corda.node.services.ContractUpgradeHandler import net.corda.node.services.FinalityHandler import net.corda.node.services.NotaryChangeHandler +import net.corda.node.services.RPCUserService import net.corda.node.services.api.* import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NodeConfiguration @@ -55,6 +56,7 @@ import net.corda.node.services.transactions.* import net.corda.node.services.upgrade.ContractUpgradeServiceImpl import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.VaultSoftLockManager +import net.corda.node.shell.InteractiveShell import net.corda.node.utilities.* import org.apache.activemq.artemis.utils.ReusableLatch import org.slf4j.Logger @@ -130,6 +132,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration, protected val _nodeReadyFuture = openFuture() protected val networkMapClient: NetworkMapClient? by lazy { configuration.compatibilityZoneURL?.let(::NetworkMapClient) } + lateinit var userService: RPCUserService get + /** Completes once the node has successfully registered with the network map service * or has loaded network map data from local database */ val nodeReadyFuture: CordaFuture @@ -213,6 +217,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration, FlowLogicRefFactoryImpl.classloader = cordappLoader.appClassLoader runOnStop += network::stop + + startShell(rpcOps) + Pair(StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, network, database, rpcOps, flowStarter, notaryService), schedulerService) } @@ -243,6 +250,10 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } } + open fun startShell(rpcOps: CordaRPCOps) { + InteractiveShell.startShell(configuration, rpcOps, userService, _services.identityService, _services.database) + } + private fun initNodeInfo(): Pair, NodeInfo> { val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null) val keyPairs = mutableSetOf(identityKeyPair) diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt index d28c8b67ab..660ce4b767 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -142,7 +142,9 @@ internal class CordaRPCOpsImpl( return FlowProgressHandleImpl( id = stateMachine.id, returnValue = stateMachine.resultFuture, - progress = stateMachine.logic.track()?.updates ?: Observable.empty() + progress = stateMachine.logic.track()?.updates ?: Observable.empty(), + stepsTreeIndexFeed = stateMachine.logic.trackStepsTreeIndex(), + stepsTreeFeed = stateMachine.logic.trackStepsTree() ) } diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index 99af1e7965..2f378ed7ba 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -131,7 +131,6 @@ open class Node(configuration: NodeConfiguration, private var shutdownHook: ShutdownHook? = null - private lateinit var userService: RPCUserService override fun makeMessagingService(database: CordaPersistence, info: NodeInfo): MessagingService { userService = RPCUserServiceImpl(configuration.rpcUsers) diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index 4ed9546140..2947700025 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -117,12 +117,13 @@ open class NodeStartup(val args: Array) { Node.printBasicNodeInfo("Node for \"$name\" started up and registered in $elapsed sec") // Don't start the shell if there's no console attached. - val runShell = !cmdlineOptions.noLocalShell && System.console() != null - startedNode.internals.startupComplete.then { - try { - InteractiveShell.startShell(cmdlineOptions.baseDirectory, runShell, cmdlineOptions.sshdServer, startedNode) - } catch (e: Throwable) { - logger.error("Shell failed to start", e) + if (!cmdlineOptions.noLocalShell && System.console() != null && conf.devMode) { + startedNode.internals.startupComplete.then { + try { + InteractiveShell.runLocalShell(startedNode) + } catch (e: Throwable) { + logger.error("Shell failed to start", e) + } } } }, @@ -317,7 +318,6 @@ open class NodeStartup(val args: Array) { a("--- ${versionInfo.vendor} ${versionInfo.releaseVersion} (${versionInfo.revision.take(7)}) -----------------------------------------------"). newline(). newline(). - newline(). reset()) } } diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index cffde21e21..607f3927b8 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -39,6 +39,7 @@ interface NodeConfiguration : NodeSSLConfiguration { // TODO Move into DevModeOptions val useTestClock: Boolean get() = false val detectPublicIp: Boolean get() = true + val sshd: SSHDConfiguration? } fun NodeConfiguration.shouldCheckCheckpoints(): Boolean { @@ -109,7 +110,9 @@ data class NodeConfigurationImpl( override val detectPublicIp: Boolean = true, override val activeMQServer: ActiveMqServerConfiguration, // TODO See TODO above. Rename this to nodeInfoPollingFrequency and make it of type Duration - override val additionalNodeInfoPollingFrequencyMsec: Long = 5.seconds.toMillis() + override val additionalNodeInfoPollingFrequencyMsec: Long = 5.seconds.toMillis(), + override val sshd: SSHDConfiguration? = null + ) : NodeConfiguration { override val exportJMXto: String get() = "http" @@ -144,3 +147,5 @@ data class CertChainPolicyConfig(val role: String, private val policy: CertChain } } } + +data class SSHDConfiguration(val port: Int) \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RpcAuthContext.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RpcAuthContext.kt index 8daa20f2a3..58cd73de22 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RpcAuthContext.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RpcAuthContext.kt @@ -23,6 +23,7 @@ data class RpcPermissions(private val values: Set = emptySet()) { companion object { val NONE = RpcPermissions() + val ALL = RpcPermissions(setOf("ALL")) } fun coverAny(permissions: Set) = !values.intersect(permissions + Permissions.all()).isEmpty() diff --git a/node/src/main/kotlin/net/corda/node/shell/CordaAuthenticationPlugin.kt b/node/src/main/kotlin/net/corda/node/shell/CordaAuthenticationPlugin.kt new file mode 100644 index 0000000000..9854a9caf4 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/shell/CordaAuthenticationPlugin.kt @@ -0,0 +1,35 @@ +package net.corda.node.shell + +import net.corda.core.context.Actor +import net.corda.core.context.InvocationContext +import net.corda.core.identity.CordaX500Name +import net.corda.core.messaging.CordaRPCOps +import net.corda.node.services.RPCUserService +import net.corda.node.services.messaging.RpcPermissions +import org.crsh.auth.AuthInfo +import org.crsh.auth.AuthenticationPlugin +import org.crsh.plugin.CRaSHPlugin + +class CordaAuthenticationPlugin(val rpcOps:CordaRPCOps, val userService:RPCUserService, val nodeLegalName:CordaX500Name) : CRaSHPlugin>(), AuthenticationPlugin { + + override fun getImplementation(): AuthenticationPlugin = this + + override fun getName(): String = "corda" + + override fun authenticate(username: String?, credential: String?): AuthInfo { + if (username == null || credential == null) { + return AuthInfo.UNSUCCESSFUL + } + + val user = userService.getUser(username) + + if (user != null && user.password == credential) { + val actor = Actor(Actor.Id(username), userService.id, nodeLegalName) + return CordaSSHAuthInfo(true, RPCOpsWithContext(rpcOps, InvocationContext.rpc(actor), RpcPermissions(user.permissions))) + } + + return AuthInfo.UNSUCCESSFUL; + } + + override fun getCredentialType(): Class = String::class.java +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/shell/CordaSSHAuthInfo.kt b/node/src/main/kotlin/net/corda/node/shell/CordaSSHAuthInfo.kt new file mode 100644 index 0000000000..04bda1a4bb --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/shell/CordaSSHAuthInfo.kt @@ -0,0 +1,9 @@ +package net.corda.node.shell + +import net.corda.core.messaging.CordaRPCOps +import net.corda.node.utilities.ANSIProgressRenderer +import org.crsh.auth.AuthInfo + +class CordaSSHAuthInfo(val successful: Boolean, val rpcOps: CordaRPCOps, val ansiProgressRenderer: ANSIProgressRenderer? = null) : AuthInfo { + override fun isSuccessful(): Boolean = successful +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt index 9e66fd4195..d2ca683740 100644 --- a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt +++ b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt @@ -9,25 +9,32 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import com.google.common.io.Closeables import net.corda.client.jackson.JacksonSupport import net.corda.client.jackson.StringToMethodCallParser +import net.corda.client.rpc.PermissionException import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.UniqueIdentifier import net.corda.core.flows.FlowLogic +import net.corda.core.identity.CordaX500Name import net.corda.core.internal.* -import net.corda.core.internal.concurrent.OpenFuture +import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.openFuture import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.DataFeed +import net.corda.core.messaging.FlowProgressHandle import net.corda.core.messaging.StateMachineUpdate import net.corda.core.utilities.getOrThrow +import net.corda.core.node.services.IdentityService +import net.corda.core.utilities.loggerFor import net.corda.node.internal.Node import net.corda.node.internal.StartedNode +import net.corda.node.services.RPCUserService +import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT import net.corda.node.services.messaging.RpcAuthContext import net.corda.node.services.messaging.RpcPermissions -import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.utilities.ANSIProgressRenderer import net.corda.node.utilities.CordaPersistence +import net.corda.node.utilities.StdoutANSIProgressRenderer import org.crsh.command.InvocationContext import org.crsh.console.jline.JLineProcessor import org.crsh.console.jline.TerminalFactory @@ -77,59 +84,55 @@ object InteractiveShell { private lateinit var node: StartedNode @VisibleForTesting internal lateinit var database: CordaPersistence + private lateinit var rpcOps:CordaRPCOps + private lateinit var userService:RPCUserService + private lateinit var identityService:IdentityService + private var shell:Shell? = null + private lateinit var nodeLegalName: CordaX500Name /** * Starts an interactive shell connected to the local terminal. This shell gives administrator access to the node * internals. */ - fun startShell(dir: Path, runLocalShell: Boolean, runSSHServer: Boolean, node: StartedNode) { - this.node = node - this.database = node.database - var runSSH = runSSHServer + fun startShell(configuration:NodeConfiguration, cordaRPCOps: CordaRPCOps, userService: RPCUserService, identityService: IdentityService, database:CordaPersistence) { + this.rpcOps = cordaRPCOps + this.userService = userService + this.identityService = identityService + this.nodeLegalName = configuration.myLegalName + this.database = database + val dir = configuration.baseDirectory + val runSshDeamon = configuration.sshd != null val config = Properties() - if (runSSH) { - // TODO: Finish and enable SSH access. - // This means bringing the CRaSH SSH plugin into the Corda tree and applying Marek's patches - // found in https://github.com/marekdapps/crash/commit/8a37ce1c7ef4d32ca18f6396a1a9d9841f7ff643 - // to that local copy, as CRaSH is no longer well maintained by the upstream and the SSH plugin - // that it comes with is based on a very old version of Apache SSHD which can't handle connections - // from newer SSH clients. It also means hooking things up to the authentication system. - Node.printBasicNodeInfo("SSH server access is not fully implemented, sorry.") - runSSH = false - } + if (runSshDeamon) { + val sshKeysDir = dir / "sshkey" + sshKeysDir.toFile().mkdirs() - if (runSSH) { // Enable SSH access. Note: these have to be strings, even though raw object assignments also work. - config["crash.ssh.keypath"] = (dir / "sshkey").toString() + config["crash.ssh.keypath"] = (sshKeysDir / "hostkey.pem").toString() config["crash.ssh.keygen"] = "true" - // config["crash.ssh.port"] = node.configuration.sshdAddress.port.toString() - config["crash.auth"] = "simple" - config["crash.auth.simple.username"] = "admin" - config["crash.auth.simple.password"] = "admin" + config["crash.ssh.port"] = configuration.sshd?.port.toString() + config["crash.auth"] = "corda" } ExternalResolver.INSTANCE.addCommand("run", "Runs a method from the CordaRPCOps interface on the node.", RunShellCommand::class.java) ExternalResolver.INSTANCE.addCommand("flow", "Commands to work with flows. Flows are how you can change the ledger.", FlowShellCommand::class.java) ExternalResolver.INSTANCE.addCommand("start", "An alias for 'flow start'", StartShellCommand::class.java) - val shell = ShellLifecycle(dir).start(config) + shell = ShellLifecycle(dir).start(config) - if (runSSH) { - // printBasicNodeInfo("SSH server listening on address", node.configuration.sshdAddress.toString()) + if (runSshDeamon) { + Node.printBasicNodeInfo("SSH server listening on port", configuration.sshd!!.port.toString()) } + } - // Possibly bring up a local shell in the launching terminal window, unless it's disabled. - if (!runLocalShell) - return - // TODO: Automatically set up the JDBC sub-command with a connection to the database. + fun runLocalShell(node:StartedNode) { val terminal = TerminalFactory.create() val consoleReader = ConsoleReader("Corda", FileInputStream(FileDescriptor.`in`), System.out, terminal) val jlineProcessor = JLineProcessor(terminal.isAnsiSupported, shell, consoleReader, System.out) InterruptHandler { jlineProcessor.interrupt() }.install() thread(name = "Command line shell processor", isDaemon = true) { // Give whoever has local shell access administrator access to the node. - // TODO remove this after Shell switches to RPC - val context = RpcAuthContext(net.corda.core.context.InvocationContext.shell(), RpcPermissions.NONE) + val context = RpcAuthContext(net.corda.core.context.InvocationContext.shell(), RpcPermissions.ALL) CURRENT_RPC_CONTEXT.set(context) Emoji.renderIfSupported { jlineProcessor.run() @@ -168,27 +171,25 @@ object InteractiveShell { // Don't use the Java language plugin (we may not have tools.jar available at runtime), this // will cause any commands using JIT Java compilation to be suppressed. In CRaSH upstream that // is only the 'jmx' command. - return super.getPlugins().filterNot { it is JavaLanguage } + return super.getPlugins().filterNot { it is JavaLanguage } + CordaAuthenticationPlugin(rpcOps, userService, nodeLegalName) } } val attributes = mapOf( - "node" to node.internals, - "services" to node.services, - "ops" to node.rpcOps, + "ops" to rpcOps, "mapper" to yamlInputMapper ) val context = PluginContext(discovery, attributes, commandsFS, confFS, classLoader) context.refresh() this.config = config start(context) - return context.getPlugin(ShellFactory::class.java).create(null) + return context.getPlugin(ShellFactory::class.java).create(null, CordaSSHAuthInfo(false, RPCOpsWithContext(rpcOps, net.corda.core.context.InvocationContext.shell(), RpcPermissions.ALL), StdoutANSIProgressRenderer)) } } private val yamlInputMapper: ObjectMapper by lazy { // Return a standard Corda Jackson object mapper, configured to use YAML by default and with extra // serializers. - JacksonSupport.createInMemoryMapper(node.services.identityService, YAMLFactory(), true).apply { + JacksonSupport.createInMemoryMapper(identityService, YAMLFactory(), true).apply { val rpcModule = SimpleModule() rpcModule.addDeserializer(InputStream::class.java, InputStreamDeserializer) rpcModule.addDeserializer(UniqueIdentifier::class.java, UniqueIdentifierDeserializer) @@ -217,42 +218,41 @@ object InteractiveShell { /** * Called from the 'flow' shell command. Takes a name fragment and finds a matching flow, or prints out * the list of options if the request is ambiguous. Then parses [inputData] as constructor arguments using - * the [runFlowFromString] method and starts the requested flow using the [ANSIProgressRenderer] to draw - * the progress tracker. Ctrl-C can be used to cancel. + * the [runFlowFromString] method and starts the requested flow. Ctrl-C can be used to cancel. */ @JvmStatic - fun runFlowByNameFragment(nameFragment: String, inputData: String, output: RenderPrintWriter) { - val matches = node.services.rpcFlows.filter { nameFragment in it.name } + fun runFlowByNameFragment(nameFragment: String, inputData: String, output: RenderPrintWriter, rpcOps: CordaRPCOps, ansiProgressRenderer: ANSIProgressRenderer) { + val matches = rpcOps.registeredFlows().filter { nameFragment in it } if (matches.isEmpty()) { output.println("No matching flow found, run 'flow list' to see your options.", Color.red) return } else if (matches.size > 1) { - output.println("Ambigous name provided, please be more specific. Your options are:") + output.println("Ambiguous name provided, please be more specific. Your options are:") matches.forEachIndexed { i, s -> output.println("${i + 1}. $s", Color.yellow) } return } - val clazz: Class> = uncheckedCast(matches.single()) + val clazz: Class> = uncheckedCast(Class.forName(matches.single())) try { - // TODO Flow invocation should use startFlowDynamic. - val context = net.corda.core.context.InvocationContext.shell() - val fsm = runFlowFromString({ node.services.startFlow(it, context).getOrThrow() }, inputData, clazz) // Show the progress tracker on the console until the flow completes or is interrupted with a // Ctrl-C keypress. + val stateObservable = runFlowFromString({ clazz,args -> rpcOps.startTrackedFlowDynamic (clazz, *args) }, inputData, clazz) + val latch = CountDownLatch(1) - ANSIProgressRenderer.onDone = { latch.countDown() } - ANSIProgressRenderer.progressTracker = (fsm as FlowStateMachineImpl).logic.progressTracker + ansiProgressRenderer.render(stateObservable, { latch.countDown() }) try { // Wait for the flow to end and the progress tracker to notice. By the time the latch is released // the tracker is done with the screen. latch.await() } catch (e: InterruptedException) { - ANSIProgressRenderer.progressTracker = null // TODO: When the flow framework allows us to kill flows mid-flight, do so here. } + } catch (e: NoApplicableConstructor) { output.println("No matching constructor found:", Color.red) e.errors.forEach { output.println("- $it", Color.red) } + } catch (e:PermissionException) { + output.println(e.message ?: "Access denied", Color.red) } finally { InputStreamDeserializer.closeAll() } @@ -273,10 +273,10 @@ object InteractiveShell { * @throws NoApplicableConstructor if no constructor could be found for the given set of types. */ @Throws(NoApplicableConstructor::class) - fun runFlowFromString(invoke: (FlowLogic<*>) -> FlowStateMachine<*>, + fun runFlowFromString(invoke: (Class>, Array) -> FlowProgressHandle, inputData: String, - clazz: Class>, - om: ObjectMapper = yamlInputMapper): FlowStateMachine<*> { + clazz: Class>, + om: ObjectMapper = yamlInputMapper): FlowProgressHandle { // For each constructor, attempt to parse the input data as a method call. Use the first that succeeds, // and keep track of the reasons we failed so we can print them out if no constructors are usable. val parser = StringToMethodCallParser(clazz, om) @@ -303,7 +303,7 @@ object InteractiveShell { errors.add("A flow must override the progress tracker in order to be run from the shell") continue } - return invoke(flow) + return invoke(clazz, args) } catch (e: StringToMethodCallParser.UnparseableCallException.MissingParameter) { errors.add("${getPrototype()}: missing parameter ${e.paramName}") } catch (e: StringToMethodCallParser.UnparseableCallException.TooManyParameters) { @@ -321,8 +321,8 @@ object InteractiveShell { // TODO Filtering on error/success when we will have some sort of flow auditing, for now it doesn't make much sense. @JvmStatic - fun runStateMachinesView(out: RenderPrintWriter): Any? { - val proxy = node.rpcOps + fun runStateMachinesView(out: RenderPrintWriter, rpcOps: CordaRPCOps): Any? { + val proxy = rpcOps val (stateMachines, stateMachineUpdates) = proxy.stateMachinesFeed() val currentStateMachines = stateMachines.map { StateMachineUpdate.Added(it) } val subscriber = FlowWatchPrintingSubscriber(out) @@ -395,7 +395,7 @@ object InteractiveShell { return result } - private fun printAndFollowRPCResponse(response: Any?, toStream: PrintWriter): CordaFuture? { + private fun printAndFollowRPCResponse(response: Any?, toStream: PrintWriter): CordaFuture { val printerFun = yamlMapper::writeValueAsString toStream.println(printerFun(response)) toStream.flush() @@ -422,28 +422,31 @@ object InteractiveShell { override fun onNext(t: Any?) { count++ toStream.println("Observation $count: " + printerFun(t)) - toStream.flush() } @Synchronized override fun onError(e: Throwable) { toStream.println("Observable completed with an error") - e.printStackTrace() + e.printStackTrace(toStream) future.setException(e) } } - private fun maybeFollow(response: Any?, printerFun: (Any?) -> String, toStream: PrintWriter): OpenFuture? { + private fun maybeFollow(response: Any?, printerFun: (Any?) -> String, toStream: PrintWriter): CordaFuture { // Match on a couple of common patterns for "important" observables. It's tough to do this in a generic // way because observables can be embedded anywhere in the object graph, and can emit other arbitrary // object graphs that contain yet more observables. So we just look for top level responses that follow // the standard "track" pattern, and print them until the user presses Ctrl-C - if (response == null) return null + if (response == null) return doneFuture(Unit) val observable: Observable<*> = when (response) { is Observable<*> -> response - is DataFeed<*, *> -> response.updates - else -> return null + is DataFeed<*, *> -> { + toStream.println("Snapshot") + toStream.println(response.snapshot) + response.updates + } + else -> return doneFuture(Unit) } val subscriber = PrintingSubscriber(printerFun, toStream) diff --git a/node/src/main/kotlin/net/corda/node/shell/InteractiveShellCommand.kt b/node/src/main/kotlin/net/corda/node/shell/InteractiveShellCommand.kt index 1c352c9bd4..9278fffb95 100644 --- a/node/src/main/kotlin/net/corda/node/shell/InteractiveShellCommand.kt +++ b/node/src/main/kotlin/net/corda/node/shell/InteractiveShellCommand.kt @@ -4,12 +4,14 @@ import com.fasterxml.jackson.databind.ObjectMapper import net.corda.core.messaging.CordaRPCOps import net.corda.node.services.api.ServiceHubInternal import org.crsh.command.BaseCommand +import org.crsh.shell.impl.command.CRaSHSession /** * Simply extends CRaSH BaseCommand to add easy access to the RPC ops class. */ open class InteractiveShellCommand : BaseCommand() { - fun ops() = context.attributes["ops"] as CordaRPCOps + fun ops() = ((context.session as CRaSHSession).authInfo as CordaSSHAuthInfo).rpcOps + fun ansiProgressRenderer() = ((context.session as CRaSHSession).authInfo as CordaSSHAuthInfo).ansiProgressRenderer fun services() = context.attributes["services"] as ServiceHubInternal fun objectMapper() = context.attributes["mapper"] as ObjectMapper } diff --git a/node/src/main/kotlin/net/corda/node/shell/RPCOpsWithContext.kt b/node/src/main/kotlin/net/corda/node/shell/RPCOpsWithContext.kt new file mode 100644 index 0000000000..75a17d7486 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/shell/RPCOpsWithContext.kt @@ -0,0 +1,210 @@ +package net.corda.node.shell + +import net.corda.core.concurrent.CordaFuture +import net.corda.core.context.InvocationContext +import net.corda.core.contracts.ContractState +import net.corda.core.crypto.SecureHash +import net.corda.core.flows.FlowLogic +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party +import net.corda.core.messaging.* +import net.corda.core.node.NodeInfo +import net.corda.core.node.services.AttachmentId +import net.corda.core.node.services.NetworkMapCache +import net.corda.core.node.services.Vault +import net.corda.core.node.services.vault.* +import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.getOrThrow +import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT +import net.corda.node.services.messaging.RpcAuthContext +import net.corda.node.services.messaging.RpcPermissions +import rx.Observable +import java.io.InputStream +import java.security.PublicKey +import java.time.Instant +import java.util.concurrent.CompletableFuture +import java.util.concurrent.Future + +class RPCOpsWithContext(val cordaRPCOps: CordaRPCOps, val invocationContext:InvocationContext, val rpcPermissions: RpcPermissions) : CordaRPCOps { + + + class RPCContextRunner(val invocationContext:InvocationContext, val permissions:RpcPermissions, val block:() -> T) : Thread() { + private var result: CompletableFuture = CompletableFuture() + override fun run() { + CURRENT_RPC_CONTEXT.set(RpcAuthContext(invocationContext, permissions)) + try { + result.complete(block()) + } catch (e:Throwable) { + result.completeExceptionally(e) + } + CURRENT_RPC_CONTEXT.remove() + } + + fun get(): Future { + start() + join() + return result + } + } + + override fun uploadAttachmentWithMetadata(jar: InputStream, uploader: String, filename: String): SecureHash { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.uploadAttachmentWithMetadata(jar, uploader, filename) }.get().getOrThrow() + } + + override fun queryAttachments(query: AttachmentQueryCriteria, sorting: AttachmentSort?): List { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.queryAttachments(query, sorting) }.get().getOrThrow() + } + + override fun nodeStateObservable(): Observable { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.nodeStateObservable() }.get().getOrThrow() + } + + override fun vaultTrackByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): DataFeed, Vault.Update> { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackByWithSorting(contractStateType, criteria, sorting) }.get().getOrThrow() + } + + override fun vaultTrackByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): DataFeed, Vault.Update> { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackByWithPagingSpec(contractStateType, criteria, paging) }.get().getOrThrow() + } + + override fun vaultTrackByCriteria(contractStateType: Class, criteria: QueryCriteria): DataFeed, Vault.Update> { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackByCriteria(contractStateType, criteria) }.get().getOrThrow() + } + + override fun vaultTrack(contractStateType: Class): DataFeed, Vault.Update> { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrack(contractStateType) }.get().getOrThrow() + } + + override fun vaultQueryByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): Vault.Page { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryByWithSorting(contractStateType, criteria, sorting) }.get().getOrThrow() + } + + override fun vaultQueryByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): Vault.Page { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryByWithPagingSpec(contractStateType, criteria, paging) }.get().getOrThrow() + } + + override fun vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class): Vault.Page { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryByCriteria(criteria, contractStateType) }.get().getOrThrow() + } + + override fun vaultQuery(contractStateType: Class): Vault.Page { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQuery(contractStateType) }.get().getOrThrow() + } + + override fun stateMachinesSnapshot(): List { + return RPCContextRunner(invocationContext, rpcPermissions, cordaRPCOps::stateMachinesSnapshot).get().getOrThrow() + } + + override fun stateMachinesFeed(): DataFeed, StateMachineUpdate> { + return RPCContextRunner(invocationContext, rpcPermissions, cordaRPCOps::stateMachinesFeed).get().getOrThrow() + } + + override fun vaultQueryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class): Vault.Page { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryBy(criteria, paging, sorting, contractStateType) }.get().getOrThrow() + } + + override fun vaultTrackBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class): DataFeed, Vault.Update> { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackBy(criteria, paging, sorting, contractStateType) }.get().getOrThrow() + } + + override fun internalVerifiedTransactionsSnapshot(): List { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.internalVerifiedTransactionsSnapshot() }.get().getOrThrow() + } + + override fun internalVerifiedTransactionsFeed(): DataFeed, SignedTransaction> { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.internalVerifiedTransactionsFeed() }.get().getOrThrow() + } + + override fun stateMachineRecordedTransactionMappingSnapshot(): List { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.stateMachineRecordedTransactionMappingSnapshot() }.get().getOrThrow() + } + + override fun stateMachineRecordedTransactionMappingFeed(): DataFeed, StateMachineTransactionMapping> { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.stateMachineRecordedTransactionMappingFeed() }.get().getOrThrow() + } + + override fun networkMapSnapshot(): List { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.networkMapSnapshot() }.get().getOrThrow() + } + + override fun networkMapFeed(): DataFeed, NetworkMapCache.MapChange> { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.networkMapFeed() }.get().getOrThrow() + } + + override fun startFlowDynamic(logicType: Class>, vararg args: Any?): FlowHandle { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.startFlowDynamic(logicType, *args) }.get().getOrThrow() + } + + override fun startTrackedFlowDynamic(logicType: Class>, vararg args: Any?): FlowProgressHandle { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.startTrackedFlowDynamic(logicType, *args) }.get().getOrThrow() + } + + override fun nodeInfo(): NodeInfo { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.nodeInfo() }.get().getOrThrow() + } + + override fun notaryIdentities(): List { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.notaryIdentities() }.get().getOrThrow() + } + + override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.addVaultTransactionNote(txnId, txnNote) }.get().getOrThrow() + } + + override fun getVaultTransactionNotes(txnId: SecureHash): Iterable { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.getVaultTransactionNotes(txnId) }.get().getOrThrow() + } + + override fun attachmentExists(id: SecureHash): Boolean { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.attachmentExists(id) }.get().getOrThrow() + } + + override fun openAttachment(id: SecureHash): InputStream { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.openAttachment(id) }.get().getOrThrow() + } + + override fun uploadAttachment(jar: InputStream): SecureHash { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.uploadAttachment(jar) }.get().getOrThrow() + } + + override fun currentNodeTime(): Instant { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.currentNodeTime() }.get().getOrThrow() + } + + override fun waitUntilNetworkReady(): CordaFuture { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.waitUntilNetworkReady() }.get().getOrThrow() + } + + override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.wellKnownPartyFromAnonymous(party) }.get().getOrThrow() + } + + override fun partyFromKey(key: PublicKey): Party? { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.partyFromKey(key) }.get().getOrThrow() + } + + override fun wellKnownPartyFromX500Name(x500Name: CordaX500Name): Party? { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.wellKnownPartyFromX500Name(x500Name) }.get().getOrThrow() + } + + override fun notaryPartyFromX500Name(x500Name: CordaX500Name): Party? { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.notaryPartyFromX500Name(x500Name) }.get().getOrThrow() + } + + override fun partiesFromName(query: String, exactMatch: Boolean): Set { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.partiesFromName(query, exactMatch) }.get().getOrThrow() + } + + override fun registeredFlows(): List { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.registeredFlows() }.get().getOrThrow() + } + + override fun nodeInfoFromParty(party: AbstractParty): NodeInfo? { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.nodeInfoFromParty(party) }.get().getOrThrow() + } + + override fun clearNetworkMapCache() { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.clearNetworkMapCache() }.get().getOrThrow() + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/utilities/ANSIProgressRenderer.kt b/node/src/main/kotlin/net/corda/node/utilities/ANSIProgressRenderer.kt index 8339d0ae22..303f6e91c0 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/ANSIProgressRenderer.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/ANSIProgressRenderer.kt @@ -1,138 +1,113 @@ package net.corda.node.utilities import net.corda.core.internal.Emoji -import net.corda.core.utilities.ProgressTracker -import net.corda.node.utilities.ANSIProgressRenderer.progressTracker +import net.corda.core.messaging.FlowProgressHandle import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.core.LogEvent import org.apache.logging.log4j.core.LoggerContext import org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender import org.apache.logging.log4j.core.appender.ConsoleAppender import org.apache.logging.log4j.core.appender.OutputStreamManager +import org.crsh.text.RenderPrintWriter import org.fusesource.jansi.Ansi import org.fusesource.jansi.AnsiConsole import org.fusesource.jansi.AnsiOutputStream import rx.Subscription -/** - * Knows how to render a [ProgressTracker] to the terminal using coloured, emoji-fied output. Useful when writing small - * command line tools, demos, tests etc. Just set the [progressTracker] field and it will go ahead and start drawing - * if the terminal supports it. Otherwise it just prints out the name of the step whenever it changes. - * - * When a progress tracker is on the screen, it takes over the bottom part and reconfigures logging so that, assuming - * 1 log event == 1 line, the progress tracker is always glued to the bottom and logging scrolls above it. - * - * TODO: More thread safety - */ -object ANSIProgressRenderer { +abstract class ANSIProgressRenderer { + + private var subscriptionIndex: Subscription? = null + private var subscriptionTree: Subscription? = null + + protected var usingANSI = false + protected var checkEmoji = false + + protected var treeIndex: Int = 0 + protected var tree: List> = listOf() + private var installedYet = false - private var subscription: Subscription? = null - private var usingANSI = false - - var progressTracker: ProgressTracker? = null - set(value) { - subscription?.unsubscribe() - - field = value - if (!installedYet) { - setup() - } - - // Reset the state when a new tracker is wired up. - if (value != null) { - prevMessagePrinted = null - prevLinesDrawn = 0 - draw(true) - subscription = value.changes.subscribe({ draw(true) }, { done(it) }, { done(null) }) - } - } - - var onDone: () -> Unit = {} - - private fun done(error: Throwable?) { - if (error == null) progressTracker = null - draw(true, error) - onDone() - } - - private fun setup() { - AnsiConsole.systemInstall() - - // This line looks weird as hell because the magic code to decide if we really have a TTY or not isn't - // actually exposed anywhere as a function (weak sauce). So we have to rely on our knowledge of jansi - // implementation details. - usingANSI = AnsiConsole.wrapOutputStream(System.out) !is AnsiOutputStream - - if (usingANSI) { - // This super ugly code hacks into log4j and swaps out its console appender for our own. It's a bit simpler - // than doing things the official way with a dedicated plugin, etc, as it avoids mucking around with all - // the config XML and lifecycle goop. - val manager = LogManager.getContext(false) as LoggerContext - val consoleAppender = manager.configuration.appenders.values.filterIsInstance().single { it.name == "Console-Appender" } - val scrollingAppender = object : AbstractOutputStreamAppender( - consoleAppender.name, consoleAppender.layout, consoleAppender.filter, - consoleAppender.ignoreExceptions(), true, consoleAppender.manager) { - override fun append(event: LogEvent) { - // We lock on the renderer to avoid threads that are logging to the screen simultaneously messing - // things up. Of course this slows stuff down a bit, but only whilst this little utility is in use. - // Eventually it will be replaced with a real GUI and we can delete all this. - synchronized(ANSIProgressRenderer) { - if (progressTracker != null) { - val ansi = Ansi.ansi() - repeat(prevLinesDrawn) { ansi.eraseLine().cursorUp(1).eraseLine() } - System.out.print(ansi) - System.out.flush() - } - - super.append(event) - - if (progressTracker != null) - draw(false) - } - } - } - scrollingAppender.start() - manager.configuration.appenders[consoleAppender.name] = scrollingAppender - val loggerConfigs = manager.configuration.loggers.values - for (config in loggerConfigs) { - val appenderRefs = config.appenderRefs - val consoleAppenders = config.appenders.filter { it.value is ConsoleAppender }.keys - consoleAppenders.forEach { config.removeAppender(it) } - appenderRefs.forEach { config.addAppender(manager.configuration.appenders[it.ref], it.level, it.filter) } - } - manager.updateLoggers() - } - - installedYet = true - } + private var onDone: () -> Unit = {} // prevMessagePrinted is just for non-ANSI mode. private var prevMessagePrinted: String? = null // prevLinesDraw is just for ANSI mode. - private var prevLinesDrawn = 0 + protected var prevLinesDrawn = 0 - @Synchronized private fun draw(moveUp: Boolean, error: Throwable? = null) { + private fun done(error: Throwable?) { + if (error == null) _render(null) + draw(true, error) + onDone() + } + + fun render(flowProgressHandle: FlowProgressHandle<*>, onDone: () -> Unit = {}) { + this.onDone = onDone + _render(flowProgressHandle) + } + + protected abstract fun printLine(line:String) + + protected abstract fun printAnsi(ansi:Ansi) + + protected abstract fun setup() + + private fun _render(flowProgressHandle: FlowProgressHandle<*>?) { + subscriptionIndex?.unsubscribe() + subscriptionTree?.unsubscribe() + treeIndex = 0 + tree = listOf() + + if (!installedYet) { + setup() + installedYet = true + } + + prevMessagePrinted = null + prevLinesDrawn = 0 + draw(true) + + + flowProgressHandle?.apply { + stepsTreeIndexFeed?.apply { + treeIndex = snapshot + subscriptionIndex = updates.subscribe({ + treeIndex = it + draw(true) + }, { done(it) }, { done(null) }) + } + stepsTreeFeed?.apply { + tree = snapshot + subscriptionTree = updates.subscribe({ + tree = it + draw(true) + }, { done(it) }, { done(null) }) + } + } + } + + + + @Synchronized protected fun draw(moveUp: Boolean, error: Throwable? = null) { if (!usingANSI) { - val currentMessage = progressTracker?.currentStepRecursive?.label + val currentMessage = tree.getOrNull(treeIndex)?.second if (currentMessage != null && currentMessage != prevMessagePrinted) { - println(currentMessage) + printLine(currentMessage) prevMessagePrinted = currentMessage } return } - Emoji.renderIfSupported { + fun printingBody() { // Handle the case where the number of steps in a progress tracker is changed during execution. - val ansi = Ansi.ansi() + val ansi = Ansi() if (prevLinesDrawn > 0 && moveUp) ansi.cursorUp(prevLinesDrawn) // Put a blank line between any logging and us. ansi.eraseLine() ansi.newline() - val pt = progressTracker ?: return - var newLinesDrawn = 1 + pt.renderLevel(ansi, 0, error != null) + if (tree.isEmpty()) return + var newLinesDrawn = 1 + renderLevel(ansi, error != null) if (error != null) { ansi.a("${Emoji.skullAndCrossbones} ${error.message}") @@ -152,46 +127,137 @@ object ANSIProgressRenderer { } prevLinesDrawn = newLinesDrawn - // Need to force a flush here in order to ensure stderr/stdout sync up properly. - System.out.print(ansi) - System.out.flush() + printAnsi(ansi) } + if (checkEmoji) { + Emoji.renderIfSupported(::printingBody) + } else { + printingBody() + } } // Returns number of lines rendered. - private fun ProgressTracker.renderLevel(ansi: Ansi, indent: Int, error: Boolean): Int { + private fun renderLevel(ansi: Ansi, error: Boolean): Int { with(ansi) { var lines = 0 - for ((index, step) in steps.withIndex()) { - // Don't bother rendering these special steps in some cases. - if (step == ProgressTracker.UNSTARTED) continue - if (indent > 0 && step == ProgressTracker.DONE) continue + for ((index, step) in tree.withIndex()) { val marker = when { - index < stepIndex -> "${Emoji.greenTick} " - index == stepIndex && step == ProgressTracker.DONE -> "${Emoji.greenTick} " - index == stepIndex -> "${Emoji.rightArrow} " + index < treeIndex -> "${Emoji.greenTick} " + treeIndex == tree.lastIndex -> "${Emoji.greenTick} " + index == treeIndex -> "${Emoji.rightArrow} " error -> "${Emoji.noEntry} " else -> " " // Not reached yet. } - a(" ".repeat(indent)) + a(" ".repeat(step.first)) a(marker) - val active = index == stepIndex && step != ProgressTracker.DONE + val active = index == treeIndex if (active) bold() - a(step.label) + a(step.second) if (active) boldOff() eraseLine(Ansi.Erase.FORWARD) newline() lines++ - - val child = getChildProgressTracker(step) - if (child != null) - lines += child.renderLevel(ansi, indent + 1, error) } return lines } } + + +} + +class CRaSHNSIProgressRenderer(val renderPrintWriter:RenderPrintWriter) : ANSIProgressRenderer() { + + override fun printLine(line: String) { + renderPrintWriter.println(line) + } + + override fun printAnsi(ansi: Ansi) { + renderPrintWriter.print(ansi) + renderPrintWriter.flush() + } + + override fun setup() { + //we assume SSH always use ansi + usingANSI = true + } + + +} + +/** + * Knows how to render a [FlowProgressHandle] to the terminal using coloured, emoji-fied output. Useful when writing small + * command line tools, demos, tests etc. Just call [draw] method and it will go ahead and start drawing + * if the terminal supports it. Otherwise it just prints out the name of the step whenever it changes. + * + * When a progress tracker is on the screen, it takes over the bottom part and reconfigures logging so that, assuming + * 1 log event == 1 line, the progress tracker is always glued to the bottom and logging scrolls above it. + * + * TODO: More thread safety + */ +object StdoutANSIProgressRenderer : ANSIProgressRenderer() { + + override fun setup() { + AnsiConsole.systemInstall() + + checkEmoji = true + + // This line looks weird as hell because the magic code to decide if we really have a TTY or not isn't + // actually exposed anywhere as a function (weak sauce). So we have to rely on our knowledge of jansi + // implementation details. + usingANSI = AnsiConsole.wrapOutputStream(System.out) !is AnsiOutputStream + + if (usingANSI) { + // This super ugly code hacks into log4j and swaps out its console appender for our own. It's a bit simpler + // than doing things the official way with a dedicated plugin, etc, as it avoids mucking around with all + // the config XML and lifecycle goop. + val manager = LogManager.getContext(false) as LoggerContext + val consoleAppender = manager.configuration.appenders.values.filterIsInstance().single { it.name == "Console-Appender" } + val scrollingAppender = object : AbstractOutputStreamAppender( + consoleAppender.name, consoleAppender.layout, consoleAppender.filter, + consoleAppender.ignoreExceptions(), true, consoleAppender.manager) { + override fun append(event: LogEvent) { + // We lock on the renderer to avoid threads that are logging to the screen simultaneously messing + // things up. Of course this slows stuff down a bit, but only whilst this little utility is in use. + // Eventually it will be replaced with a real GUI and we can delete all this. + synchronized(StdoutANSIProgressRenderer) { + if (tree.isNotEmpty()) { + val ansi = Ansi.ansi() + repeat(prevLinesDrawn) { ansi.eraseLine().cursorUp(1).eraseLine() } + System.out.print(ansi) + System.out.flush() + } + + super.append(event) + + if (tree.isNotEmpty()) + draw(false) + } + } + } + scrollingAppender.start() + manager.configuration.appenders[consoleAppender.name] = scrollingAppender + val loggerConfigs = manager.configuration.loggers.values + for (config in loggerConfigs) { + val appenderRefs = config.appenderRefs + val consoleAppenders = config.appenders.filter { it.value is ConsoleAppender }.keys + consoleAppenders.forEach { config.removeAppender(it) } + appenderRefs.forEach { config.addAppender(manager.configuration.appenders[it.ref], it.level, it.filter) } + } + manager.updateLoggers() + } + } + + override fun printLine(line:String) { + System.out.println(line) + } + + override fun printAnsi(ansi: Ansi) { + // Need to force a flush here in order to ensure stderr/stdout sync up properly. + System.out.print(ansi) + System.out.flush() + } } diff --git a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt index 622fc9667c..5f55195c22 100644 --- a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt +++ b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt @@ -2,11 +2,17 @@ package net.corda.node import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import net.corda.client.jackson.JacksonSupport +import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.Amount import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowLogic +import net.corda.core.flows.StateMachineRunId import net.corda.core.identity.Party import net.corda.core.internal.FlowStateMachine +import net.corda.core.internal.concurrent.openFuture +import net.corda.core.internal.objectOrNewInstance +import net.corda.core.messaging.FlowProgressHandle +import net.corda.core.messaging.FlowProgressHandleImpl import net.corda.core.utilities.ProgressTracker import net.corda.node.services.identity.InMemoryIdentityService import net.corda.node.shell.InteractiveShell @@ -20,6 +26,7 @@ import net.corda.testing.rigorousMock import org.junit.After import org.junit.Before import org.junit.Test +import rx.Observable import java.util.* import kotlin.test.assertEquals @@ -36,8 +43,8 @@ class InteractiveShellTest { @Suppress("UNUSED") class FlowA(val a: String) : FlowLogic() { - constructor(b: Int) : this(b.toString()) - constructor(b: Int, c: String) : this(b.toString() + c) + constructor(b: Int?) : this(b.toString()) + constructor(b: Int?, c: String) : this(b.toString() + c) constructor(amount: Amount) : this(amount.toString()) constructor(pair: Pair, SecureHash.SHA256>) : this(pair.toString()) constructor(party: Party) : this(party.name.toString()) @@ -50,9 +57,16 @@ class InteractiveShellTest { private val om = JacksonSupport.createInMemoryMapper(ids, YAMLFactory()) private fun check(input: String, expected: String) { - var output: DummyFSM? = null - InteractiveShell.runFlowFromString({ DummyFSM(it as FlowA).apply { output = this } }, input, FlowA::class.java, om) - assertEquals(expected, output!!.logic.a, input) + var output: String? = null + InteractiveShell.runFlowFromString( { clazz, args -> + + val instance = clazz.getConstructor(*args.map { it!!::class.java }.toTypedArray()).newInstance(*args) as FlowA + output = instance.a + val future = openFuture() + future.set("ABC") + FlowProgressHandleImpl(StateMachineRunId.createRandom(), future, Observable.just("Some string")) + }, input, FlowA::class.java, om) + assertEquals(expected, output!!, input) } @Test diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 269db1b6d0..b00bebad91 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -85,7 +85,8 @@ private val DRIVER_REQUIRED_PERMISSIONS = setOf( invokeRpc(CordaRPCOps::nodeInfoFromParty), invokeRpc(CordaRPCOps::internalVerifiedTransactionsFeed), invokeRpc("vaultQueryBy"), - invokeRpc("vaultTrackBy") + invokeRpc("vaultTrackBy"), + invokeRpc(CordaRPCOps::registeredFlows) ) /** diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 7aed47e4c6..aea7accf55 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -12,6 +12,7 @@ import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.createDirectories import net.corda.core.internal.createDirectory import net.corda.core.internal.uncheckedCast +import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient @@ -43,6 +44,8 @@ import net.corda.testing.node.MockServices.Companion.makeTestDataSourcePropertie import net.corda.testing.setGlobalSerialization import net.corda.testing.testNodeConfiguration import org.apache.activemq.artemis.utils.ReusableLatch +import org.apache.sshd.common.util.security.SecurityUtils +import org.slf4j.Logger import java.io.Closeable import java.math.BigInteger import java.nio.file.Path @@ -124,6 +127,13 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete /** Helper constructor for creating a [MockNetwork] with custom parameters from Java. */ constructor(parameters: MockNetworkParameters) : this(defaultParameters = parameters) + init { + // Apache SSHD for whatever reason registers a SFTP FileSystemProvider - which gets loaded by JimFS. + // This SFTP support loads BouncyCastle, which we want to avoid. + // Please see https://issues.apache.org/jira/browse/SSHD-736 - it's easier then to create our own fork of SSHD + SecurityUtils.setAPrioriDisabledProvider("BC", true) + } + var nextNodeId = 0 private set private val filesystem = Jimfs.newFileSystem(unix()) @@ -269,6 +279,10 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete return E2ETestKeyManagementService(identityService, keyPairs) } + override fun startShell(rpcOps: CordaRPCOps) { + //No mock shell + } + override fun startMessagingService(rpcOps: RPCOps) { // Nothing to do } From c467a056aeed7f68751b0e292912c851737f794b Mon Sep 17 00:00:00 2001 From: bpaunescu Date: Tue, 21 Nov 2017 09:52:17 +0000 Subject: [PATCH 26/31] Revert CORDA-296: added rpc that returns an observable for node state (#2091) * Revert "CORDA-296: added rpc that returns an observable for node state (#2004)" This reverts commit 7d1f7ab * Revert "CORDA-296: added rpc that returns an observable for node state (#2004)" This reverts commit 7d1f7ab --- .ci/api-current.txt | 7 ----- .../net/corda/client/rpc/RPCStabilityTests.kt | 28 ------------------- .../net/corda/core/messaging/CordaRPCOps.kt | 9 ------ .../kotlin/net/corda/core/node/ServiceHub.kt | 5 ---- .../net/corda/node/internal/AbstractNode.kt | 6 ---- .../corda/node/internal/CordaRPCOpsImpl.kt | 4 --- .../node/internal/RpcAuthorisationProxy.kt | 4 --- .../net/corda/testing/node/MockServices.kt | 3 -- 8 files changed, 66 deletions(-) diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 9416a54a9c..84b4d41817 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -1533,7 +1533,6 @@ public final class net.corda.core.identity.IdentityUtils extends java.lang.Objec @org.jetbrains.annotations.NotNull public abstract List networkMapSnapshot() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NodeInfo nodeInfo() @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo nodeInfoFromParty(net.corda.core.identity.AbstractParty) - @net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract rx.Observable nodeStateObservable() @org.jetbrains.annotations.NotNull public abstract List notaryIdentities() @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party notaryPartyFromX500Name(net.corda.core.identity.CordaX500Name) @org.jetbrains.annotations.NotNull public abstract java.io.InputStream openAttachment(net.corda.core.crypto.SecureHash) @@ -1613,11 +1612,6 @@ public final class net.corda.core.messaging.CordaRPCOpsKt extends java.lang.Obje ## @net.corda.core.serialization.CordaSerializable public interface net.corda.core.messaging.MessageRecipients ## -@net.corda.core.serialization.CordaSerializable public final class net.corda.core.messaging.NodeState extends java.lang.Enum - protected (String, int) - public static net.corda.core.messaging.NodeState valueOf(String) - public static net.corda.core.messaging.NodeState[] values() -## @net.corda.core.DoNotImplement public interface net.corda.core.messaging.RPCOps public abstract int getProtocolVersion() ## @@ -1714,7 +1708,6 @@ public @interface net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.ContractUpgradeService getContractUpgradeService() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.KeyManagementService getKeyManagementService() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NodeInfo getMyInfo() - @org.jetbrains.annotations.NotNull public abstract rx.Observable getMyNodeStateObservable() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.NetworkMapCache getNetworkMapCache() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.TransactionVerifierService getTransactionVerifierService() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.TransactionStorage getValidatedTransactions() diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt index 7247c33ddd..8d6885f0b3 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt @@ -6,16 +6,12 @@ import net.corda.core.context.Trace import net.corda.core.crypto.random63BitValue import net.corda.core.internal.concurrent.fork import net.corda.core.internal.concurrent.transpose -import net.corda.core.messaging.CordaRPCOps -import net.corda.core.messaging.NodeState import net.corda.core.messaging.RPCOps import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.serialize import net.corda.core.utilities.* -import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.messaging.RPCServerConfiguration import net.corda.nodeapi.RPCApi -import net.corda.nodeapi.User import net.corda.testing.driver.poll import net.corda.testing.internal.* import org.apache.activemq.artemis.api.core.SimpleString @@ -241,30 +237,6 @@ class RPCStabilityTests { } } - @Test - fun `clients receive notifications that node is shutting down`() { - val alice = User("Alice", "Alice", setOf(invokeRpc(CordaRPCOps::nodeStateObservable))) - val bob = User("Bob", "Bob", setOf(invokeRpc(CordaRPCOps::nodeStateObservable))) - val slagathor = User("Slagathor", "Slagathor", setOf(invokeRpc(CordaRPCOps::nodeStateObservable))) - val userList = listOf(alice, bob, slagathor) - val expectedMessages = ArrayList() - - rpcDriver(startNodesInProcess = true) { - val node = startNode(rpcUsers = listOf(alice, bob, slagathor)).getOrThrow() - userList.forEach { - val connection = node.rpcClientToNode().start(it.username, it.password) - val nodeStateObservable = connection.proxy.nodeStateObservable() - nodeStateObservable.subscribe { update -> - expectedMessages.add(update) - } - } - - node.stop() - } - assertEquals(userList.size, expectedMessages.size) - assertEquals(NodeState.SHUTTING_DOWN, expectedMessages.first()) - } - interface TrackSubscriberOps : RPCOps { fun subscribe(): Observable } diff --git a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt index 8859d94ec8..b6880a8103 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt @@ -226,10 +226,6 @@ interface CordaRPCOps : RPCOps { /** Returns Node's NodeInfo, assuming this will not change while the node is running. */ fun nodeInfo(): NodeInfo - /** Returns and [Observable] object with future states of the node. */ - @RPCReturnsObservables - fun nodeStateObservable(): Observable - /** * Returns network's notary identities, assuming this will not change while the node is running. * @@ -468,8 +464,3 @@ inline fun > CordaRPCOps.startTrac */ @CordaSerializable data class DataFeed(val snapshot: A, val updates: Observable) - -@CordaSerializable -enum class NodeState { - SHUTTING_DOWN -} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt index 68ae3fc5f3..78f1704336 100644 --- a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt +++ b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt @@ -8,13 +8,11 @@ import net.corda.core.crypto.SignableData import net.corda.core.crypto.SignatureMetadata import net.corda.core.crypto.TransactionSignature import net.corda.core.flows.ContractUpgradeFlow -import net.corda.core.messaging.NodeState import net.corda.core.node.services.* import net.corda.core.serialization.SerializeAsToken import net.corda.core.transactions.FilteredTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder -import rx.Observable import java.security.PublicKey import java.sql.Connection import java.time.Clock @@ -150,9 +148,6 @@ interface ServiceHub : ServicesForResolution { /** The [NodeInfo] object corresponding to our own entry in the network map. */ val myInfo: NodeInfo - /** The [Observable] object used to communicate to RPC clients the state of the node. */ - val myNodeStateObservable: Observable - /** * Return the singleton instance of the given Corda service type. This is a class that is annotated with * [CordaService] and will have automatically been registered by the node. diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index d6aad5cf45..149f11668e 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -61,7 +61,6 @@ import net.corda.node.utilities.* import org.apache.activemq.artemis.utils.ReusableLatch import org.slf4j.Logger import rx.Observable -import rx.subjects.PublishSubject import java.io.IOException import java.lang.reflect.InvocationTargetException import java.security.KeyPair @@ -121,7 +120,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, protected val services: ServiceHubInternal get() = _services private lateinit var _services: ServiceHubInternalImpl - protected val nodeStateObservable: PublishSubject = PublishSubject.create() protected var myNotaryIdentity: PartyAndCertificate? = null protected lateinit var checkpointStorage: CheckpointStorage protected lateinit var smm: StateMachineManager @@ -635,9 +633,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, // Meanwhile, we let the remote service send us updates until the acknowledgment buffer overflows and it // unsubscribes us forcibly, rather than blocking the shutdown process. - // Notify observers that the node is shutting down - nodeStateObservable.onNext(NodeState.SHUTTING_DOWN) - // Run shutdown hooks in opposite order to starting for (toRun in runOnStop.reversed()) { toRun() @@ -738,7 +733,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, override val attachments: AttachmentStorage get() = this@AbstractNode.attachments override val networkService: MessagingService get() = network override val clock: Clock get() = platformClock - override val myNodeStateObservable: Observable get() = nodeStateObservable override val configuration: NodeConfiguration get() = this@AbstractNode.configuration override fun cordaService(type: Class): T { require(type.isAnnotationPresent(CordaService::class.java)) { "${type.name} is not a Corda service" } diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt index 660ce4b767..ef0597c675 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -117,10 +117,6 @@ internal class CordaRPCOpsImpl( return services.myInfo } - override fun nodeStateObservable(): Observable { - return services.myNodeStateObservable - } - override fun notaryIdentities(): List { return services.networkMapCache.notaryIdentities } diff --git a/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt b/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt index 2ae8594db9..7a1e66f6ea 100644 --- a/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt +++ b/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt @@ -8,14 +8,12 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.DataFeed -import net.corda.core.messaging.NodeState import net.corda.core.node.NodeInfo import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.Vault import net.corda.core.node.services.vault.* import net.corda.node.services.messaging.RpcAuthContext -import rx.Observable import java.io.InputStream import java.security.PublicKey @@ -68,8 +66,6 @@ class RpcAuthorisationProxy(private val implementation: CordaRPCOps, private val override fun nodeInfo(): NodeInfo = guard("nodeInfo", implementation::nodeInfo) - override fun nodeStateObservable(): Observable = guard("nodeStateObservable", implementation::nodeStateObservable) - override fun notaryIdentities(): List = guard("notaryIdentities", implementation::notaryIdentities) override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) = guard("addVaultTransactionNote") { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index 4ed9b5ef94..696472bb9e 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -10,7 +10,6 @@ import net.corda.core.identity.PartyAndCertificate import net.corda.core.messaging.DataFeed import net.corda.core.messaging.FlowHandle import net.corda.core.messaging.FlowProgressHandle -import net.corda.core.messaging.NodeState import net.corda.core.node.* import net.corda.core.node.services.* import net.corda.core.serialization.SerializeAsToken @@ -179,8 +178,6 @@ open class MockServices( val identity = getTestPartyAndCertificate(initialIdentityName, key.public) return NodeInfo(emptyList(), listOf(identity), 1, serial = 1L) } - override val myNodeStateObservable: Observable - get() = PublishSubject.create() override val transactionVerifierService: TransactionVerifierService get() = InMemoryTransactionVerifierService(2) val mockCordappProvider = MockCordappProvider(cordappLoader, attachments) override val cordappProvider: CordappProvider get() = mockCordappProvider From ad9f335dde02702ee82fa44fd1229cc72da2530e Mon Sep 17 00:00:00 2001 From: szymonsztuka Date: Tue, 21 Nov 2017 09:57:38 +0000 Subject: [PATCH 27/31] Consistent database columns naming convention. (#2097) --- .../node/services/transactions/RaftUniquenessProvider.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt index 1976c0e82c..5b1e1743d1 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt @@ -75,10 +75,10 @@ class RaftUniquenessProvider(private val transportConfiguration: NodeSSLConfigur var key: String = "", @Lob - @Column + @Column(name = "state_value") var value: ByteArray = ByteArray(0), - @Column + @Column(name = "state_index") var index: Long = 0 ) From 37b4d0e939c9b3e51752e13bf4ff2747fe98a824 Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Tue, 21 Nov 2017 11:20:17 +0000 Subject: [PATCH 28/31] Fixed some permission issues with demos. (#2099) --- samples/attachment-demo/build.gradle | 5 ++++- samples/bank-of-corda-demo/build.gradle | 10 ++++++++-- .../main/kotlin/net/corda/bank/BankOfCordaDriver.kt | 7 +++++-- samples/irs-demo/cordapp/build.gradle | 4 +++- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/samples/attachment-demo/build.gradle b/samples/attachment-demo/build.gradle index b977d45ffb..93f10fefb1 100644 --- a/samples/attachment-demo/build.gradle +++ b/samples/attachment-demo/build.gradle @@ -35,7 +35,10 @@ dependencies { } task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { - ext.rpcUsers = [['username': "demo", 'password': "demo", 'permissions': ["StartFlow.net.corda.attachmentdemo.AttachmentDemoFlow"]]] + ext.rpcUsers = [['username': "demo", 'password': "demo", 'permissions': ["StartFlow.net.corda.attachmentdemo.AttachmentDemoFlow", + "InvokeRpc.attachmentExists", + "InvokeRpc.uploadAttachment", + "InvokeRpc.internalVerifiedTransactionsFeed"]]] directory "./build/nodes" node { diff --git a/samples/bank-of-corda-demo/build.gradle b/samples/bank-of-corda-demo/build.gradle index d95d203fc4..4d291f727a 100644 --- a/samples/bank-of-corda-demo/build.gradle +++ b/samples/bank-of-corda-demo/build.gradle @@ -71,7 +71,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { "StartFlow.net.corda.finance.flows.CashConfigDataFlow", "StartFlow.net.corda.finance.flows.CashExitFlow", "StartFlow.net.corda.finance.flows.CashIssueAndPaymentFlow", - "StartFlow.net.corda.finance.flows.CashConfigDataFlow"]] + "StartFlow.net.corda.finance.flows.CashConfigDataFlow", + "InvokeRpc.waitUntilNetworkReady", + "InvokeRpc.wellKnownPartyFromX500Name", + "InvokeRpc.notaryIdentities"]] ] } node { @@ -84,7 +87,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { ['username' : "bigCorpUser", 'password' : "test", 'permissions': ["StartFlow.net.corda.finance.flows.CashPaymentFlow", - "StartFlow.net.corda.finance.flows.CashConfigDataFlow"]] + "StartFlow.net.corda.finance.flows.CashConfigDataFlow", + "InvokeRpc.waitUntilNetworkReady", + "InvokeRpc.wellKnownPartyFromX500Name", + "InvokeRpc.notaryIdentities"]] ] } } diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt index f836788d71..00ada75a26 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt @@ -9,6 +9,7 @@ import net.corda.finance.flows.CashConfigDataFlow import net.corda.finance.flows.CashExitFlow import net.corda.finance.flows.CashIssueAndPaymentFlow import net.corda.finance.flows.CashPaymentFlow +import net.corda.node.services.Permissions.Companion.all import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.BOC @@ -65,7 +66,8 @@ private class BankOfCordaDriver { startFlow(), startFlow(), startFlow(), - startFlow() + startFlow(), + all() )) val bankOfCorda = startNode( providedName = BOC.name, @@ -73,7 +75,8 @@ private class BankOfCordaDriver { val bigCorpUser = User(BIGCORP_USERNAME, "test", permissions = setOf( startFlow(), - startFlow())) + startFlow(), + all())) startNode(providedName = BIGCORP_LEGAL_NAME, rpcUsers = listOf(bigCorpUser)) startWebserver(bankOfCorda.get()) } diff --git a/samples/irs-demo/cordapp/build.gradle b/samples/irs-demo/cordapp/build.gradle index 74dfa92b45..66e7aa6953 100644 --- a/samples/irs-demo/cordapp/build.gradle +++ b/samples/irs-demo/cordapp/build.gradle @@ -51,7 +51,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { 'permissions' : [ "StartFlow.net.corda.irs.flows.AutoOfferFlow\$Requester", "StartFlow.net.corda.irs.flows.UpdateBusinessDayFlow\$Broadcast", - "StartFlow.net.corda.irs.api.NodeInterestRates\$UploadFixesFlow" + "StartFlow.net.corda.irs.api.NodeInterestRates\$UploadFixesFlow", + "InvokeRpc.vaultQueryBy", + "InvokeRpc.networkMapSnapshot" ]] ] From 15dbbb2de9dd122aa4e6ee002d7887544352d8d9 Mon Sep 17 00:00:00 2001 From: bpaunescu Date: Tue, 21 Nov 2017 12:03:51 +0000 Subject: [PATCH 29/31] remove NodeState and nodeStateObservable remnants (#2102) --- .../src/main/kotlin/net/corda/node/shell/RPCOpsWithContext.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/shell/RPCOpsWithContext.kt b/node/src/main/kotlin/net/corda/node/shell/RPCOpsWithContext.kt index 75a17d7486..31a15d89f6 100644 --- a/node/src/main/kotlin/net/corda/node/shell/RPCOpsWithContext.kt +++ b/node/src/main/kotlin/net/corda/node/shell/RPCOpsWithContext.kt @@ -56,10 +56,6 @@ class RPCOpsWithContext(val cordaRPCOps: CordaRPCOps, val invocationContext:Invo return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.queryAttachments(query, sorting) }.get().getOrThrow() } - override fun nodeStateObservable(): Observable { - return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.nodeStateObservable() }.get().getOrThrow() - } - override fun vaultTrackByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): DataFeed, Vault.Update> { return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackByWithSorting(contractStateType, criteria, sorting) }.get().getOrThrow() } From 288eb5fcc41afce5e6227fed4cc27f6160f1b2f3 Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Tue, 21 Nov 2017 12:49:21 +0000 Subject: [PATCH 30/31] CORDA-716 Call stop on InMemoryMessagingNetwork (#2077) * Inline code used by only 1 test * Remove superfluous interface * Warnings crusade * Inline Builder, remove unused method * Remove stop from interface * Register stops up-front --- .../corda/core/flows/ReceiveAllFlowTests.kt | 55 ++++++------ .../net/corda/node/internal/AbstractNode.kt | 4 - .../kotlin/net/corda/node/internal/Node.kt | 10 ++- .../node/services/messaging/Messaging.kt | 9 -- .../services/messaging/P2PMessagingClient.kt | 8 +- .../net/corda/node/shell/RPCOpsWithContext.kt | 1 - .../testing/node/InMemoryMessagingNetwork.kt | 90 ++++--------------- .../kotlin/net/corda/testing/node/MockNode.kt | 20 +---- 8 files changed, 62 insertions(+), 135 deletions(-) diff --git a/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt index ea2db6947b..dd2dbab17f 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt @@ -5,46 +5,45 @@ import net.corda.core.identity.Party import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap -import net.corda.testing.node.network +import net.corda.testing.node.MockNetwork import net.corda.testing.singleIdentity import net.corda.testing.startFlow import org.assertj.core.api.Assertions.assertThat +import org.junit.After import org.junit.Test class ReceiveMultipleFlowTests { + private val mockNet = MockNetwork() + private val nodes = (0..2).map { mockNet.createPartyNode() } + @After + fun stopNodes() { + mockNet.stopNodes() + } + @Test fun `receive all messages in parallel using map style`() { - network(3) { nodes -> - val doubleValue = 5.0 - nodes[1].registerAnswer(AlgorithmDefinition::class, doubleValue) - val stringValue = "Thriller" - nodes[2].registerAnswer(AlgorithmDefinition::class, stringValue) - - val flow = nodes[0].services.startFlow(ParallelAlgorithmMap(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity())) - runNetwork() - - val result = flow.resultFuture.getOrThrow() - - assertThat(result).isEqualTo(doubleValue * stringValue.length) - } + val doubleValue = 5.0 + nodes[1].registerAnswer(AlgorithmDefinition::class, doubleValue) + val stringValue = "Thriller" + nodes[2].registerAnswer(AlgorithmDefinition::class, stringValue) + val flow = nodes[0].services.startFlow(ParallelAlgorithmMap(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity())) + mockNet.runNetwork() + val result = flow.resultFuture.getOrThrow() + assertThat(result).isEqualTo(doubleValue * stringValue.length) } @Test fun `receive all messages in parallel using list style`() { - network(3) { nodes -> - val value1 = 5.0 - nodes[1].registerAnswer(ParallelAlgorithmList::class, value1) - val value2 = 6.0 - nodes[2].registerAnswer(ParallelAlgorithmList::class, value2) - - val flow = nodes[0].services.startFlow(ParallelAlgorithmList(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity())) - runNetwork() - val data = flow.resultFuture.getOrThrow() - - assertThat(data[0]).isEqualTo(value1) - assertThat(data[1]).isEqualTo(value2) - assertThat(data.fold(1.0) { a, b -> a * b }).isEqualTo(value1 * value2) - } + val value1 = 5.0 + nodes[1].registerAnswer(ParallelAlgorithmList::class, value1) + val value2 = 6.0 + nodes[2].registerAnswer(ParallelAlgorithmList::class, value2) + val flow = nodes[0].services.startFlow(ParallelAlgorithmList(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity())) + mockNet.runNetwork() + val data = flow.resultFuture.getOrThrow() + assertThat(data[0]).isEqualTo(value1) + assertThat(data[1]).isEqualTo(value2) + assertThat(data.fold(1.0) { a, b -> a * b }).isEqualTo(value1 * value2) } class ParallelAlgorithmMap(doubleMember: Party, stringMember: Party) : AlgorithmDefinition(doubleMember, stringMember) { diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 149f11668e..89af824be0 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -213,11 +213,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, registerCordappFlows() _services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows } FlowLogicRefFactoryImpl.classloader = cordappLoader.appClassLoader - - runOnStop += network::stop - startShell(rpcOps) - Pair(StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, network, database, rpcOps, flowStarter, notaryService), schedulerService) } diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index 2f378ed7ba..5cf3f33f18 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -16,7 +16,6 @@ import net.corda.core.utilities.contextLogger import net.corda.node.VersionInfo import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.serialization.KryoServerSerializationScheme -import net.corda.node.services.RPCUserService import net.corda.node.services.RPCUserServiceImpl import net.corda.node.services.api.SchemaService import net.corda.node.services.config.NodeConfiguration @@ -206,14 +205,17 @@ open class Node(configuration: NodeConfiguration, } // Start up the MQ clients. rpcMessagingClient.run { - start(rpcOps, userService) runOnStop += this::stop + start(rpcOps, userService) } verifierMessagingClient?.run { - start() runOnStop += this::stop + start() + } + (network as P2PMessagingClient).apply { + runOnStop += this::stop + start() } - (network as P2PMessagingClient).start() } /** diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt index 2241e3096f..4d91e00ec6 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt @@ -1,6 +1,5 @@ package net.corda.node.services.messaging -import com.google.common.util.concurrent.ListenableFuture import net.corda.core.concurrent.CordaFuture import net.corda.core.identity.CordaX500Name import net.corda.core.internal.concurrent.openFuture @@ -133,14 +132,6 @@ interface MessagingService { /** Returns an address that refers to this node. */ val myAddress: SingleMessageRecipient - - /** - * Initiates shutdown: if called from a thread that isn't controlled by the executor passed to the constructor - * then this will block until all in-flight messages have finished being handled and acknowledged. If called - * from a thread that's a part of the [net.corda.node.utilities.AffinityExecutor] given to the constructor, - * it returns immediately and shutdown is asynchronous. - */ - fun stop() } /** diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt index e78dfb54af..8b56a45e70 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt @@ -323,7 +323,13 @@ class P2PMessagingClient(config: NodeConfiguration, } } - override fun stop() { + /** + * Initiates shutdown: if called from a thread that isn't controlled by the executor passed to the constructor + * then this will block until all in-flight messages have finished being handled and acknowledged. If called + * from a thread that's a part of the [net.corda.node.utilities.AffinityExecutor] given to the constructor, + * it returns immediately and shutdown is asynchronous. + */ + fun stop() { val running = state.locked { // We allow stop() to be called without a run() in between, but it must have at least been started. check(artemis.started != null) diff --git a/node/src/main/kotlin/net/corda/node/shell/RPCOpsWithContext.kt b/node/src/main/kotlin/net/corda/node/shell/RPCOpsWithContext.kt index 31a15d89f6..adcb8f30b7 100644 --- a/node/src/main/kotlin/net/corda/node/shell/RPCOpsWithContext.kt +++ b/node/src/main/kotlin/net/corda/node/shell/RPCOpsWithContext.kt @@ -19,7 +19,6 @@ import net.corda.core.utilities.getOrThrow import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT import net.corda.node.services.messaging.RpcAuthContext import net.corda.node.services.messaging.RpcPermissions -import rx.Observable import java.io.InputStream import java.security.PublicKey import java.time.Instant diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt index e1195084a5..11fc77758d 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt @@ -1,6 +1,5 @@ package net.corda.testing.node -import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.CompositeKey import net.corda.core.identity.CordaX500Name import net.corda.core.internal.ThreadBox @@ -10,7 +9,6 @@ import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.openFuture import net.corda.core.node.services.PartyInfo import net.corda.core.serialization.CordaSerializable @@ -33,19 +31,6 @@ import javax.annotation.concurrent.ThreadSafe import kotlin.concurrent.schedule import kotlin.concurrent.thread -/** - * This class lets you start up a [MessagingService]. Its purpose is to stop you from getting access to the methods - * on the messaging service interface until you have successfully started up the system. One of these objects should - * be the only way to obtain a reference to a [MessagingService]. Startup may be a slow process: some implementations - * may let you cast the returned future to an object that lets you get status info. - * - * A specific implementation of the controller class will have extra features that let you customise it before starting - * it up. - */ -interface MessagingServiceBuilder { - fun start(): CordaFuture -} - /** * An in-memory network allows you to manufacture [InMemoryMessaging]s for a set of participants. Each * [InMemoryMessaging] maintains a queue of messages it has received, and a background thread that dispatches @@ -59,11 +44,11 @@ interface MessagingServiceBuilder { @ThreadSafe class InMemoryMessagingNetwork( val sendManuallyPumped: Boolean, - val servicePeerAllocationStrategy: ServicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(), + private val servicePeerAllocationStrategy: ServicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(), private val messagesInFlight: ReusableLatch = ReusableLatch() ) : SingletonSerializeAsToken() { companion object { - const val MESSAGES_LOG_NAME = "messages" + private const val MESSAGES_LOG_NAME = "messages" private val log = LoggerFactory.getLogger(MESSAGES_LOG_NAME) } @@ -103,38 +88,15 @@ class InMemoryMessagingNetwork( get() = _receivedMessages val endpoints: List @Synchronized get() = handleEndpointMap.values.toList() - fun endpoint(peer: PeerHandle): InMemoryMessaging? = handleEndpointMap.get(peer) - - /** - * Creates a node and returns the new object that identifies its location on the network to senders, and the - * [InMemoryMessaging] that the recipient/in-memory node uses to receive messages and send messages itself. - * - * If [manuallyPumped] is set to true, then you are expected to call the [InMemoryMessaging.pump] method on the [InMemoryMessaging] - * in order to cause the delivery of a single message, which will occur on the thread of the caller. If set to false - * then this class will set up a background thread to deliver messages asynchronously, if the handler specifies no - * executor. - * - * @param persistenceTx a lambda to wrap message handling in a transaction if necessary. Defaults to a no-op. - */ - @Synchronized - fun createNode(manuallyPumped: Boolean, - executor: AffinityExecutor, - notaryService: PartyAndCertificate?, - database: CordaPersistence): Pair> { - check(counter >= 0) { "In memory network stopped: please recreate." } - val builder = createNodeWithID(manuallyPumped, counter, executor, notaryService, database = database) as Builder - counter++ - val id = builder.id - return Pair(id, builder) - } - /** * Creates a node at the given address: useful if you want to recreate a node to simulate a restart. * - * @param manuallyPumped see [createNode]. + * @param manuallyPumped if set to true, then you are expected to call the [InMemoryMessaging.pump] method on the [InMemoryMessaging] + * in order to cause the delivery of a single message, which will occur on the thread of the caller. If set to false + * then this class will set up a background thread to deliver messages asynchronously, if the handler specifies no + * executor. * @param id the numeric ID to use, e.g. set to whatever ID the node used last time. * @param description text string that identifies this node for message logging (if is enabled) or null to autogenerate. - * @param persistenceTx a lambda to wrap message handling in a transaction if necessary. */ fun createNodeWithID( manuallyPumped: Boolean, @@ -143,12 +105,19 @@ class InMemoryMessagingNetwork( notaryService: PartyAndCertificate?, description: CordaX500Name = CordaX500Name(organisation = "In memory node $id", locality = "London", country = "UK"), database: CordaPersistence) - : MessagingServiceBuilder { + : InMemoryMessaging { val peerHandle = PeerHandle(id, description) peersMapping[peerHandle.description] = peerHandle // Assume that the same name - the same entity in MockNetwork. notaryService?.let { if (it.owningKey !is CompositeKey) peersMapping[it.name] = peerHandle } val serviceHandles = notaryService?.let { listOf(ServiceHandle(it.party)) } ?: emptyList() //TODO only notary can be distributed? - return Builder(manuallyPumped, peerHandle, serviceHandles, executor, database = database) + synchronized(this) { + val node = InMemoryMessaging(manuallyPumped, peerHandle, executor, database) + handleEndpointMap[peerHandle] = node + serviceHandles.forEach { + serviceToPeersMapping.getOrPut(it) { LinkedHashSet() }.add(peerHandle) + } + return node + } } interface LatencyCalculator { @@ -157,7 +126,7 @@ class InMemoryMessagingNetwork( /** This can be set to an object which can inject artificial latency between sender/recipient pairs. */ @Volatile - var latencyCalculator: LatencyCalculator? = null + private var latencyCalculator: LatencyCalculator? = null private val timer = Timer() @Synchronized @@ -197,25 +166,6 @@ class InMemoryMessagingNetwork( messageReceiveQueues.clear() } - inner class Builder( - val manuallyPumped: Boolean, - val id: PeerHandle, - val serviceHandles: List, - val executor: AffinityExecutor, - val database: CordaPersistence) : MessagingServiceBuilder { - override fun start(): CordaFuture { - synchronized(this@InMemoryMessagingNetwork) { - val node = InMemoryMessaging(manuallyPumped, id, executor, database) - handleEndpointMap[id] = node - serviceHandles.forEach { - serviceToPeersMapping.getOrPut(it) { LinkedHashSet() }.add(id) - Unit - } - return doneFuture(node) - } - } - } - @CordaSerializable data class PeerHandle(val id: Int, val description: CordaX500Name) : SingleMessageRecipient { override fun toString() = description.toString() @@ -240,7 +190,7 @@ class InMemoryMessagingNetwork( } class RoundRobin : ServicePeerAllocationStrategy() { - val previousPicks = HashMap() + private val previousPicks = HashMap() override fun pickNext(service: ServiceHandle, pickFrom: List): A { val nextIndex = previousPicks.compute(service) { _, previous -> (previous?.plus(1) ?: 0) % pickFrom.size @@ -392,7 +342,7 @@ class InMemoryMessagingNetwork( acknowledgementHandler?.invoke() } - override fun stop() { + fun stop() { if (backgroundThread != null) { backgroundThread.interrupt() backgroundThread.join() @@ -463,9 +413,7 @@ class InMemoryMessagingNetwork( private fun pumpReceiveInternal(block: Boolean): MessageTransfer? { val q = getQueueForPeerHandle(peerHandle) - val next = getNextQueue(q, block) ?: return null - val (transfer, deliverTo) = next - + val (transfer, deliverTo) = getNextQueue(q, block) ?: return null if (transfer.message.uniqueMessageId !in processedMessages) { executor.execute { database.transaction { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index aea7accf55..f10b8f9e5e 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -22,7 +22,6 @@ import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SerializationWhitelist import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger -import net.corda.core.utilities.getOrThrow import net.corda.node.internal.AbstractNode import net.corda.node.internal.StartedNode import net.corda.node.internal.cordapp.CordappLoader @@ -45,8 +44,6 @@ import net.corda.testing.setGlobalSerialization import net.corda.testing.testNodeConfiguration import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.sshd.common.util.security.SecurityUtils -import org.slf4j.Logger -import java.io.Closeable import java.math.BigInteger import java.nio.file.Path import java.security.KeyPair @@ -123,7 +120,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete private val defaultFactory: (MockNodeArgs) -> MockNode = defaultParameters.defaultFactory, initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, private val notarySpecs: List = listOf(NotarySpec(DUMMY_NOTARY.name)), - private val cordappPackages: List = defaultParameters.cordappPackages) : Closeable { + private val cordappPackages: List = defaultParameters.cordappPackages) { /** Helper constructor for creating a [MockNetwork] with custom parameters from Java. */ constructor(parameters: MockNetworkParameters) : this(defaultParameters = parameters) @@ -267,8 +264,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete serverThread, myNotaryIdentity, myLegalName, - database - ).start().getOrThrow() + database).also { runOnStop += it::stop } } fun setMessagingServiceSpy(messagingServiceSpy: MessagingServiceSpy) { @@ -424,6 +420,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete fun stopNodes() { nodes.forEach { it.started?.dispose() } serializationEnv.unset() + messagingNetwork.stop() } // Test method to block until all scheduled activity, active flows @@ -432,22 +429,11 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete busyLatch.await() } - override fun close() { - stopNodes() - } - data class NotarySpec(val name: CordaX500Name, val validating: Boolean = true) { constructor(name: CordaX500Name) : this(name, validating = true) } } -fun network(nodesCount: Int, action: MockNetwork.(List>) -> Unit) { - MockNetwork().use { mockNet -> - val nodes = (1..nodesCount).map { mockNet.createPartyNode() } - mockNet.action(nodes) - } -} - /** * Extend this class in order to intercept and modify messages passing through the [MessagingService] when using the [InMemoryMessagingNetwork]. */ From f7e51a9ae1cb06b408f8e27c38ccce4ffd575d87 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Mon, 20 Nov 2017 11:57:58 +0000 Subject: [PATCH 31/31] Various cleanups to AMQP and Kryo serialisation: * PartyAndCertificate serialiser has been converted to deal with just CertPath * Removed X500Name serialiser as we no longer use the BC implementation and have our own CordaX500Name * Converted X509 cert serialiser to use Java certs and not BC X509 cert holder * Added to test to AMQP serialisation to make sure c'tor is invoked on deserialisation --- .../corda/core/contracts/PrivacySaltTest.kt | 13 +++ .../amqp/AMQPSerializationScheme.kt | 5 +- .../amqp/custom/CertPathSerializer.kt | 27 +++++++ .../custom/PartyAndCertificateSerializer.kt | 30 ------- .../amqp/custom/X500NameSerializer.kt | 23 ------ .../custom/X509CertificateHolderSerializer.kt | 23 ------ .../amqp/custom/X509CertificateSerializer.kt | 27 +++++++ .../kryo/DefaultKryoCustomizer.kt | 6 +- .../internal/serialization/kryo/Kryo.kt | 45 +++-------- .../internal/serialization/KryoTests.kt | 21 ++--- .../serialization/MapsSerializationTest.kt | 36 +++++---- .../amqp/SerializationOutputTests.kt | 79 +++++++++++++------ 12 files changed, 166 insertions(+), 169 deletions(-) create mode 100644 core/src/test/kotlin/net/corda/core/contracts/PrivacySaltTest.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/CertPathSerializer.kt delete mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PartyAndCertificateSerializer.kt delete mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X500NameSerializer.kt delete mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateHolderSerializer.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateSerializer.kt diff --git a/core/src/test/kotlin/net/corda/core/contracts/PrivacySaltTest.kt b/core/src/test/kotlin/net/corda/core/contracts/PrivacySaltTest.kt new file mode 100644 index 0000000000..c9fc2033a6 --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/contracts/PrivacySaltTest.kt @@ -0,0 +1,13 @@ +package net.corda.core.contracts + +import org.assertj.core.api.Assertions.assertThatExceptionOfType +import org.junit.Test + +class PrivacySaltTest { + @Test + fun `all-zero PrivacySalt not allowed`() { + assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy { + PrivacySalt(ByteArray(32)) + }.withMessage("Privacy salt should not be all zeros.") + } +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt index ca3487ead8..4327d114ea 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt @@ -36,7 +36,6 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme { register(publicKeySerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.PrivateKeySerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.ThrowableSerializer(this)) - register(net.corda.nodeapi.internal.serialization.amqp.custom.X500NameSerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.BigDecimalSerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.CurrencySerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.OpaqueBytesSubSequenceSerializer(this)) @@ -54,8 +53,8 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme { register(net.corda.nodeapi.internal.serialization.amqp.custom.MonthDaySerializer(this)) register(net.corda.nodeapi.internal.serialization.amqp.custom.PeriodSerializer(this)) register(net.corda.nodeapi.internal.serialization.amqp.custom.ClassSerializer(this)) - register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer) - register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory)) + register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateSerializer) + register(net.corda.nodeapi.internal.serialization.amqp.custom.CertPathSerializer(this)) register(net.corda.nodeapi.internal.serialization.amqp.custom.StringBufferSerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.SimpleStringSerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.InputStreamSerializer) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/CertPathSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/CertPathSerializer.kt new file mode 100644 index 0000000000..b6ed889190 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/CertPathSerializer.kt @@ -0,0 +1,27 @@ +package net.corda.nodeapi.internal.serialization.amqp.custom + +import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer +import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory +import java.io.NotSerializableException +import java.security.cert.CertPath +import java.security.cert.CertificateException +import java.security.cert.CertificateFactory + +class CertPathSerializer(factory: SerializerFactory) + : CustomSerializer.Proxy(CertPath::class.java, CertPathProxy::class.java, factory) { + override fun toProxy(obj: CertPath): CertPathProxy = CertPathProxy(obj.type, obj.encoded) + + override fun fromProxy(proxy: CertPathProxy): CertPath { + try { + val cf = CertificateFactory.getInstance(proxy.type) + return cf.generateCertPath(proxy.encoded.inputStream()) + } catch (ce: CertificateException) { + val nse = NotSerializableException("java.security.cert.CertPath: $type") + nse.initCause(ce) + throw nse + } + } + + @Suppress("ArrayInDataClass") + data class CertPathProxy(val type: String, val encoded: ByteArray) +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PartyAndCertificateSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PartyAndCertificateSerializer.kt deleted file mode 100644 index 8bdde643d7..0000000000 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PartyAndCertificateSerializer.kt +++ /dev/null @@ -1,30 +0,0 @@ -package net.corda.nodeapi.internal.serialization.amqp.custom - -import net.corda.core.crypto.Crypto -import net.corda.core.identity.PartyAndCertificate -import net.corda.nodeapi.internal.serialization.amqp.* -import java.io.ByteArrayInputStream -import java.io.NotSerializableException -import java.security.cert.CertPath -import java.security.cert.CertificateException -import java.security.cert.CertificateFactory - -/** - * A serializer that writes out a party and certificate in encoded format. - */ -class PartyAndCertificateSerializer(factory: SerializerFactory) : CustomSerializer.Proxy(PartyAndCertificate::class.java, PartyAndCertificateProxy::class.java, factory) { - override fun toProxy(obj: PartyAndCertificate): PartyAndCertificateProxy = PartyAndCertificateProxy(obj.certPath.type, obj.certPath.encoded) - - override fun fromProxy(proxy: PartyAndCertificateProxy): PartyAndCertificate { - try { - val cf = CertificateFactory.getInstance(proxy.type) - return PartyAndCertificate(cf.generateCertPath(ByteArrayInputStream(proxy.encoded))) - } catch (ce: CertificateException) { - val nse = NotSerializableException("java.security.cert.CertPath: " + type) - nse.initCause(ce) - throw nse - } - } - - data class PartyAndCertificateProxy(val type: String, val encoded: ByteArray) -} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X500NameSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X500NameSerializer.kt deleted file mode 100644 index 0138d28dd0..0000000000 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X500NameSerializer.kt +++ /dev/null @@ -1,23 +0,0 @@ -package net.corda.nodeapi.internal.serialization.amqp.custom - -import net.corda.nodeapi.internal.serialization.amqp.* -import org.apache.qpid.proton.codec.Data -import org.bouncycastle.asn1.ASN1InputStream -import org.bouncycastle.asn1.x500.X500Name -import java.lang.reflect.Type - -/** - * Custom serializer for X500 names that utilizes their ASN.1 encoding on the wire. - */ -object X500NameSerializer : CustomSerializer.Implements(X500Name::class.java) { - override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, descriptor, emptyList()))) - - override fun writeDescribedObject(obj: X500Name, data: Data, type: Type, output: SerializationOutput) { - output.writeObject(obj.encoded, data, clazz) - } - - override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): X500Name { - val binary = input.readObject(obj, schema, ByteArray::class.java) as ByteArray - return X500Name.getInstance(ASN1InputStream(binary).readObject()) - } -} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateHolderSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateHolderSerializer.kt deleted file mode 100644 index 13ca64dd75..0000000000 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateHolderSerializer.kt +++ /dev/null @@ -1,23 +0,0 @@ -package net.corda.nodeapi.internal.serialization.amqp.custom - -import net.corda.core.crypto.Crypto -import net.corda.nodeapi.internal.serialization.amqp.* -import org.apache.qpid.proton.codec.Data -import org.bouncycastle.cert.X509CertificateHolder -import java.lang.reflect.Type - -/** - * A serializer that writes out a certificate in X.509 format. - */ -object X509CertificateHolderSerializer : CustomSerializer.Implements(X509CertificateHolder::class.java) { - override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, descriptor, emptyList()))) - - override fun writeDescribedObject(obj: X509CertificateHolder, data: Data, type: Type, output: SerializationOutput) { - output.writeObject(obj.encoded, data, clazz) - } - - override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): X509CertificateHolder { - val bits = input.readObject(obj, schema, ByteArray::class.java) as ByteArray - return X509CertificateHolder(bits) - } -} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateSerializer.kt new file mode 100644 index 0000000000..a1e3c17b8f --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateSerializer.kt @@ -0,0 +1,27 @@ +package net.corda.nodeapi.internal.serialization.amqp.custom + +import net.corda.nodeapi.internal.serialization.amqp.* +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Type +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate + +object X509CertificateSerializer : CustomSerializer.Implements(X509Certificate::class.java) { + override val schemaForDocumentation = Schema(listOf(RestrictedType( + type.toString(), + "", + listOf(type.toString()), + SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, + descriptor, + emptyList() + ))) + + override fun writeDescribedObject(obj: X509Certificate, data: Data, type: Type, output: SerializationOutput) { + output.writeObject(obj.encoded, data, clazz) + } + + override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): X509Certificate { + val bits = input.readObject(obj, schema, ByteArray::class.java) as ByteArray + return CertificateFactory.getInstance("X.509").generateCertificate(bits.inputStream()) as X509Certificate + } +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt index 4191d72842..7d69264a43 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt @@ -32,8 +32,6 @@ import net.corda.nodeapi.internal.serialization.GeneratedAttachment import net.corda.nodeapi.internal.serialization.MutableClassWhitelist import net.i2p.crypto.eddsa.EdDSAPrivateKey import net.i2p.crypto.eddsa.EdDSAPublicKey -import org.bouncycastle.asn1.x500.X500Name -import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey @@ -53,6 +51,7 @@ import java.io.InputStream import java.lang.reflect.Modifier.isPublic import java.security.PublicKey import java.security.cert.CertPath +import java.security.cert.X509Certificate import java.util.* import kotlin.collections.ArrayList @@ -109,8 +108,7 @@ object DefaultKryoCustomizer { register(FileInputStream::class.java, InputStreamSerializer) register(CertPath::class.java, CertPathSerializer) register(X509CertPath::class.java, CertPathSerializer) - register(X500Name::class.java, X500NameSerializer) - register(X509CertificateHolder::class.java, X509CertificateSerializer) + register(X509Certificate::class.java, X509CertificateSerializer) register(BCECPrivateKey::class.java, PrivateKeySerializer) register(BCECPublicKey::class.java, publicKeySerializer) register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt index 8cdc24f941..d3927dd47d 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt @@ -15,7 +15,8 @@ import net.corda.core.crypto.TransactionSignature import net.corda.core.identity.Party import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.SerializationContext -import net.corda.core.serialization.SerializationContext.UseCase.* +import net.corda.core.serialization.SerializationContext.UseCase.Checkpoint +import net.corda.core.serialization.SerializationContext.UseCase.Storage import net.corda.core.serialization.SerializeAsTokenContext import net.corda.core.serialization.SerializedBytes import net.corda.core.toFuture @@ -23,9 +24,6 @@ import net.corda.core.toObservable import net.corda.core.transactions.* import net.corda.nodeapi.internal.serialization.CordaClassResolver import net.corda.nodeapi.internal.serialization.serializationContextKey -import org.bouncycastle.asn1.ASN1InputStream -import org.bouncycastle.asn1.x500.X500Name -import org.bouncycastle.cert.X509CertificateHolder import org.slf4j.Logger import org.slf4j.LoggerFactory import rx.Observable @@ -36,6 +34,7 @@ import java.security.PrivateKey import java.security.PublicKey import java.security.cert.CertPath import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate import java.util.* import javax.annotation.concurrent.ThreadSafe import kotlin.reflect.KClass @@ -471,46 +470,28 @@ object ClassSerializer : Serializer>() { } } -/** - * For serialising an [X500Name] without touching Sun internal classes. - */ -@ThreadSafe -object X500NameSerializer : Serializer() { - override fun read(kryo: Kryo, input: Input, type: Class): X500Name { - return X500Name.getInstance(ASN1InputStream(input.readBytes()).readObject()) - } - - override fun write(kryo: Kryo, output: Output, obj: X500Name) { - output.writeBytes(obj.encoded) - } -} - -/** - * For serialising an [CertPath] in an X.500 standard format. - */ @ThreadSafe object CertPathSerializer : Serializer() { - val factory: CertificateFactory = CertificateFactory.getInstance("X.509") override fun read(kryo: Kryo, input: Input, type: Class): CertPath { - return factory.generateCertPath(input) + val factory = CertificateFactory.getInstance(input.readString()) + return factory.generateCertPath(input.readBytesWithLength().inputStream()) } override fun write(kryo: Kryo, output: Output, obj: CertPath) { - output.writeBytes(obj.encoded) + output.writeString(obj.type) + output.writeBytesWithLength(obj.encoded) } } -/** - * For serialising an [X509CertificateHolder] in an X.500 standard format. - */ @ThreadSafe -object X509CertificateSerializer : Serializer() { - override fun read(kryo: Kryo, input: Input, type: Class): X509CertificateHolder { - return X509CertificateHolder(input.readBytes()) +object X509CertificateSerializer : Serializer() { + override fun read(kryo: Kryo, input: Input, type: Class): X509Certificate { + val factory = CertificateFactory.getInstance("X.509") + return factory.generateCertificate(input.readBytesWithLength().inputStream()) as X509Certificate } - override fun write(kryo: Kryo, output: Output, obj: X509CertificateHolder) { - output.writeBytes(obj.encoded) + override fun write(kryo: Kryo, output: Output, obj: X509Certificate) { + output.writeBytesWithLength(obj.encoded) } } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt index cafeb14dd0..6eee1230be 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt @@ -21,13 +21,14 @@ import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Before import org.junit.Rule import org.junit.Test -import org.junit.rules.ExpectedException import org.slf4j.LoggerFactory import java.io.ByteArrayInputStream import java.io.InputStream import java.time.Instant -import java.util.Collections -import kotlin.test.* +import java.util.* +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue class KryoTests { @Rule @@ -36,9 +37,6 @@ class KryoTests { private lateinit var factory: SerializationFactory private lateinit var context: SerializationContext - @get:Rule - val expectedEx: ExpectedException = ExpectedException.none() - @Before fun setup() { factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) } @@ -51,7 +49,7 @@ class KryoTests { } @Test - fun ok() { + fun `simple data class`() { val birthday = Instant.parse("1984-04-17T00:30:00.00Z") val mike = Person("mike", birthday) val bits = mike.serialize(factory, context) @@ -59,7 +57,7 @@ class KryoTests { } @Test - fun nullables() { + fun `null values`() { val bob = Person("bob", null) val bits = bob.serialize(factory, context) assertThat(bits.deserialize(factory, context)).isEqualTo(Person("bob", null)) @@ -202,13 +200,6 @@ class KryoTests { assertEquals(expected, actual) } - @Test - fun `all-zero PrivacySalt not allowed`() { - expectedEx.expect(IllegalArgumentException::class.java) - expectedEx.expectMessage("Privacy salt should not be all zeros.") - PrivacySalt(ByteArray(32)) - } - @CordaSerializable private object TestSingleton diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt index ed45e63b68..f726d0b97a 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt @@ -2,16 +2,16 @@ package net.corda.nodeapi.internal.serialization import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.util.DefaultClassResolver +import net.corda.core.identity.CordaX500Name import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.node.services.statemachine.SessionData import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.amqpSpecific import net.corda.testing.kryoSpecific -import net.corda.testing.SerializationEnvironmentRule -import org.assertj.core.api.Assertions -import org.bouncycastle.asn1.x500.X500Name +import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Assert.assertArrayEquals import org.junit.Rule import org.junit.Test @@ -53,7 +53,7 @@ class MapsSerializationTest { fun `check throws for forbidden declared type`() = amqpSpecific("Such exceptions are not expected in Kryo mode.") { val payload = HashMap(smallMap) val wrongPayloadType = WrongPayloadType(payload) - Assertions.assertThatThrownBy { wrongPayloadType.serialize() } + assertThatThrownBy { wrongPayloadType.serialize() } .isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining( "Map type class java.util.HashMap is unstable under iteration. Suggested fix: use java.util.LinkedHashMap instead.") } @@ -62,27 +62,29 @@ class MapsSerializationTest { data class MyKey(val keyContent: Double) @CordaSerializable - data class MyValue(val valueContent: X500Name) + data class MyValue(val valueContent: CordaX500Name) @Test fun `check map serialization works with custom types`() { val myMap = mapOf( - MyKey(1.0) to MyValue(X500Name("CN=one")), - MyKey(10.0) to MyValue(X500Name("CN=ten"))) + MyKey(1.0) to MyValue(CordaX500Name("OOO", "LLL", "CC")), + MyKey(10.0) to MyValue(CordaX500Name("OO", "LL", "CC"))) assertEqualAfterRoundTripSerialization(myMap) } @Test - fun `check empty map serialises as Java emptyMap`() = kryoSpecific("Specifically checks Kryo serialization") { - val nameID = 0 - val serializedForm = emptyMap().serialize() - val output = ByteArrayOutputStream().apply { - write(KryoHeaderV0_1.bytes) - write(DefaultClassResolver.NAME + 2) - write(nameID) - write(javaEmptyMapClass.name.toAscii()) - write(Kryo.NOT_NULL.toInt()) + fun `check empty map serialises as Java emptyMap`() { + kryoSpecific("Specifically checks Kryo serialization") { + val nameID = 0 + val serializedForm = emptyMap().serialize() + val output = ByteArrayOutputStream().apply { + write(KryoHeaderV0_1.bytes) + write(DefaultClassResolver.NAME + 2) + write(nameID) + write(javaEmptyMapClass.name.toAscii()) + write(Kryo.NOT_NULL.toInt()) + } + assertArrayEquals(output.toByteArray(), serializedForm.bytes) } - assertArrayEquals(output.toByteArray(), serializedForm.bytes) } } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt index ea93b0038f..04c390202d 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt @@ -1,17 +1,20 @@ +@file:Suppress("unused", "MemberVisibilityCanPrivate") + package net.corda.nodeapi.internal.serialization.amqp +import net.corda.client.rpc.RPCException import net.corda.core.CordaRuntimeException +import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.secureRandomBytes import net.corda.core.flows.FlowException import net.corda.core.identity.AbstractParty -import net.corda.core.internal.toX509CertHolder +import net.corda.core.internal.AbstractAttachment import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.MissingAttachmentsException import net.corda.core.serialization.SerializationFactory import net.corda.core.transactions.LedgerTransaction -import net.corda.client.rpc.RPCException -import net.corda.core.contracts.* -import net.corda.core.internal.AbstractAttachment -import net.corda.core.serialization.MissingAttachmentsException +import net.corda.core.utilities.OpaqueBytes import net.corda.nodeapi.internal.serialization.AllWhitelist import net.corda.nodeapi.internal.serialization.EmptyWhitelist import net.corda.nodeapi.internal.serialization.GeneratedAttachment @@ -25,10 +28,8 @@ import org.apache.activemq.artemis.api.core.SimpleString import org.apache.qpid.proton.amqp.* import org.apache.qpid.proton.codec.DecoderImpl import org.apache.qpid.proton.codec.EncoderImpl -import org.junit.Assert.assertArrayEquals -import org.junit.Assert.assertNotSame -import org.junit.Assert.assertSame -import org.assertj.core.api.Assertions.assertThatThrownBy +import org.assertj.core.api.Assertions.* +import org.junit.Assert.* import org.junit.Ignore import org.junit.Test import java.io.ByteArrayInputStream @@ -39,10 +40,7 @@ import java.nio.ByteBuffer import java.time.* import java.time.temporal.ChronoUnit import java.util.* -import kotlin.reflect.full.declaredFunctions -import kotlin.reflect.full.declaredMemberFunctions import kotlin.reflect.full.superclasses -import kotlin.reflect.jvm.javaMethod import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -79,7 +77,6 @@ class SerializationOutputTests { } data class Woo(val fred: Int) { - @Suppress("unused") val bob = "Bob" } @@ -89,7 +86,6 @@ class SerializationOutputTests { @CordaSerializable data class AnnotatedWoo(val fred: Int) { - @Suppress("unused") val bob = "Bob" } @@ -151,6 +147,13 @@ class SerializationOutputTests { data class PolymorphicProperty(val foo: FooInterface?) + @CordaSerializable + class NonZeroByte(val value: Byte) { + init { + require(value.toInt() != 0) { "Zero not allowed" } + } + } + private inline fun serdes(obj: T, factory: SerializerFactory = SerializerFactory( AllWhitelist, ClassLoader.getSystemClassLoader()), @@ -406,6 +409,32 @@ class SerializationOutputTests { serdes(obj) } + @Test + fun `class constructor is invoked on deserialisation`() { + val ser = SerializationOutput(SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) + val des = DeserializationInput(ser.serializerFactory) + + val serialisedOne = ser.serialize(NonZeroByte(1)).bytes + val serialisedTwo = ser.serialize(NonZeroByte(2)).bytes + + // Find the index that holds the value byte + val valueIndex = serialisedOne.zip(serialisedTwo).mapIndexedNotNull { index, (oneByte, twoByte) -> + if (oneByte.toInt() == 1 && twoByte.toInt() == 2) index else null + }.single() + + val copy = serialisedTwo.clone() + + // Double check + copy[valueIndex] = 0x03 + assertThat(des.deserialize(OpaqueBytes(copy), NonZeroByte::class.java).value).isEqualTo(3) + + // Now use the forbidden value + copy[valueIndex] = 0x00 + assertThatExceptionOfType(NotSerializableException::class.java).isThrownBy { + des.deserialize(OpaqueBytes(copy), NonZeroByte::class.java) + }.withMessageContaining("Zero not allowed") + } + @Test fun `test custom serializers on public key`() { val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) @@ -762,26 +791,32 @@ class SerializationOutputTests { } @Test - fun `test certificate holder serialize`() { + fun `test privacy salt serialize`() { + serdes(PrivacySalt()) + serdes(PrivacySalt(secureRandomBytes(32))) + } + + @Test + fun `test X509 certificate serialize`() { val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) - factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer) + factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateSerializer) val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) - factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer) + factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateSerializer) - val obj = BOB_IDENTITY.certificate.toX509CertHolder() + val obj = BOB_IDENTITY.certificate serdes(obj, factory, factory2) } @Test - fun `test party and certificate serialize`() { + fun `test cert path serialize`() { val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) - factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory)) + factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.CertPathSerializer(factory)) val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) - factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory2)) + factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.CertPathSerializer(factory2)) - val obj = BOB_IDENTITY + val obj = BOB_IDENTITY.certPath serdes(obj, factory, factory2) }