From 43a8d21977f58b50eeebc93432cd81100a42cea9 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Wed, 22 Nov 2017 12:07:53 +0000 Subject: [PATCH 01/13] Removes outdated devmode cert info, links to existing docs to avoid duplication. --- docs/source/setting-up-a-corda-network.rst | 30 ++++++++++------------ 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/docs/source/setting-up-a-corda-network.rst b/docs/source/setting-up-a-corda-network.rst index 41a44d300f..c5e43e01fd 100644 --- a/docs/source/setting-up-a-corda-network.rst +++ b/docs/source/setting-up-a-corda-network.rst @@ -3,15 +3,20 @@ Creating a Corda network ======================== -A Corda network consists of a number of machines running nodes. These nodes communicate using persistent protocols in order to create and validate transactions. +A Corda network consists of a number of machines running nodes. These nodes communicate using persistent protocols in +order to create and validate transactions. -There are four broader categories of functionality one such node may have. These pieces of functionality are provided as -services, and one node may run several of them. +There are four broader categories of functionality one such node may have. These pieces of functionality are provided +as services, and one node may run several of them. -* Network map: The node running the network map provides a way to resolve identities to physical node addresses and associated public keys. -* Notary: Nodes running a notary service witness state spends and have the final say in whether a transaction is a double-spend or not. -* Oracle: Network services that link the ledger to the outside world by providing facts that affect the validity of transactions. -* Regular node: All nodes have a vault and may start protocols communicating with other nodes, notaries and oracles and evolve their private ledger. +* Network map: The node running the network map provides a way to resolve identities to physical node addresses and + associated public keys +* Notary: Nodes running a notary service witness state spends and have the final say in whether a transaction is a + double-spend or not +* Oracle: Network services that link the ledger to the outside world by providing facts that affect the validity of + transactions +* Regular node: All nodes have a vault and may start protocols communicating with other nodes, notaries and oracles and + evolve their private ledger Setting up your own network --------------------------- @@ -19,15 +24,8 @@ Setting up your own network Certificates ~~~~~~~~~~~~ -All nodes belonging to the same Corda network must have the same root CA. For testing purposes you can -use ``certSigningRequestUtility.jar`` to generate a node certificate with a fixed test root: - -.. sourcecode:: bash - - # Build the jars - ./gradlew buildCordaJAR - # Generate certificate - java -jar build/libs/certSigningRequestUtility.jar --base-dir NODE_DIRECTORY/ +Every node in a given Corda network must have an identity certificate signed by the network's root CA. See +:doc:`permissioning` for more information. Configuration ~~~~~~~~~~~~~ From f314bf48498c3c170fa0cee643ca4dea078bb27a Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Wed, 22 Nov 2017 15:39:48 +0000 Subject: [PATCH 02/13] Re-introduce demo docs (#1981) Document the included samples in the docs again so there's some continuity from v1 docs, while making it clear they're not best practice examples but instead illustrate solutions to various use-cases. --- docs/source/building-a-cordapp-index.rst | 3 ++- docs/source/building-a-cordapp-samples.rst | 21 +++++++++++++++++++++ docs/source/quickstart-index.rst | 1 + docs/source/running-the-demos.rst | 0 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 docs/source/building-a-cordapp-samples.rst delete mode 100644 docs/source/running-the-demos.rst diff --git a/docs/source/building-a-cordapp-index.rst b/docs/source/building-a-cordapp-index.rst index b3b4bd8eb6..a1373f9849 100644 --- a/docs/source/building-a-cordapp-index.rst +++ b/docs/source/building-a-cordapp-index.rst @@ -10,4 +10,5 @@ CorDapps building-against-master corda-api flow-cookbook - cheat-sheet \ No newline at end of file + cheat-sheet + building-a-cordapp-samples diff --git a/docs/source/building-a-cordapp-samples.rst b/docs/source/building-a-cordapp-samples.rst new file mode 100644 index 0000000000..40affe1a26 --- /dev/null +++ b/docs/source/building-a-cordapp-samples.rst @@ -0,0 +1,21 @@ +CorDapp samples +=============== + +There are two distinct sets of samples provided with Corda, one introducing new developers to how to write CorDapps, and +more complex worked examples of how solutions to a number of common designs could be implemented in a CorDapp. +The former can be found on `the Corda website `_. In particular, new developers +should start with the :doc:`example CorDapp `. + +The advanced samples are contained within the `samples/` folder of the Corda repository. The most generally useful of +these samples are: + +1. The `trader-demo`, which shows a delivery-vs-payment atomic swap of commercial paper for cash +2. The `attachment-demo`, which demonstrates uploading attachments to nodes +3. The `bank-of-corda-demo`, which shows a node acting as an issuer of assets (the Bank of Corda) while remote client + applications request issuance of some cash on behalf of a node called Big Corporation + +Documentation on running the samples can be found inside the sample directories themselves, in the `README.md` file. + +.. note:: If you would like to see flow activity on the nodes type in the node terminal ``flow watch``. + +Please report any bugs with the samples on `GitHub `_. diff --git a/docs/source/quickstart-index.rst b/docs/source/quickstart-index.rst index a42b469e95..02e9045ac9 100644 --- a/docs/source/quickstart-index.rst +++ b/docs/source/quickstart-index.rst @@ -7,4 +7,5 @@ Quickstart getting-set-up tutorial-cordapp Sample CorDapps + building-against-master CLI-vs-IDE \ No newline at end of file diff --git a/docs/source/running-the-demos.rst b/docs/source/running-the-demos.rst deleted file mode 100644 index e69de29bb2..0000000000 From 7bde9ecefd998efc551b05e3f139f341366730ec Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Wed, 22 Nov 2017 17:33:40 +0000 Subject: [PATCH 03/13] Adds upgrade notes for v1 to v2. Minor tweaks (e.g. ToC). --- docs/source/upgrade-notes.rst | 45 ++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/docs/source/upgrade-notes.rst b/docs/source/upgrade-notes.rst index 1dcb0d72b4..7ddbd362f1 100644 --- a/docs/source/upgrade-notes.rst +++ b/docs/source/upgrade-notes.rst @@ -1,11 +1,14 @@ Upgrade notes ============= -These notes provide helpful instructions to upgrade your Corda Applications (CorDapps) from previous versions, starting -from our first public Beta (:ref:`Milestone 12 `), to :ref:`V1.0 ` +These notes provide instructions for upgrading your CorDapps from previous versions, starting with the upgrade from our +first public Beta (:ref:`Milestone 12 `), to :ref:`V1.0 `. -General -------- +.. contents:: + :depth: 3 + +General rules +------------- Always remember to update the version identifiers in your project gradle file: .. sourcecode:: shell @@ -29,7 +32,7 @@ UNRELEASED ---------- Testing -^^^^^^^ +~~~~~~~ * The registration mechanism for CorDapps in ``MockNetwork`` unit tests has changed. @@ -38,10 +41,24 @@ Testing package names of the CorDapps containing the contract verification code you wish to load. The ``unsetCordappPackages`` method is now redundant and has been removed. -:ref:`Milestone 14 ` +V1.0 to V2.0 ------------ -Build +You only need to update the ``corda_release_version`` identifier in your project gradle file. The +corda_gradle_plugins_version should remain at 1.0.0: + +.. sourcecode:: shell + + ext.corda_release_version = '2.0.0' + ext.corda_gradle_plugins_version = '1.0.0' + +Public Beta (M12) to V1.0 +------------------------- + +:ref:`From Milestone 14 ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Build ^^^^^ * MockNetwork has moved. @@ -151,7 +168,7 @@ Flow framework Note that ``SwapIdentitiesFlow`` must be imported from the *confidential-identities** package ''net.corda.confidential'' Node services (ServiceHub) -^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^ * VaultQueryService: unresolved reference to `vaultQueryService`. @@ -243,8 +260,8 @@ Gotchas The 3rd parameter to ``CashIssueFlow`` should be the ** notary ** (not the ** node identity **) -:ref:`Milestone 13 ` ------------- +:ref:`From Milestone 13 ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Core data structures ^^^^^^^^^^^^^^^^^^^^ @@ -266,7 +283,7 @@ Core data structures * No longer need to override Contract ``contract()`` function. Node services (ServiceHub) -^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^ * ServiceHub API method changes. @@ -311,8 +328,8 @@ Testing ``CordaX500Name``, instead of using ``getX509Name`` -:ref:`Milestone 12 ` (First Public Beta) ------------------------------------ +:ref:`From Milestone 12 (First Public Beta) ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Core data structures ^^^^^^^^^^^^^^^^^^^^ @@ -338,7 +355,7 @@ Build compile "net.corda:rpc:$corda_release_version" -> compile "net.corda:corda-rpc:$corda_release_version" Node services (ServiceHub) -^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^ * ServiceHub API changes. From 22d29db54b5b7e77bda83be2a46a87a456742e7e Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Wed, 22 Nov 2017 18:00:43 +0000 Subject: [PATCH 04/13] Add X500 name constraints for non-organisation attributes (#2108) Enforce X500 name constraints consistently across all attributes --- .../net/corda/core/identity/CordaX500Name.kt | 4 +- .../corda/core/internal/LegalNameValidator.kt | 40 ++++++++++++---- .../core/internal/LegalNameValidatorTest.kt | 46 +++++++++---------- docs/source/key-concepts-identity.rst | 13 +++++- .../net/corda/demobench/views/NodeTabView.kt | 4 +- 5 files changed, 70 insertions(+), 37 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt b/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt index 37e872d562..ee8baa8733 100644 --- a/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt +++ b/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt @@ -9,7 +9,7 @@ import org.bouncycastle.asn1.ASN1ObjectIdentifier import org.bouncycastle.asn1.x500.AttributeTypeAndValue import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.style.BCStyle -import java.util.Locale +import java.util.* import javax.security.auth.x500.X500Principal /** @@ -45,7 +45,7 @@ data class CordaX500Name(val commonName: String?, init { // Legal name checks. - LegalNameValidator.validateLegalName(organisation) + LegalNameValidator.validateOrganization(organisation) // Attribute data width checks. require(country.length == LENGTH_COUNTRY) { "Invalid country '$country' Country code must be $LENGTH_COUNTRY letters ISO code " } diff --git a/core/src/main/kotlin/net/corda/core/internal/LegalNameValidator.kt b/core/src/main/kotlin/net/corda/core/internal/LegalNameValidator.kt index 967afc5e7a..892f83b224 100644 --- a/core/src/main/kotlin/net/corda/core/internal/LegalNameValidator.kt +++ b/core/src/main/kotlin/net/corda/core/internal/LegalNameValidator.kt @@ -6,8 +6,27 @@ import java.util.regex.Pattern import javax.security.auth.x500.X500Principal object LegalNameValidator { + @Deprecated("Use validateOrganization instead", replaceWith = ReplaceWith("validateOrganization(normalizedLegalName)")) + fun validateLegalName(normalizedLegalName: String) = validateOrganization(normalizedLegalName) + /** - * The validation function will validate the input string using the following rules: + * The validation function validates a string for use as part of a legal name. It applies the following rules: + * + * - No blacklisted words like "node", "server". + * - Restrict names to Latin scripts for now to avoid right-to-left issues, debugging issues when we can't pronounce + * names over the phone, and character confusability attacks. + * - No commas or equals signs. + * - No dollars or quote marks, we might need to relax the quote mark constraint in future to handle Irish company names. + * + * @throws IllegalArgumentException if the name does not meet the required rules. The message indicates why not. + */ + fun validateNameAttribute(normalizedNameAttribute: String) { + Rule.baseNameRules.forEach { it.validate(normalizedNameAttribute) } + } + + /** + * The validation function validates a string for use as the organization attribute of a name, which includes additional + * constraints over basic name attribute checks. It applies the following rules: * * - No blacklisted words like "node", "server". * - Restrict names to Latin scripts for now to avoid right-to-left issues, debugging issues when we can't pronounce @@ -18,16 +37,19 @@ object LegalNameValidator { * * @throws IllegalArgumentException if the name does not meet the required rules. The message indicates why not. */ - fun validateLegalName(normalizedLegalName: String) { - Rule.legalNameRules.forEach { it.validate(normalizedLegalName) } + fun validateOrganization(normalizedOrganization: String) { + Rule.legalNameRules.forEach { it.validate(normalizedOrganization) } } + @Deprecated("Use normalize instead", replaceWith = ReplaceWith("normalize(legalName)")) + fun normalizeLegalName(legalName: String): String = normalize(legalName) + /** * The normalize function will trim the input string, replace any multiple spaces with a single space, * and normalize the string according to NFKC normalization form. */ - fun normaliseLegalName(legalName: String): String { - val trimmedLegalName = legalName.trim().replace(WHITESPACE, " ") + fun normalize(nameAttribute: String): String { + val trimmedLegalName = nameAttribute.trim().replace(WHITESPACE, " ") return Normalizer.normalize(trimmedLegalName, Normalizer.Form.NFKC) } @@ -35,15 +57,17 @@ object LegalNameValidator { sealed class Rule { companion object { - val legalNameRules: List> = listOf( + val baseNameRules: List> = listOf( UnicodeNormalizationRule(), CharacterRule(',', '=', '$', '"', '\'', '\\'), WordRule("node", "server"), LengthRule(maxLength = 255), // TODO: Implement confusable character detection if we add more scripts. UnicodeRangeRule(LATIN, COMMON, INHERITED), + X500NameRule() + ) + val legalNameRules: List> = baseNameRules + listOf( CapitalLetterRule(), - X500NameRule(), MustHaveAtLeastTwoLettersRule() ) } @@ -52,7 +76,7 @@ object LegalNameValidator { private class UnicodeNormalizationRule : Rule() { override fun validate(legalName: String) { - require(legalName == normaliseLegalName(legalName)) { "Legal name must be normalized. Please use 'normaliseLegalName' to normalize the legal name before validation." } + require(legalName == normalize(legalName)) { "Legal name must be normalized. Please use 'normalize' to normalize the legal name before validation." } } } diff --git a/core/src/test/kotlin/net/corda/core/internal/LegalNameValidatorTest.kt b/core/src/test/kotlin/net/corda/core/internal/LegalNameValidatorTest.kt index 0f96141922..fa854b42e4 100644 --- a/core/src/test/kotlin/net/corda/core/internal/LegalNameValidatorTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/LegalNameValidatorTest.kt @@ -8,55 +8,55 @@ class LegalNameValidatorTest { @Test fun `no double spaces`() { assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("Test Legal Name") + LegalNameValidator.validateOrganization("Test Legal Name") } - LegalNameValidator.validateLegalName(LegalNameValidator.normaliseLegalName("Test Legal Name")) + LegalNameValidator.validateOrganization(LegalNameValidator.normalize("Test Legal Name")) } @Test fun `no trailing white space`() { assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("Test ") + LegalNameValidator.validateOrganization("Test ") } } @Test fun `no prefixed white space`() { assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName(" Test") + LegalNameValidator.validateOrganization(" Test") } } @Test fun `blacklisted words`() { assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("Test Server") + LegalNameValidator.validateOrganization("Test Server") } } @Test fun `blacklisted characters`() { - LegalNameValidator.validateLegalName("Test") + LegalNameValidator.validateOrganization("Test") assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("\$Test") + LegalNameValidator.validateOrganization("\$Test") } assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("\"Test") + LegalNameValidator.validateOrganization("\"Test") } assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("\'Test") + LegalNameValidator.validateOrganization("\'Test") } assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("=Test") + LegalNameValidator.validateOrganization("=Test") } } @Test fun `unicode range`() { - LegalNameValidator.validateLegalName("Test A") + LegalNameValidator.validateOrganization("Test A") assertFailsWith(IllegalArgumentException::class) { // Greek letter A. - LegalNameValidator.validateLegalName("Test Α") + LegalNameValidator.validateOrganization("Test Α") } } @@ -66,37 +66,37 @@ class LegalNameValidatorTest { while (longLegalName.length < 255) { longLegalName.append("A") } - LegalNameValidator.validateLegalName(longLegalName.toString()) + LegalNameValidator.validateOrganization(longLegalName.toString()) assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName(longLegalName.append("A").toString()) + LegalNameValidator.validateOrganization(longLegalName.append("A").toString()) } } @Test fun `legal name should be capitalized`() { - LegalNameValidator.validateLegalName("Good legal name") + LegalNameValidator.validateOrganization("Good legal name") assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("bad name") + LegalNameValidator.validateOrganization("bad name") } assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("bad Name") + LegalNameValidator.validateOrganization("bad Name") } } @Test fun `correctly handle whitespaces`() { - assertEquals("Legal Name With Tab", LegalNameValidator.normaliseLegalName("Legal Name With\tTab")) - assertEquals("Legal Name With Unicode Whitespaces", LegalNameValidator.normaliseLegalName("Legal Name\u2004With\u0009Unicode\u0020Whitespaces")) - assertEquals("Legal Name With Line Breaks", LegalNameValidator.normaliseLegalName("Legal Name With\n\rLine\nBreaks")) + assertEquals("Legal Name With Tab", LegalNameValidator.normalize("Legal Name With\tTab")) + assertEquals("Legal Name With Unicode Whitespaces", LegalNameValidator.normalize("Legal Name\u2004With\u0009Unicode\u0020Whitespaces")) + assertEquals("Legal Name With Line Breaks", LegalNameValidator.normalize("Legal Name With\n\rLine\nBreaks")) assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("Legal Name With\tTab") + LegalNameValidator.validateOrganization("Legal Name With\tTab") } assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("Legal Name\u2004With\u0009Unicode\u0020Whitespaces") + LegalNameValidator.validateOrganization("Legal Name\u2004With\u0009Unicode\u0020Whitespaces") } assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("Legal Name With\n\rLine\nBreaks") + LegalNameValidator.validateOrganization("Legal Name With\n\rLine\nBreaks") } } } \ No newline at end of file diff --git a/docs/source/key-concepts-identity.rst b/docs/source/key-concepts-identity.rst index 7f1f9bbcc2..de01ee8295 100644 --- a/docs/source/key-concepts-identity.rst +++ b/docs/source/key-concepts-identity.rst @@ -49,8 +49,17 @@ the minimum supported set for X.509 certificates (specified in RFC 3280), plus t * common name (CN) - used only for service identities The organisation, locality and country attributes are required, while state, organisational-unit and common name are -optional. Attributes cannot be be present more than once in the name. The "country" code is strictly restricted to valid -ISO 3166-1 two letter codes. +optional. Attributes cannot be be present more than once in the name. + +All of these attributes have the following set of constraints applied for security reasons: + + - No blacklisted words (currently "node" and "server"). + - Restrict names to Latin scripts for now to avoid right-to-left issues, debugging issues when we can't pronounce names over the phone, and character confusability attacks. + - No commas or equals signs. + - No dollars or quote marks. + +Additionally the "organisation" attribute must consist of at least three letters and starting with a capital letter, +and "country code" is strictly restricted to valid ISO 3166-1 two letter codes. Certificates ------------ diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt index cc728b4708..cede37faac 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt @@ -207,11 +207,11 @@ class NodeTabView : Fragment() { validator { if (it == null) { error("Node name is required") - } else if (nodeController.nameExists(LegalNameValidator.normaliseLegalName(it))) { + } else if (nodeController.nameExists(LegalNameValidator.normalize(it))) { error("Node with this name already exists") } else { try { - LegalNameValidator.validateLegalName(LegalNameValidator.normaliseLegalName(it)) + LegalNameValidator.validateOrganization(LegalNameValidator.normalize(it)) null } catch (e: IllegalArgumentException) { error(e.message) From 5c18c574179ce917760deb8989c24dae4631b837 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Thu, 23 Nov 2017 11:27:34 +0000 Subject: [PATCH 05/13] Fixes an error in the contract testing tutorial. --- docs/source/tutorial-test-dsl.rst | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/source/tutorial-test-dsl.rst b/docs/source/tutorial-test-dsl.rst index ce66251a5c..6771f077cb 100644 --- a/docs/source/tutorial-test-dsl.rst +++ b/docs/source/tutorial-test-dsl.rst @@ -35,14 +35,17 @@ We start with the empty ledger: .. sourcecode:: java - import static net.corda.core.testing.JavaTestHelpers.*; - import static net.corda.core.contracts.JavaTestHelpers.*; + import org.junit.Test; - @Test - public void emptyLedger() { - ledger(l -> { - return Unit.INSTANCE; // We need to return this explicitly - }); + import static net.corda.testing.NodeTestUtils.ledger; + + public class CommercialPaperTest { + @Test + public void emptyLedger() { + ledger(l -> { + return null; + }); + } } The DSL keyword ``ledger`` takes a closure that can build up several transactions and may verify their overall From 502d0df630f742319a29ec4acbd76b730e2d07e4 Mon Sep 17 00:00:00 2001 From: Konstantinos Chalkias Date: Thu, 23 Nov 2017 16:07:08 +0000 Subject: [PATCH 06/13] Mutual TLS auth - mixed RSA and ECDSA keys (#2095) --- .../kotlin/net/corda/core/crypto/Crypto.kt | 12 +- .../node/utilities/TLSAuthenticationTests.kt | 394 ++++++++++++++++++ 2 files changed, 400 insertions(+), 6 deletions(-) create mode 100644 node/src/test/kotlin/net/corda/node/utilities/TLSAuthenticationTests.kt diff --git a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt index f2c0a45918..ccae80d47e 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt @@ -63,21 +63,21 @@ import javax.crypto.spec.SecretKeySpec */ object Crypto { /** - * RSA_SHA256 signature scheme using SHA256 as hash algorithm and MGF1 (with SHA256) as mask generation function. + * RSA_SHA256 signature scheme using SHA256 as hash algorithm. * Note: Recommended key size >= 3072 bits. */ @JvmField val RSA_SHA256 = SignatureScheme( 1, "RSA_SHA256", - AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, null), - emptyList(), + AlgorithmIdentifier(PKCSObjectIdentifiers.sha256WithRSAEncryption, null), + listOf(AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, null)), BouncyCastleProvider.PROVIDER_NAME, "RSA", - "SHA256WITHRSAANDMGF1", + "SHA256WITHRSAEncryption", null, 3072, - "RSA_SHA256 signature scheme using SHA256 as hash algorithm and MGF1 (with SHA256) as mask generation function." + "RSA_SHA256 signature scheme using SHA256 as hash algorithm." ) /** ECDSA signature scheme using the secp256k1 Koblitz curve. */ @@ -117,7 +117,7 @@ object Crypto { "EDDSA_ED25519_SHA512", // OID taken from https://tools.ietf.org/html/draft-ietf-curdle-pkix-00 AlgorithmIdentifier(ASN1ObjectIdentifier("1.3.101.112"), null), - emptyList(), + emptyList(), // Both keys and the signature scheme use the same OID in i2p library. // We added EdDSA to bouncy castle for certificate signing. BouncyCastleProvider.PROVIDER_NAME, "1.3.101.112", diff --git a/node/src/test/kotlin/net/corda/node/utilities/TLSAuthenticationTests.kt b/node/src/test/kotlin/net/corda/node/utilities/TLSAuthenticationTests.kt new file mode 100644 index 0000000000..0d6eb5e8fc --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/utilities/TLSAuthenticationTests.kt @@ -0,0 +1,394 @@ +package net.corda.node.utilities + +import net.corda.core.crypto.Crypto +import net.corda.core.crypto.SignatureScheme +import net.corda.core.crypto.newSecureRandom +import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.* +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import java.io.DataInputStream +import java.io.DataOutputStream +import java.io.IOException +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.ServerSocket +import java.nio.file.Path +import java.security.KeyStore +import javax.net.ssl.* +import kotlin.concurrent.thread +import kotlin.test.* + +/** + * Various tests for mixed-scheme mutual TLS authentication, such as: + * Both TLS keys and CAs are using EC NIST P-256. + * Both TLS keys and CAs are using RSA. + * Server EC NIST P-256 - Client RSA. + * Server RSA - Client EC NIST P-256. + * Mixed CA and TLS keys. + * + * TLS/SSL protocols support a large number of cipher suites. + * A cipher suite is a collection of symmetric and asymmetric encryption algorithms used by hosts to establish + * a secure communication. Supported cipher suites can be classified based on encryption algorithm strength, + * key length, key exchange and authentication mechanisms. Some cipher suites offer better level of security than others. + * + * Each TLS cipher suite has a unique name that is used to identify it and to describe the algorithmic contents of it. + * Each segment in a cipher suite name stands for a different algorithm or protocol. + * An example of a cipher suite name: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + * The meaning of this name is: + * TLS defines the protocol that this cipher suite is for; it will usually be TLS. + * ECDHE indicates the key exchange algorithm being used. + * ECDSA indicates the authentication algorithm (signing the DH keys). + * AES_128_GCM indicates the block cipher being used to encrypt the message stream. + * SHA256 indicates the message authentication algorithm which is used to authenticate a message. + */ +class TLSAuthenticationTests { + + @Rule + @JvmField + val tempFolder: TemporaryFolder = TemporaryFolder() + + // Root CA. + private val ROOT_X500 = CordaX500Name(commonName = "Root_CA_1", organisation = "R3CEV", locality = "London", country = "GB") + // Intermediate CA. + private val INTERMEDIATE_X500 = CordaX500Name(commonName = "Intermediate_CA_1", organisation = "R3CEV", locality = "London", country = "GB") + // TLS server (client1). + private val CLIENT_1_X500 = CordaX500Name(commonName = "Client_1", organisation = "R3CEV", locality = "London", country = "GB") + // TLS client (client2). + private val CLIENT_2_X500 = CordaX500Name(commonName = "Client_2", organisation = "R3CEV", locality = "London", country = "GB") + // Password for keys and keystores. + private val PASSWORD = "dummypassword" + // Default supported TLS schemes for Corda nodes. + private val CORDA_TLS_CIPHER_SUITES = arrayOf( + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256" + ) + + @Test + fun `All EC R1`() { + val (serverSocketFactory, clientSocketFactory) = buildTLSFactories( + rootCAScheme = Crypto.ECDSA_SECP256R1_SHA256, + intermediateCAScheme = Crypto.ECDSA_SECP256R1_SHA256, + client1CAScheme = Crypto.ECDSA_SECP256R1_SHA256, + client1TLSScheme = Crypto.ECDSA_SECP256R1_SHA256, + client2CAScheme = Crypto.ECDSA_SECP256R1_SHA256, + client2TLSScheme = Crypto.ECDSA_SECP256R1_SHA256 + ) + + val (serverSocket, clientSocket) = + buildTLSSockets(serverSocketFactory, clientSocketFactory, 0, 0) + + testConnect(serverSocket, clientSocket, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256") + } + + @Test + fun `All RSA`() { + val (serverSocketFactory, clientSocketFactory) = buildTLSFactories( + rootCAScheme = Crypto.RSA_SHA256, + intermediateCAScheme = Crypto.RSA_SHA256, + client1CAScheme = Crypto.RSA_SHA256, + client1TLSScheme = Crypto.RSA_SHA256, + client2CAScheme = Crypto.RSA_SHA256, + client2TLSScheme = Crypto.RSA_SHA256 + ) + + val (serverSocket, clientSocket) = + buildTLSSockets(serverSocketFactory, clientSocketFactory, 0, 0) + + testConnect(serverSocket, clientSocket, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") + } + + // Server's public key type is the one selected if users use different key types (e.g RSA and EC R1). + @Test + fun `Server RSA - Client EC R1 - CAs all EC R1`() { + val (serverSocketFactory, clientSocketFactory) = buildTLSFactories( + rootCAScheme = Crypto.ECDSA_SECP256R1_SHA256, + intermediateCAScheme = Crypto.ECDSA_SECP256R1_SHA256, + client1CAScheme = Crypto.ECDSA_SECP256R1_SHA256, + client1TLSScheme = Crypto.RSA_SHA256, + client2CAScheme = Crypto.ECDSA_SECP256R1_SHA256, + client2TLSScheme = Crypto.ECDSA_SECP256R1_SHA256 + ) + + val (serverSocket, clientSocket) = + buildTLSSockets(serverSocketFactory, clientSocketFactory, 0, 0) + testConnect(serverSocket, clientSocket, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") // Server's key type is selected. + } + + @Test + fun `Server EC R1 - Client RSA - CAs all EC R1`() { + val (serverSocketFactory, clientSocketFactory) = buildTLSFactories( + rootCAScheme = Crypto.ECDSA_SECP256R1_SHA256, + intermediateCAScheme = Crypto.ECDSA_SECP256R1_SHA256, + client1CAScheme = Crypto.ECDSA_SECP256R1_SHA256, + client1TLSScheme = Crypto.ECDSA_SECP256R1_SHA256, + client2CAScheme = Crypto.ECDSA_SECP256R1_SHA256, + client2TLSScheme = Crypto.RSA_SHA256 + ) + + val (serverSocket, clientSocket) = buildTLSSockets(serverSocketFactory, clientSocketFactory, 0, 0) + testConnect(serverSocket, clientSocket, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256") // Server's key type is selected. + } + + @Test + fun `Server EC R1 - Client EC R1 - CAs all RSA`() { + val (serverSocketFactory, clientSocketFactory) = buildTLSFactories( + rootCAScheme = Crypto.RSA_SHA256, + intermediateCAScheme = Crypto.RSA_SHA256, + client1CAScheme = Crypto.RSA_SHA256, + client1TLSScheme = Crypto.ECDSA_SECP256R1_SHA256, + client2CAScheme = Crypto.RSA_SHA256, + client2TLSScheme = Crypto.ECDSA_SECP256R1_SHA256 + ) + + val (serverSocket, clientSocket) = buildTLSSockets(serverSocketFactory, clientSocketFactory, 0, 0) + testConnect(serverSocket, clientSocket, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256") + } + + @Test + fun `Server EC R1 - Client RSA - Mixed CAs`() { + val (serverSocketFactory, clientSocketFactory) = buildTLSFactories( + rootCAScheme = Crypto.ECDSA_SECP256R1_SHA256, + intermediateCAScheme = Crypto.RSA_SHA256, + client1CAScheme = Crypto.RSA_SHA256, + client1TLSScheme = Crypto.ECDSA_SECP256R1_SHA256, + client2CAScheme = Crypto.ECDSA_SECP256R1_SHA256, + client2TLSScheme = Crypto.RSA_SHA256 + ) + + val (serverSocket, clientSocket) = buildTLSSockets(serverSocketFactory, clientSocketFactory, 0, 0) + testConnect(serverSocket, clientSocket, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256") + } + + @Test + fun `All RSA - avoid ECC for DH`() { + val (serverSocketFactory, clientSocketFactory) = buildTLSFactories( + rootCAScheme = Crypto.RSA_SHA256, + intermediateCAScheme = Crypto.RSA_SHA256, + client1CAScheme = Crypto.RSA_SHA256, + client1TLSScheme = Crypto.RSA_SHA256, + client2CAScheme = Crypto.RSA_SHA256, + client2TLSScheme = Crypto.RSA_SHA256 + ) + + val (serverSocket, clientSocket) = buildTLSSockets( + serverSocketFactory, + clientSocketFactory, + 0, + 0, + CORDA_TLS_CIPHER_SUITES, + arrayOf("TLS_DHE_RSA_WITH_AES_128_GCM_SHA256")) // Second client accepts DHE only. + testConnect(serverSocket, clientSocket, "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256") + } + + // According to RFC 5246 (TLS 1.2), section 7.4.1.2 ClientHello cipher_suites: + // This is a list of the cryptographic options supported by the client, with the client's first preference first. + // + // However, the server is still free to ignore this order and pick what it thinks is best, + // see https://security.stackexchange.com/questions/121608 for more information. + @Test + fun `TLS cipher suite order matters - client wins`() { + val (serverSocketFactory, clientSocketFactory) = buildTLSFactories( + rootCAScheme = Crypto.ECDSA_SECP256R1_SHA256, + intermediateCAScheme = Crypto.ECDSA_SECP256R1_SHA256, + client1CAScheme = Crypto.ECDSA_SECP256R1_SHA256, + client1TLSScheme = Crypto.ECDSA_SECP256R1_SHA256, + client2CAScheme = Crypto.ECDSA_SECP256R1_SHA256, + client2TLSScheme = Crypto.ECDSA_SECP256R1_SHA256 + ) + + val (serverSocket, clientSocket) = buildTLSSockets( + serverSocketFactory, + clientSocketFactory, + 0, + 0, + arrayOf("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"), // GCM then CBC. + arrayOf("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")) // CBC then GCM. + testConnect(serverSocket, clientSocket, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256") // Client order wins. + } + + private fun tempFile(name: String): Path = tempFolder.root.toPath() / name + + private fun buildTLSFactories( + rootCAScheme: SignatureScheme, + intermediateCAScheme: SignatureScheme, + client1CAScheme: SignatureScheme, + client1TLSScheme: SignatureScheme, + client2CAScheme: SignatureScheme, + client2TLSScheme: SignatureScheme + ): Pair { + + val trustStorePath = tempFile("cordaTrustStore.jks") + val client1TLSKeyStorePath = tempFile("client1sslkeystore.jks") + val client2TLSKeyStorePath = tempFile("client2sslkeystore.jks") + + // ROOT CA key and cert. + val rootCAKeyPair = Crypto.generateKeyPair(rootCAScheme) + val rootCACert = X509Utilities.createSelfSignedCACertificate(ROOT_X500, rootCAKeyPair) + + // Intermediate CA key and cert. + val intermediateCAKeyPair = Crypto.generateKeyPair(intermediateCAScheme) + val intermediateCACert = X509Utilities.createCertificate( + CertificateType.INTERMEDIATE_CA, + rootCACert, + rootCAKeyPair, + INTERMEDIATE_X500, + intermediateCAKeyPair.public + ) + + // Client 1 keys, certs and SSLKeyStore. + val client1CAKeyPair = Crypto.generateKeyPair(client1CAScheme) + val client1CACert = X509Utilities.createCertificate( + CertificateType.CLIENT_CA, + intermediateCACert, + intermediateCAKeyPair, + CLIENT_1_X500, + client1CAKeyPair.public + ) + + val client1TLSKeyPair = Crypto.generateKeyPair(client1TLSScheme) + val client1TLSCert = X509Utilities.createCertificate( + CertificateType.TLS, + client1CACert, + client1CAKeyPair, + CLIENT_1_X500, + client1TLSKeyPair.public + ) + + val client1TLSKeyStore = loadOrCreateKeyStore(client1TLSKeyStorePath, PASSWORD) + client1TLSKeyStore.addOrReplaceKey( + X509Utilities.CORDA_CLIENT_TLS, + client1TLSKeyPair.private, + PASSWORD.toCharArray(), + arrayOf(client1TLSCert, client1CACert, intermediateCACert, rootCACert)) + // client1TLSKeyStore.save(client1TLSKeyStorePath, PASSWORD) + + // Client 2 keys, certs and SSLKeyStore. + val client2CAKeyPair = Crypto.generateKeyPair(client2CAScheme) + val client2CACert = X509Utilities.createCertificate( + CertificateType.CLIENT_CA, + intermediateCACert, + intermediateCAKeyPair, + CLIENT_2_X500, + client2CAKeyPair.public + ) + + val client2TLSKeyPair = Crypto.generateKeyPair(client2TLSScheme) + val client2TLSCert = X509Utilities.createCertificate( + CertificateType.TLS, + client2CACert, + client2CAKeyPair, + CLIENT_2_X500, + client2TLSKeyPair.public + ) + + val client2TLSKeyStore = loadOrCreateKeyStore(client2TLSKeyStorePath, PASSWORD) + client2TLSKeyStore.addOrReplaceKey( + X509Utilities.CORDA_CLIENT_TLS, + client2TLSKeyPair.private, + PASSWORD.toCharArray(), + arrayOf(client2TLSCert, client2CACert, intermediateCACert, rootCACert)) + // client2TLSKeyStore.save(client2TLSKeyStorePath, PASSWORD) + + val trustStore = loadOrCreateKeyStore(trustStorePath, PASSWORD) + trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert.cert) + trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCACert.cert) + // trustStore.save(trustStorePath, PASSWORD) + + val client1SSLContext = sslContext(client1TLSKeyStore, PASSWORD, trustStore) + val client2SSLContext = sslContext(client2TLSKeyStore, PASSWORD, trustStore) + + val serverSocketFactory = client1SSLContext.serverSocketFactory + val clientSocketFactory = client2SSLContext.socketFactory + + return Pair(serverSocketFactory, clientSocketFactory) + } + + private fun buildTLSSockets( + serverSocketFactory: SSLServerSocketFactory, + clientSocketFactory: SSLSocketFactory, + serverPort: Int = 0, // Use 0 to get first free socket. + clientPort: Int = 0, // Use 0 to get first free socket. + cipherSuitesServer: Array = CORDA_TLS_CIPHER_SUITES, + cipherSuitesClient: Array = CORDA_TLS_CIPHER_SUITES + ): Pair { + val serverSocket = serverSocketFactory.createServerSocket(serverPort) as SSLServerSocket // use 0 to get first free socket. + val serverParams = SSLParameters(cipherSuitesServer, arrayOf("TLSv1.2")) + serverParams.needClientAuth = true // Note that needClientAuth is requiring client authentication Vs wantClientAuth, in which client authentication is optional). + serverParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator. + serverSocket.sslParameters = serverParams + serverSocket.useClientMode = false + + val clientSocket = clientSocketFactory.createSocket() as SSLSocket + val clientParams = SSLParameters(cipherSuitesClient, arrayOf("TLSv1.2")) + clientParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator. + clientSocket.sslParameters = clientParams + clientSocket.useClientMode = true + // We need to specify this explicitly because by default the client binds to 'localhost' and we want it to bind + // to whatever resolves to(as that's what the server binds to). In particular on Debian + // resolves to 127.0.1.1 instead of the external address of the interface, so the TLS handshake fails. + clientSocket.bind(InetSocketAddress(InetAddress.getLocalHost(), clientPort)) + return Pair(serverSocket, clientSocket) + } + + private fun testConnect(serverSocket: ServerSocket, clientSocket: SSLSocket, expectedCipherSuite: String) { + val lock = Object() + var done = false + var serverError = false + + val serverThread = thread { + try { + val sslServerSocket = serverSocket.accept() + assertTrue(sslServerSocket.isConnected) + val serverInput = DataInputStream(sslServerSocket.inputStream) + val receivedString = serverInput.readUTF() + assertEquals("Hello World", receivedString) + synchronized(lock) { + done = true + lock.notifyAll() + } + sslServerSocket.close() + } catch (ex: Throwable) { + serverError = true + } + } + + clientSocket.connect(InetSocketAddress(InetAddress.getLocalHost(), serverSocket.localPort)) + assertTrue(clientSocket.isConnected) + assertEquals(expectedCipherSuite, clientSocket.session.cipherSuite) + + // Timeout after 30 secs. + val output = DataOutputStream(clientSocket.outputStream) + output.writeUTF("Hello World") + var timeout = 0 + synchronized(lock) { + while (!done) { + timeout++ + if (timeout > 30) throw IOException("Timed out waiting for server to complete") + lock.wait(1000) + } + } + + clientSocket.close() + serverThread.join(1000) + assertFalse { serverError } + serverSocket.close() + assertTrue(done) + } + + // Generate an SSLContext from a KeyStore and a TrustStore. + private fun sslContext(sslKeyStore: KeyStore, sslKeyStorePassword: String, sslTrustStore: KeyStore) : SSLContext { + val context = SSLContext.getInstance("TLS") + val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) + // Requires the KeyStore password as well. + keyManagerFactory.init(sslKeyStore, sslKeyStorePassword.toCharArray()) + val keyManagers = keyManagerFactory.keyManagers + val trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + // Password is not required for TrustStore. + trustMgrFactory.init(sslTrustStore) + val trustManagers = trustMgrFactory.trustManagers + return context.apply { init(keyManagers, trustManagers, newSecureRandom()) } + } +} \ No newline at end of file From ce9b6c1f189a9b1695dbd49e90b73c284b4c8f7c Mon Sep 17 00:00:00 2001 From: Maksymilian Pawlak <120831+m4ksio@users.noreply.github.com> Date: Thu, 23 Nov 2017 16:34:57 +0000 Subject: [PATCH 07/13] CORDA-311-post PR merged fixes (#2106) * SSH server integration --- .../kotlin/net/corda/core/flows/FlowLogic.kt | 12 + .../net/corda/core/messaging/FlowHandle.kt | 22 +- .../corda/core/utilities/ProgressTracker.kt | 11 + .../core/utilities/ProgressTrackerTest.kt | 20 +- docs/source/shell.rst | 8 +- .../kotlin/net/corda/node/SSHServerTest.kt | 4 +- .../corda/node/shell/FlowShellCommand.java | 5 +- .../net/corda/node/shell/RunShellCommand.java | 2 +- .../corda/node/shell/StartShellCommand.java | 4 +- .../node/shell/CordaAuthenticationPlugin.kt | 2 +- .../net/corda/node/shell/InteractiveShell.kt | 19 +- .../net/corda/node/shell/RPCOpsWithContext.kt | 214 +++--------------- .../node/utilities/ANSIProgressRenderer.kt | 4 +- 13 files changed, 101 insertions(+), 226 deletions(-) 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 8c9fba60de..a10a3d3fe2 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,12 @@ abstract class FlowLogic { } } + /** + * Returns a pair of the current progress step index (as integer) in steps tree of current [progressTracker], and an observable + * of its upcoming changes. + * + * @return Returns null if this flow has no progress tracker. + */ fun trackStepsTreeIndex(): DataFeed? { // TODO this is not threadsafe, needs an atomic get-step-and-subscribe return progressTracker?.let { @@ -355,6 +361,12 @@ abstract class FlowLogic { } } + /** + * Returns a pair of the current steps tree of current [progressTracker] as pairs of zero-based depth and stringified step + * label and observable of upcoming changes to the structure. + * + * @return Returns null if this flow has no progress tracker. + */ fun trackStepsTree(): DataFeed>, List>>? { // TODO this is not threadsafe, needs an atomic get-step-and-subscribe return progressTracker?.let { 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 90ba7e2596..84d734db29 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt @@ -8,13 +8,17 @@ import rx.Observable /** * [FlowHandle] is a serialisable handle for the started flow, parameterised by the type of the flow's return value. - * - * @property id The started state machine's ID. - * @property returnValue A [CordaFuture] of the flow's return value. */ @DoNotImplement interface FlowHandle : AutoCloseable { + /** + * The started state machine's ID. + */ val id: StateMachineRunId + + /** + * A [CordaFuture] of the flow's return value. + */ val returnValue: CordaFuture /** @@ -25,15 +29,23 @@ interface FlowHandle : AutoCloseable { /** * [FlowProgressHandle] is a serialisable handle for the started flow, parameterised by the type of the flow's return value. - * - * @property progress The stream of progress tracker events. */ interface FlowProgressHandle : FlowHandle { + /** + * The stream of progress tracker events. + */ val progress: Observable + /** + * [DataFeed] of current step in the steps tree, see [ProgressTracker] + */ val stepsTreeIndexFeed: DataFeed? + /** + * [DataFeed] of current steps tree, see [ProgressTracker] + */ 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. 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 22661eeb7b..8ce440ead3 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt @@ -99,6 +99,7 @@ class ProgressTracker(vararg steps: Step) { field = value } + /** The zero-bases index of the current step in a [allStepsLabels] list */ var stepsTreeIndex: Int = -1 private set(value) { field = value @@ -226,6 +227,10 @@ class ProgressTracker(vararg steps: Step) { */ val allSteps: List> get() = _allStepsCache + /** + * A list of all steps label 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 allStepsLabels: List> get() = _allStepsLabels() private var curChangeSubscription: Subscription? = null @@ -245,8 +250,14 @@ class ProgressTracker(vararg steps: Step) { */ val changes: Observable get() = _changes + /** + * An observable stream of changes to the [allStepsLabels] + */ val stepsTreeChanges: Observable>> get() = _stepsTreeChanges + /** + * An observable stream of changes to the [stepsTreeIndex] + */ val stepsTreeIndexChanges: Observable get() = _stepsTreeIndexChanges /** Returns true if the progress tracker has ended, either by reaching the [DONE] step or prematurely with an error */ 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 9bcf5c0fb5..4e15f122d9 100644 --- a/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt +++ b/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt @@ -98,7 +98,7 @@ class ProgressTrackerTest { val allSteps = pt.allSteps - //capture notifications + // Capture notifications. val stepsIndexNotifications = LinkedList() pt.stepsTreeIndexChanges.subscribe { stepsIndexNotifications += it @@ -113,7 +113,7 @@ class ProgressTrackerTest { assertEquals(step, allSteps[pt.stepsTreeIndex].second) } - //travel tree + // Travel tree. pt.currentStep = SimpleSteps.ONE assertCurrentStepsTree(0, SimpleSteps.ONE) @@ -126,7 +126,7 @@ class ProgressTrackerTest { pt.currentStep = SimpleSteps.THREE assertCurrentStepsTree(5, SimpleSteps.THREE) - //assert no structure changes and proper steps propagation + // Assert no structure changes and proper steps propagation. assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(0, 1, 3, 5)) assertThat(stepsTreeNotification).isEmpty() } @@ -135,13 +135,13 @@ class ProgressTrackerTest { fun `structure changes are pushed down when progress trackers are added`() { pt.setChildProgressTracker(SimpleSteps.TWO, pt2) - //capture notifications + // Capture notifications. val stepsIndexNotifications = LinkedList() pt.stepsTreeIndexChanges.subscribe { stepsIndexNotifications += it } - //put current state as a first change for simplicity when asserting + // Put current state as a first change for simplicity when asserting. val stepsTreeNotification = mutableListOf(pt.allStepsLabels) println(pt.allStepsLabels) pt.stepsTreeChanges.subscribe { @@ -164,7 +164,7 @@ class ProgressTrackerTest { assertCurrentStepsTree(9, SimpleSteps.FOUR) - //assert no structure changes and proper steps propagation + // 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 } @@ -173,13 +173,13 @@ class ProgressTrackerTest { fun `structure changes are pushed down when progress trackers are removed`() { pt.setChildProgressTracker(SimpleSteps.TWO, pt2) - //capture notifications + // Capture notifications. val stepsIndexNotifications = LinkedList() pt.stepsTreeIndexChanges.subscribe { stepsIndexNotifications += it } - //put current state as a first change for simplicity when asserting + // Put current state as a first change for simplicity when asserting. val stepsTreeNotification = mutableListOf(pt.allStepsLabels) pt.stepsTreeChanges.subscribe { stepsTreeNotification += it @@ -199,9 +199,9 @@ class ProgressTrackerTest { assertCurrentStepsTree(2, BabySteps.UNOS) - //assert no structure changes and proper steps propagation + // 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 + assertThat(stepsTreeNotification).hasSize(2) // 1 change + 1 our initial state. } @Test diff --git a/docs/source/shell.rst b/docs/source/shell.rst index fe74e44f29..2fc11829b2 100644 --- a/docs/source/shell.rst +++ b/docs/source/shell.rst @@ -35,9 +35,9 @@ Shell can also be accessible via SSH. By default SSH server is *disabled*. To en 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. +SSH requires users to login first - using the same users as RPC system. In fact, the shell serves as a proxy to RPC and communicates +with the node using RPC calls. This also means that RPC permissions are enforced. No permissions are required to allow the connection +and log 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. @@ -51,7 +51,7 @@ errors. Connecting ---------- -Linux and MacOS computers usually come with SSH client preinstalled. On Windows it usually require extra download. +Linux and MacOS computers usually come with SSH client preinstalled. On Windows it usually requires 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. diff --git a/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt b/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt index e7346a1932..96c701f761 100644 --- a/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt @@ -139,8 +139,8 @@ class SSHServerTest { 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) + // There are ANSI control characters involved, so we want to avoid direct byte to byte matching. + assertThat(response.lines()).filteredOn( { it.contains("Done")}).hasSize(1) } } 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 d3ed752531..f857a7eb62 100644 --- a/node/src/main/java/net/corda/node/shell/FlowShellCommand.java +++ b/node/src/main/java/net/corda/node/shell/FlowShellCommand.java @@ -4,7 +4,7 @@ package net.corda.node.shell; import net.corda.core.messaging.CordaRPCOps; import net.corda.node.utilities.ANSIProgressRenderer; -import net.corda.node.utilities.CRaSHNSIProgressRenderer; +import net.corda.node.utilities.CRaSHANSIProgressRenderer; import org.crsh.cli.*; import org.crsh.command.*; import org.crsh.text.*; @@ -12,7 +12,6 @@ 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( @@ -49,7 +48,7 @@ public class FlowShellCommand extends InteractiveShellCommand { return; } String inp = input == null ? "" : String.join(" ", input).trim(); - runFlowByNameFragment(name, inp, out, rpcOps, ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHNSIProgressRenderer(out) ); + runFlowByNameFragment(name, inp, out, rpcOps, ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHANSIProgressRenderer(out) ); } @Command diff --git a/node/src/main/java/net/corda/node/shell/RunShellCommand.java b/node/src/main/java/net/corda/node/shell/RunShellCommand.java index 108b567a9b..6875a5cdb8 100644 --- a/node/src/main/java/net/corda/node/shell/RunShellCommand.java +++ b/node/src/main/java/net/corda/node/shell/RunShellCommand.java @@ -30,7 +30,7 @@ public class RunShellCommand extends InteractiveShellCommand { return null; } - return InteractiveShell.runRPCFromString(command, out, context); + return InteractiveShell.runRPCFromString(command, out, context, ops()); } private void emitHelp(InvocationContext context, StringToMethodCallParser parser) { 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 e1c91ebb75..3d2b9953e9 100644 --- a/node/src/main/java/net/corda/node/shell/StartShellCommand.java +++ b/node/src/main/java/net/corda/node/shell/StartShellCommand.java @@ -3,7 +3,7 @@ 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 net.corda.node.utilities.CRaSHANSIProgressRenderer; import org.crsh.cli.*; import java.util.*; @@ -14,6 +14,6 @@ public class StartShellCommand extends InteractiveShellCommand { 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) { ANSIProgressRenderer ansiProgressRenderer = ansiProgressRenderer(); - FlowShellCommand.startFlow(name, input, out, ops(), ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHNSIProgressRenderer(out)); + FlowShellCommand.startFlow(name, input, out, ops(), ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHANSIProgressRenderer(out)); } } diff --git a/node/src/main/kotlin/net/corda/node/shell/CordaAuthenticationPlugin.kt b/node/src/main/kotlin/net/corda/node/shell/CordaAuthenticationPlugin.kt index 9854a9caf4..61fbf90f56 100644 --- a/node/src/main/kotlin/net/corda/node/shell/CordaAuthenticationPlugin.kt +++ b/node/src/main/kotlin/net/corda/node/shell/CordaAuthenticationPlugin.kt @@ -25,7 +25,7 @@ class CordaAuthenticationPlugin(val rpcOps:CordaRPCOps, val userService:RPCUserS 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 CordaSSHAuthInfo(true, makeRPCOpsWithContext(rpcOps, InvocationContext.rpc(actor), RpcPermissions(user.permissions))) } return AuthInfo.UNSUCCESSFUL; 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 d2ca683740..755b47e3a0 100644 --- a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt +++ b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt @@ -101,10 +101,10 @@ object InteractiveShell { this.nodeLegalName = configuration.myLegalName this.database = database val dir = configuration.baseDirectory - val runSshDeamon = configuration.sshd != null + val runSshDaemon = configuration.sshd != null val config = Properties() - if (runSshDeamon) { + if (runSshDaemon) { val sshKeysDir = dir / "sshkey" sshKeysDir.toFile().mkdirs() @@ -120,7 +120,7 @@ object InteractiveShell { ExternalResolver.INSTANCE.addCommand("start", "An alias for 'flow start'", StartShellCommand::class.java) shell = ShellLifecycle(dir).start(config) - if (runSshDeamon) { + if (runSshDaemon) { Node.printBasicNodeInfo("SSH server listening on port", configuration.sshd!!.port.toString()) } } @@ -182,7 +182,7 @@ object InteractiveShell { context.refresh() this.config = config start(context) - return context.getPlugin(ShellFactory::class.java).create(null, CordaSSHAuthInfo(false, RPCOpsWithContext(rpcOps, net.corda.core.context.InvocationContext.shell(), RpcPermissions.ALL), StdoutANSIProgressRenderer)) + return context.getPlugin(ShellFactory::class.java).create(null, CordaSSHAuthInfo(false, makeRPCOpsWithContext(rpcOps, net.corda.core.context.InvocationContext.shell(), RpcPermissions.ALL), StdoutANSIProgressRenderer)) } } @@ -236,7 +236,7 @@ object InteractiveShell { try { // 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 stateObservable = runFlowFromString({ clazz, args -> rpcOps.startTrackedFlowDynamic(clazz, *args) }, inputData, clazz) val latch = CountDownLatch(1) ansiProgressRenderer.render(stateObservable, { latch.countDown() }) @@ -247,7 +247,6 @@ object InteractiveShell { } catch (e: InterruptedException) { // 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) } @@ -326,7 +325,9 @@ object InteractiveShell { val (stateMachines, stateMachineUpdates) = proxy.stateMachinesFeed() val currentStateMachines = stateMachines.map { StateMachineUpdate.Added(it) } val subscriber = FlowWatchPrintingSubscriber(out) - stateMachineUpdates.startWith(currentStateMachines).subscribe(subscriber) + database.transaction { + stateMachineUpdates.startWith(currentStateMachines).subscribe(subscriber) + } var result: Any? = subscriber.future if (result is Future<*>) { if (!result.isDone) { @@ -348,7 +349,7 @@ object InteractiveShell { } @JvmStatic - fun runRPCFromString(input: List, out: RenderPrintWriter, context: InvocationContext): Any? { + fun runRPCFromString(input: List, out: RenderPrintWriter, context: InvocationContext, cordaRPCOps: CordaRPCOps): Any? { val parser = StringToMethodCallParser(CordaRPCOps::class.java, context.attributes["mapper"] as ObjectMapper) val cmd = input.joinToString(" ").trim { it <= ' ' } @@ -363,7 +364,7 @@ object InteractiveShell { var result: Any? = null try { InputStreamSerializer.invokeContext = context - val call = database.transaction { parser.parse(context.attributes["ops"] as CordaRPCOps, cmd) } + val call = database.transaction { parser.parse(cordaRPCOps, cmd) } result = call.call() if (result != null && result !is kotlin.Unit && result !is Void) { result = printAndFollowRPCResponse(result, out) 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 adcb8f30b7..afb163fed0 100644 --- a/node/src/main/kotlin/net/corda/node/shell/RPCOpsWithContext.kt +++ b/node/src/main/kotlin/net/corda/node/shell/RPCOpsWithContext.kt @@ -1,205 +1,45 @@ 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 java.io.InputStream -import java.security.PublicKey -import java.time.Instant +import java.lang.reflect.InvocationTargetException +import java.lang.reflect.Proxy import java.util.concurrent.CompletableFuture import java.util.concurrent.Future -class RPCOpsWithContext(val cordaRPCOps: CordaRPCOps, val invocationContext:InvocationContext, val rpcPermissions: RpcPermissions) : CordaRPCOps { +fun makeRPCOpsWithContext(cordaRPCOps: CordaRPCOps, invocationContext:InvocationContext, rpcPermissions: RpcPermissions) : CordaRPCOps { + return Proxy.newProxyInstance(CordaRPCOps::class.java.classLoader, arrayOf(CordaRPCOps::class.java), { proxy, method, args -> + RPCContextRunner(invocationContext, rpcPermissions) { + try { + method.invoke(cordaRPCOps, *(args ?: arrayOf())) + } catch (e: InvocationTargetException) { + // Unpack exception. + throw e.targetException + } + }.get().getOrThrow() + }) as 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) - } +private class RPCContextRunner(val invocationContext:InvocationContext, val rpcPermissions: RpcPermissions, val block:() -> T) : Thread() { + private var result: CompletableFuture = CompletableFuture() + override fun run() { + CURRENT_RPC_CONTEXT.set(RpcAuthContext(invocationContext, rpcPermissions)) + try { + result.complete(block()) + } catch (e:Throwable) { + result.completeExceptionally(e) + } finally { 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 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() + fun get(): Future { + start() + join() + return result } } \ 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 303f6e91c0..74c8e077c4 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/ANSIProgressRenderer.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/ANSIProgressRenderer.kt @@ -169,7 +169,7 @@ abstract class ANSIProgressRenderer { } -class CRaSHNSIProgressRenderer(val renderPrintWriter:RenderPrintWriter) : ANSIProgressRenderer() { +class CRaSHANSIProgressRenderer(val renderPrintWriter:RenderPrintWriter) : ANSIProgressRenderer() { override fun printLine(line: String) { renderPrintWriter.println(line) @@ -181,7 +181,7 @@ class CRaSHNSIProgressRenderer(val renderPrintWriter:RenderPrintWriter) : ANSIPr } override fun setup() { - //we assume SSH always use ansi + // We assume SSH always use ANSI. usingANSI = true } From ffd693719a592d92265dea3e2f862cec6c34af80 Mon Sep 17 00:00:00 2001 From: Alberto Arri <30873160+al-r3@users.noreply.github.com> Date: Fri, 24 Nov 2017 13:27:30 +0000 Subject: [PATCH 08/13] docs for additional-node-infos (#2107) * docs for additional-node-infos --- docs/source/corda-networks-index.rst | 3 ++- docs/source/network-map.rst | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/source/corda-networks-index.rst b/docs/source/corda-networks-index.rst index abdcf7c0b5..9315a2de09 100644 --- a/docs/source/corda-networks-index.rst +++ b/docs/source/corda-networks-index.rst @@ -6,4 +6,5 @@ Corda networks setting-up-a-corda-network permissioning - versioning \ No newline at end of file + network-map + versioning diff --git a/docs/source/network-map.rst b/docs/source/network-map.rst index d8ee82a2cf..34356bad4f 100644 --- a/docs/source/network-map.rst +++ b/docs/source/network-map.rst @@ -1,6 +1,14 @@ Network Map =========== +The network map stores a collection of ``NodeInfo`` objects, each representing another node with which the node can interact. +There two sources from which a Corda node can retrieve ``NodeInfo`` objects: + +1. the REST protocol with the network map service, which also provides a publishing API, + +2. the ``additional-node-infos`` directory. + + Protocol Design --------------- The node info publishing protocol: @@ -35,4 +43,15 @@ Network Map service REST API: | GET | /api/network-map/parameters/{hash}| Retrieve ``NetworkParameters`` object with the same hash. | +----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ -TODO: Access control of the network map will be added in the future. \ No newline at end of file +TODO: Access control of the network map will be added in the future. + + +The ``additional-node-infos`` directory +--------------------------------------- +Each Corda node reads, and continuously polls, the files contained in a directory named ``additional-node-infos`` inside the node base directory. + +Nodes expect to find a serialized ``SignedData`` object, the same object which is sent to network map server. + +Whenever a node starts it writes on disk a file containing its own ``NodeInfo``, this file is called ``nodeInfo-XXX`` where ``XXX`` is a long string. + +Hence if an operator wants node A to see node B they can pick B's ``NodeInfo`` file from B base directory and drop it into A's ``additional-node-infos`` directory. From 20337aaa25842dc66f5ffe080f25dcfe4458b6fe Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Fri, 24 Nov 2017 18:06:01 +0000 Subject: [PATCH 09/13] Network permissioning new doc --- docs/source/permissioning.rst | 131 ++++++++++++++++------------------ 1 file changed, 63 insertions(+), 68 deletions(-) diff --git a/docs/source/permissioning.rst b/docs/source/permissioning.rst index 637b78ab50..13d889cc43 100644 --- a/docs/source/permissioning.rst +++ b/docs/source/permissioning.rst @@ -1,104 +1,99 @@ -Network permissioning (Doorman) -=============================== +Network Permissioning +===================== -The keystore located in ``/certificates/sslkeystore.jks`` is required to connect to the Corda network securely. -In development mode (when ``devMode = true``, see ":doc:`corda-configuration-file`" for more information) a pre-configured -keystore will be used if the keystore does not exist. This is to ensure developers can get the nodes working as quickly -as possible. +.. contents:: -However this is not secure for the real network. This documentation will explain the procedure of obtaining a signed -certificate for TestNet. +Corda networks are *permissioned*. To connect to a network, a node needs three keystores in its +``/certificates/`` folder: -Initial Registration --------------------- +* ``truststore.jks``, which stores trusted public keys and certificates (in our case, those of the network root CA) +* ``nodekeystore.jks``, which stores the node’s identity keypairs and certificates +* ``sslkeystore.jks``, which stores the node’s TLS keypairs and certificates -The certificate signing request will be created based on node information obtained from the node configuration. -The following information from the node configuration file is needed to generate the request. +In development mode (i.e. when ``devMode = true``, see :doc:`corda-configuration-file` for more information), +pre-configured keystores are used if the required keystores do not exist. This ensures that developers can get the +nodes working as quickly as possible. -:myLegalName: Your company's legal name as an X.500 string. X.500 allows differentiation between entities with the same - name as the legal name needs to be unique on the network. If another node has already been permissioned with this - name then the permissioning server will automatically reject the request. The request will also be rejected if it - violates legal name rules, see `Legal Name Constraints`_ for more information. +However, these pre-configured keystores are not secure. For a real network, you need to create a certificate authority +that will be used in the creation of these keystores for each node joining the network. -:emailAddress: e.g. "admin@company.com" +Creating the network keypairs and certificates +---------------------------------------------- +You can use any standard key tools or Corda's ``X509Utilities`` (which uses Bouncy Castle) to create the required +public/private keypairs and certificates. The keypairs and certificates should obey the following restrictions: -:compatibilityZoneURL: Corda compatibility zone network management service root URL. +* The certificates must follow the `X.509 standard `_ -A new pair of private and public keys generated by the Corda node will be used to create the request. + * We recommend X.509 v3 for forward compatibility -The utility will submit the request to the doorman server and poll for a result periodically to retrieve the certificates. -Once the request has been approved and the certificates downloaded from the server, the node will create the keystore and trust store using the certificates and the generated private key. +* The TLS certificates must follow the `TLS v1.2 standard `_ -.. note:: You can exit the utility at any time if the approval process is taking longer than expected. The request process will resume on restart. +Creating the node certificate authority +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The node certificate authority is used to sign the node identity certificates. An intermediate CA is used instead of +the root CA for day-to-day key signing to reduce the risk of the root CA's private key being compromised. -This process only is needed when the node connects to the network for the first time, or when the certificate expires. +Creating the root CA's keystore and truststore +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Legal Name Constraints ----------------------- -The legal name is the unique identifier in the Corda network, so constraints have been set out to prevent encoding attacks and visual spoofing. +1. Create a new keypair -The legal name validator (see ``LegalNameValidator.kt``) is used to enforce rules on Corda's legal names, it is intended to be used by the network operator and Corda node during the node registration process. -It has two functions, a function to normalize legal names, and a function to validate legal names. + * This will be used as the root CA's keypair -The normalize function performs the following transformations: +2. Create a self-signed certificate for the keypair. The basic constraints extension must be set to ``true`` -* Remove leading and trailing whitespaces. + * This will be used as the root CA's certificate -* Replace multiple whitespaces with a single space. +3. Store the root CA's keypair and certificate in a keystore for later use -* Normalize the string according to `NFKC normalization form `_. +4. Store the root CA's certificate in a Java keystore named ``truststore.jks`` using the alias ``cordarootca`` -The validation function will validate the input string using the following rules: +.. warning:: The root CA's private key should be protected and kept safe. -* No blacklisted words like "node", "server". +Creating the intermediate CA's keystore +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* Restrict names to Latin scripts for now to avoid right-to-left issues, debugging issues when we can't pronounce names over the phone, and character confusability attacks. +1. Create a new keypair -* Should start with a capital letter. + * This will be used as the intermediate CA's keypair -* No commas or equals signs. +2. Obtain a certificate for the keypair signed with the root CA key. The basic constraints extension must be set to + ``true`` -* No dollars or quote marks, although we may relax the quote mark constraint in future to handle Irish company names. + * This will be used as the intermediate CA's certificate -Starting the Registration -------------------------- +3. Store the intermediate CA's keypair and certificate chain (i.e. the intermediate CA certificate *and* the root CA + certificate) in a keystore for later use -You will need to specify the working directory of your Corda node using ``--base-dir`` flag. This is defaulted to current directory if left blank. -You can also specify the location of ``node.conf`` with ``--config-file`` flag if it's not in the working directory. +Creating the node CA keystores and TLS keystores +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Each node serves as its own "node CA" in issuing the child certificates that it uses to sign its identity keys, +anonymous keys and TLS certificates. -**To start the registration**:: +Creating the node CA keystores +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - java -jar corda.jar --initial-registration --base-dir <> --config-file <> +1. On each node, create a new keypair -A ``certificates`` folder containing the keystore and trust store will be created in the base directory when the process is completed. +2. Obtain a certificate for the keypair signed with the intermediate CA key. The basic constraints extension must be + set to ``true`` -.. warning:: The keystore is protected by the keystore password from the node configuration file. The password should kept safe to protect the private key and certificate. +3. Store the keypair in a Java keystore named ``nodekeystore.jks`` using the alias ``cordaclientca`` +Creating the node TLS keystores +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Protocol Design ---------------- -.. note:: This section is intended for developers who want to implement their own doorman service. +1. On each node, create a new keypair -The certificate signing protocol: +2. Create a certificate for the keypair signed with the node CA key. The basic constraints extension must be set to + ``false`` -* Generate a keypair, save it to disk. +3. Store the key and certificates in a Java keystore named ``sslkeystore.jks`` using the alias ``cordaclienttls`` -* Generate a CSR using Bouncy Castle or the java crypto APIs containing myLegalName from the config file. We should also have an admin email address in the config file and CSR so we know who to email if anything goes wrong. Sign it with the private key. +Installing the certificates on the nodes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +For each node: -* HTTPS POST the CSR to the doorman. It creates the server-side records of this request, allocates an ID for it, and then sends back an HTTP redirect to another URL that contains that request ID (which should be sufficiently large that it's not predictable or brute forceable). +1. Copy the node's ``nodekeystore.jks`` and ``sslkeystore.jks`` keystores to the node's certificate directory -* Store that URL to disk. - -* Server goes into a slow polling loop, in which every 10 minutes or so it fetches the URL it was given in the redirect. Mostly it will get 204 No Content. Eventually it will get 200 OK and download the signed certificate in binary form, which it can then stash in its local keystore file. - -The initial registration process uses the following web api to communicate with the doorman service: - -+----------------+------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ -| Request method | Path | Description | -+================+==============================+========================================================================================================================================================+ -| POST | /api/certificate | Create new certificate request record and stored for further approval process, server will response with a request ID if the request has been accepted.| -+----------------+------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ -| GET | /api/certificate/{requestId} | Retrieve certificates for requestId, the server will return HTTP 204 if request is not yet approved or HTTP 401 if it has been rejected. | -+----------------+------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ - -See ``NetworkRegistrationHelper`` and ``X509Utilities`` for examples of certificate signing request creation and certificate signing using Bouncy Castle. +2. Copy the ``truststore.jks`` keystore created by the root CA to the node's certificate directory \ No newline at end of file From 449155cea3f93d65afca4d4d06b8b9658316daf5 Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Mon, 27 Nov 2017 12:34:33 +0000 Subject: [PATCH 10/13] IdentityService is no longer obtained lazily. (#2130) --- .../net/corda/node/internal/AbstractNode.kt | 17 +++++++++-------- .../main/kotlin/net/corda/node/internal/Node.kt | 5 +++-- .../identity/PersistentIdentityService.kt | 14 +++++++------- .../persistence/AbstractPartyDescriptor.kt | 4 +--- .../AbstractPartyToX500NameAsStringConverter.kt | 4 +--- .../persistence/HibernateConfiguration.kt | 6 +++--- .../corda/node/utilities/CordaPersistence.kt | 13 ++++++------- .../services/vault/VaultQueryJavaTests.java | 2 +- .../net/corda/node/InteractiveShellTest.kt | 6 +----- .../services/events/NodeSchedulerServiceTest.kt | 3 +-- .../identity/PersistentIdentityServiceTests.kt | 6 ++---- .../services/messaging/ArtemisMessagingTests.kt | 3 +-- .../persistence/DBCheckpointStorageTests.kt | 4 ++-- .../persistence/DBTransactionStorageTests.kt | 3 +-- .../persistence/HibernateConfigurationTest.kt | 2 +- .../persistence/NodeAttachmentStorageTest.kt | 4 ++-- .../services/schema/HibernateObserverTests.kt | 4 ++-- .../DistributedImmutableMapTests.kt | 4 ++-- .../PersistentUniquenessProviderTests.kt | 3 +-- .../node/services/vault/VaultQueryTests.kt | 2 +- .../corda/node/utilities/ObservablesTests.kt | 4 ++-- .../net/corda/irs/api/NodeInterestRatesTest.kt | 3 +-- .../kotlin/net/corda/testing/node/MockNode.kt | 8 +++++--- .../net/corda/testing/node/MockServices.kt | 11 +++++------ 24 files changed, 61 insertions(+), 74 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 89af824be0..79583b374f 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -181,11 +181,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration, initCertificate() val (keyPairs, info) = initNodeInfo() val schemaService = NodeSchemaService(cordappLoader) + val identityService = makeIdentityService(info) // Do all of this in a database transaction so anything that might need a connection has one. - val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService) { database -> + val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService) { database -> + identityService.loadIdentities(info.legalIdentitiesAndCerts) val transactionStorage = makeTransactionStorage(database) val stateLoader = StateLoaderImpl(transactionStorage) - val nodeServices = makeServices(keyPairs, schemaService, transactionStorage, stateLoader, database, info) + val nodeServices = makeServices(keyPairs, schemaService, transactionStorage, stateLoader, database, info, identityService) val notaryService = makeNotaryService(nodeServices, database) smm = makeStateMachineManager(database) val flowStarter = FlowStarterImpl(serverThread, smm) @@ -489,12 +491,11 @@ 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, info: NodeInfo): MutableList { + private fun makeServices(keyPairs: Set, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, stateLoader: StateLoader, database: CordaPersistence, info: NodeInfo, identityService: IdentityService): MutableList { checkpointStorage = DBCheckpointStorage() val metrics = MetricRegistry() attachments = NodeAttachmentService(metrics) val cordappProvider = CordappProviderImpl(cordappLoader, attachments) - val identityService = makeIdentityService(info) val keyManagementService = makeKeyManagementService(identityService, keyPairs) _services = ServiceHubInternalImpl( identityService, @@ -548,10 +549,10 @@ abstract class AbstractNode(val configuration: NodeConfiguration, // Specific class so that MockNode can catch it. class DatabaseConfigurationException(msg: String) : CordaException(msg) - protected open fun initialiseDatabasePersistence(schemaService: SchemaService, insideTransaction: (CordaPersistence) -> T): T { + protected open fun initialiseDatabasePersistence(schemaService: SchemaService, identityService: IdentityService, insideTransaction: (CordaPersistence) -> T): T { val props = configuration.dataSourceProperties if (props.isNotEmpty()) { - val database = configureDatabase(props, configuration.database, { _services.identityService }, schemaService) + val database = configureDatabase(props, configuration.database, identityService, schemaService) // Now log the vendor string as this will also cause a connection to be tested eagerly. database.transaction { log.info("Connected to ${database.dataSource.connection.metaData.databaseProductName} database.") @@ -611,13 +612,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } } - private fun makeIdentityService(info: NodeInfo): IdentityService { + private fun makeIdentityService(info: NodeInfo): PersistentIdentityService { val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword) val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) val trustRoot = trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA) val clientCa = caKeyStore.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) val caCertificates = arrayOf(info.legalIdentitiesAndCerts[0].certificate, clientCa.certificate.cert) - return PersistentIdentityService(info.legalIdentitiesAndCerts, trustRoot = trustRoot, caCertificates = *caCertificates) + return PersistentIdentityService(trustRoot, *caCertificates) } protected abstract fun makeTransactionVerifierService(): TransactionVerifierService 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 5cf3f33f18..f7824796a4 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.IdentityService import net.corda.core.node.services.TransactionVerifierService import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.serialization.internal.SerializationEnvironmentImpl @@ -228,7 +229,7 @@ open class Node(configuration: NodeConfiguration, * This is not using the H2 "automatic mixed mode" directly but leans on many of the underpinnings. For more details * on H2 URLs and configuration see: http://www.h2database.com/html/features.html#database_url */ - override fun initialiseDatabasePersistence(schemaService: SchemaService, insideTransaction: (CordaPersistence) -> T): T { + override fun initialiseDatabasePersistence(schemaService: SchemaService, identityService: IdentityService, insideTransaction: (CordaPersistence) -> T): T { val databaseUrl = configuration.dataSourceProperties.getProperty("dataSource.url") val h2Prefix = "jdbc:h2:file:" if (databaseUrl != null && databaseUrl.startsWith(h2Prefix)) { @@ -245,7 +246,7 @@ open class Node(configuration: NodeConfiguration, printBasicNodeInfo("Database connection url is", "jdbc:h2:$url/node") } } - return super.initialiseDatabasePersistence(schemaService, insideTransaction) + return super.initialiseDatabasePersistence(schemaService, identityService, insideTransaction) } private val _startupComplete = openFuture() 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 960cfb84fc..a108ae0744 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 @@ -26,13 +26,9 @@ import javax.persistence.Id import javax.persistence.Lob @ThreadSafe -class PersistentIdentityService(identities: Iterable = emptySet(), - confidentialIdentities: Iterable = emptySet(), - override val trustRoot: X509Certificate, +class PersistentIdentityService(override val trustRoot: X509Certificate, vararg caCertificates: X509Certificate) : SingletonSerializeAsToken(), IdentityService { - constructor(wellKnownIdentities: Iterable = emptySet(), - confidentialIdentities: Iterable = emptySet(), - trustRoot: X509CertificateHolder) : this(wellKnownIdentities, confidentialIdentities, trustRoot.cert) + constructor(trustRoot: X509CertificateHolder) : this(trustRoot.cert) companion object { private val log = contextLogger() @@ -101,6 +97,10 @@ class PersistentIdentityService(identities: Iterable = empt init { val caCertificatesWithRoot: Set = caCertificates.toSet() + trustRoot caCertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(caCertificatesWithRoot)) + } + + /** Requires a database transaction. */ + fun loadIdentities(identities: Iterable = emptySet(), confidentialIdentities: Iterable = emptySet()) { identities.forEach { val key = mapToKey(it) keyToParties.addWithDuplicatesAllowed(key, it, false) @@ -136,7 +136,7 @@ class PersistentIdentityService(identities: Iterable = empt val certificates = identity.certPath.certificates val idx = certificates.lastIndexOf(firstCertWithThisName) val certFactory = CertificateFactory.getInstance("X509") - val firstPath = certFactory.generateCertPath(certificates.slice(idx..certificates.size - 1)) + val firstPath = certFactory.generateCertPath(certificates.slice(idx until certificates.size)) verifyAndRegisterIdentity(PartyAndCertificate(firstPath)) } 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 7201c7df56..d4d6384719 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 @@ -9,13 +9,11 @@ import org.hibernate.type.descriptor.java.AbstractTypeDescriptor import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan import org.hibernate.type.descriptor.java.MutabilityPlan -class AbstractPartyDescriptor(identitySvc: () -> IdentityService) : AbstractTypeDescriptor(AbstractParty::class.java) { +class AbstractPartyDescriptor(private val identityService: IdentityService) : AbstractTypeDescriptor(AbstractParty::class.java) { companion object { private val log = contextLogger() } - private val identityService: IdentityService by lazy(identitySvc) - override fun fromString(dbData: String?): AbstractParty? { return if (dbData != null) { val party = identityService.wellKnownPartyFromX500Name(CordaX500Name.parse(dbData)) 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 dcaf9f82d3..cf813a25d9 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 @@ -12,13 +12,11 @@ import javax.persistence.Converter * Completely anonymous parties are stored as null (to preserve privacy). */ @Converter(autoApply = true) -class AbstractPartyToX500NameAsStringConverter(identitySvc: () -> IdentityService) : AttributeConverter { +class AbstractPartyToX500NameAsStringConverter(private val identityService: IdentityService) : AttributeConverter { companion object { private val log = contextLogger() } - private val identityService: IdentityService by lazy(identitySvc) - override fun convertToDatabaseColumn(party: AbstractParty?): String? { if (party != null) { val partyName = identityService.wellKnownPartyFromAnonymous(party)?.toString() 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 7d5f20b248..1b205b41c5 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 @@ -26,7 +26,7 @@ import java.sql.Connection import java.util.* import java.util.concurrent.ConcurrentHashMap -class HibernateConfiguration(val schemaService: SchemaService, private val databaseProperties: Properties, private val createIdentityService: () -> IdentityService) { +class HibernateConfiguration(val schemaService: SchemaService, private val databaseProperties: Properties, private val identityService: IdentityService) { companion object { private val logger = contextLogger() } @@ -41,7 +41,7 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab // Hibernate warns about not being able to find a descriptor if we don't provide one, but won't use it by default // so we end up providing both descriptor and converter. We should re-examine this in later versions to see if // either Hibernate can be convinced to stop warning, use the descriptor by default, or something else. - JavaTypeDescriptorRegistry.INSTANCE.addDescriptor(AbstractPartyDescriptor(createIdentityService)) + JavaTypeDescriptorRegistry.INSTANCE.addDescriptor(AbstractPartyDescriptor(identityService)) sessionFactoryForSchemas(it) } @@ -80,7 +80,7 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab } }) // register custom converters - applyAttributeConverter(AbstractPartyToX500NameAsStringConverter(createIdentityService)) + applyAttributeConverter(AbstractPartyToX500NameAsStringConverter(identityService)) // 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) diff --git a/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt b/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt index 77055c6073..c02f5a4416 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt @@ -22,19 +22,19 @@ const val NODE_DATABASE_PREFIX = "node_" //HikariDataSource implements Closeable which allows CordaPersistence to be Closeable class CordaPersistence(var dataSource: HikariDataSource, private val schemaService: SchemaService, - private val createIdentityService: () -> IdentityService, databaseProperties: Properties) : Closeable { + private val identityService: IdentityService, databaseProperties: Properties) : Closeable { var transactionIsolationLevel = parserTransactionIsolationLevel(databaseProperties.getProperty("transactionIsolationLevel")) val hibernateConfig: HibernateConfiguration by lazy { transaction { - HibernateConfiguration(schemaService, databaseProperties, createIdentityService) + HibernateConfiguration(schemaService, databaseProperties, identityService) } } val entityManagerFactory get() = hibernateConfig.sessionFactoryForRegisteredSchemas companion object { - fun connect(dataSource: HikariDataSource, schemaService: SchemaService, createIdentityService: () -> IdentityService, databaseProperties: Properties): CordaPersistence { - return CordaPersistence(dataSource, schemaService, createIdentityService, databaseProperties).apply { + fun connect(dataSource: HikariDataSource, schemaService: SchemaService, identityService: IdentityService, databaseProperties: Properties): CordaPersistence { + return CordaPersistence(dataSource, schemaService, identityService, databaseProperties).apply { DatabaseTransactionManager(this) } } @@ -120,11 +120,10 @@ class CordaPersistence(var dataSource: HikariDataSource, private val schemaServi } } -fun configureDatabase(dataSourceProperties: Properties, databaseProperties: Properties?, createIdentityService: () -> IdentityService, schemaService: SchemaService = NodeSchemaService(null)): CordaPersistence { +fun configureDatabase(dataSourceProperties: Properties, databaseProperties: Properties?, identityService: IdentityService, schemaService: SchemaService = NodeSchemaService(null)): CordaPersistence { val config = HikariConfig(dataSourceProperties) val dataSource = HikariDataSource(config) - val persistence = CordaPersistence.connect(dataSource, schemaService, createIdentityService, databaseProperties ?: Properties()) - + val persistence = CordaPersistence.connect(dataSource, schemaService, identityService, databaseProperties ?: Properties()) // Check not in read-only mode. persistence.transaction { persistence.dataSource.connection.use { 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 350c6a4a3e..9b2a1de3c0 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 @@ -69,7 +69,7 @@ public class VaultQueryJavaTests { keys.add(getDUMMY_NOTARY_KEY()); IdentityService identitySvc = makeTestIdentityService(); @SuppressWarnings("unchecked") - Pair databaseAndServices = makeTestDatabaseAndMockServices(keys, () -> identitySvc, cordappPackages); + Pair databaseAndServices = makeTestDatabaseAndMockServices(keys, identitySvc, cordappPackages); issuerServices = new MockServices(cordappPackages, getDUMMY_CASH_ISSUER_NAME(), getDUMMY_CASH_ISSUER_KEY(), getBOC_KEY()); database = databaseAndServices.getFirst(); services = databaseAndServices.getSecond(); diff --git a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt index 5f55195c22..f8168afeb6 100644 --- a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt +++ b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt @@ -2,7 +2,6 @@ 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 @@ -10,8 +9,6 @@ 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 @@ -21,7 +18,6 @@ import net.corda.testing.DEV_TRUST_ROOT import net.corda.testing.MEGA_CORP import net.corda.testing.MEGA_CORP_IDENTITY import net.corda.testing.node.MockServices -import net.corda.testing.node.MockServices.Companion.makeTestIdentityService import net.corda.testing.rigorousMock import org.junit.After import org.junit.Before @@ -33,7 +29,7 @@ import kotlin.test.assertEquals class InteractiveShellTest { @Before fun setup() { - InteractiveShell.database = configureDatabase(MockServices.makeTestDataSourceProperties(), MockServices.makeTestDatabaseProperties(), ::makeTestIdentityService) + InteractiveShell.database = configureDatabase(MockServices.makeTestDataSourceProperties(), MockServices.makeTestDatabaseProperties(), rigorousMock()) } @After 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 3dda01deb1..e33accb68e 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 @@ -38,7 +38,6 @@ import net.corda.testing.contracts.DummyContract import net.corda.testing.node.* 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.assertj.core.api.Assertions.assertThat import org.junit.* import java.nio.file.Paths @@ -92,7 +91,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { calls = 0 val dataSourceProps = makeTestDataSourceProperties() val databaseProperties = makeTestDatabaseProperties() - database = configureDatabase(dataSourceProps, databaseProperties, ::makeTestIdentityService) + database = configureDatabase(dataSourceProps, databaseProperties, rigorousMock()) val identityService = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT) kms = MockKeyManagementService(identityService, ALICE_KEY) val configuration = testNodeConfiguration(Paths.get("."), CordaX500Name("Alice", "London", "GB")) 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 30c1753a43..e4d57e09c2 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 @@ -10,7 +10,6 @@ import net.corda.core.internal.toX509CertHolder import net.corda.core.node.services.IdentityService import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.core.internal.cert -import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.utilities.CertificateAndKeyPair import net.corda.node.utilities.CertificateType import net.corda.node.utilities.CordaPersistence @@ -21,7 +20,6 @@ import org.junit.After import org.junit.Before import org.junit.Test import java.security.cert.CertificateFactory -import javax.security.auth.x500.X500Principal import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertNull @@ -37,7 +35,7 @@ class PersistentIdentityServiceTests { @Before fun setup() { - val databaseAndServices = MockServices.makeTestDatabaseAndMockServices(keys = emptyList(), createIdentityService = { PersistentIdentityService(trustRoot = DEV_TRUST_ROOT) }) + val databaseAndServices = MockServices.makeTestDatabaseAndMockServices(keys = emptyList(), identityService = PersistentIdentityService(DEV_TRUST_ROOT)) database = databaseAndServices.first services = databaseAndServices.second identityService = services.identityService @@ -236,7 +234,7 @@ class PersistentIdentityServiceTests { // Create new identity service mounted onto same DB val newPersistentIdentityService = database.transaction { - PersistentIdentityService(trustRoot = DEV_TRUST_ROOT) + PersistentIdentityService(DEV_TRUST_ROOT) } database.transaction { 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 225ea6952f..41a96f7f58 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 @@ -19,7 +19,6 @@ import net.corda.testing.* import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO 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.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.After @@ -68,7 +67,7 @@ class ArtemisMessagingTests { baseDirectory = baseDirectory, myLegalName = ALICE.name) LogHelper.setLevel(PersistentUniquenessProvider::class) - database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService) + database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), rigorousMock()) networkMapRegistrationFuture = doneFuture(Unit) networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database), rigorousMock()) } diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt index 489ad49198..8b97d53de6 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt @@ -10,8 +10,8 @@ import net.corda.node.utilities.configureDatabase import net.corda.testing.LogHelper 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 net.corda.testing.SerializationEnvironmentRule +import net.corda.testing.rigorousMock import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -37,7 +37,7 @@ class DBCheckpointStorageTests { @Before fun setUp() { LogHelper.setLevel(PersistentUniquenessProvider::class) - database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService) + database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), rigorousMock()) newCheckpointStorage() } diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt index 28026dd049..3f14110397 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt @@ -20,7 +20,6 @@ import net.corda.testing.* import net.corda.testing.node.MockServices 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.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -42,7 +41,7 @@ class DBTransactionStorageTests { fun setUp() { LogHelper.setLevel(PersistentUniquenessProvider::class) val dataSourceProps = makeTestDataSourceProperties() - database = configureDatabase(dataSourceProps, makeTestDatabaseProperties(), ::makeTestIdentityService) + database = configureDatabase(dataSourceProps, makeTestDatabaseProperties(), rigorousMock()) database.transaction { services = object : MockServices(BOB_KEY) { override val vaultService: VaultServiceInternal 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 a7851fc154..dd5a051229 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 @@ -85,7 +85,7 @@ class HibernateConfigurationTest { notaryServices = MockServices(cordappPackages, DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) val dataSourceProps = makeTestDataSourceProperties() val defaultDatabaseProperties = makeTestDatabaseProperties() - database = configureDatabase(dataSourceProps, defaultDatabaseProperties, ::makeTestIdentityService) + database = configureDatabase(dataSourceProps, defaultDatabaseProperties, makeTestIdentityService()) database.transaction { hibernateConfig = database.hibernateConfig // `consumeCash` expects we can self-notarise transactions diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt index 003ba0f33e..5d898383e9 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt @@ -19,7 +19,7 @@ import net.corda.node.utilities.configureDatabase import net.corda.testing.LogHelper 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 net.corda.testing.rigorousMock import org.junit.After import org.junit.Before import org.junit.Ignore @@ -44,7 +44,7 @@ class NodeAttachmentStorageTest { LogHelper.setLevel(PersistentUniquenessProvider::class) val dataSourceProperties = makeTestDataSourceProperties() - database = configureDatabase(dataSourceProperties, makeTestDatabaseProperties(), ::makeTestIdentityService) + database = configureDatabase(dataSourceProperties, makeTestDatabaseProperties(), rigorousMock()) fs = Jimfs.newFileSystem(Configuration.unix()) } diff --git a/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt b/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt index 30d877ab93..20dd2149fe 100644 --- a/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt @@ -18,7 +18,7 @@ import net.corda.testing.MEGA_CORP import net.corda.testing.contracts.DummyContract 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 net.corda.testing.rigorousMock import org.junit.After import org.junit.Before import org.junit.Test @@ -68,7 +68,7 @@ class HibernateObserverTests { return parent } } - val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService, schemaService) + val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), rigorousMock(), schemaService) HibernateObserver.install(rawUpdatesPublisher, database.hibernateConfig) database.transaction { rawUpdatesPublisher.onNext(Vault.Update(emptySet(), setOf(StateAndRef(TransactionState(TestState(), DummyContract.PROGRAM_ID, MEGA_CORP), StateRef(SecureHash.sha256("dummy"), 0))))) 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 9de048310e..8177cee077 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 @@ -18,7 +18,7 @@ import net.corda.testing.SerializationEnvironmentRule 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 net.corda.testing.rigorousMock import org.junit.* import java.util.concurrent.CompletableFuture import kotlin.test.assertEquals @@ -86,7 +86,7 @@ class DistributedImmutableMapTests { private fun createReplica(myAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): CompletableFuture { val storage = Storage.builder().withStorageLevel(StorageLevel.MEMORY).build() val address = Address(myAddress.host, myAddress.port) - val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties("serverNameTablePrefix", "PORT_${myAddress.port}_"), ::makeTestIdentityService) + val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties("serverNameTablePrefix", "PORT_${myAddress.port}_"), rigorousMock()) databases.add(database) val stateMachineFactory = { DistributedImmutableMap(database, RaftUniquenessProvider.Companion::createMap) } diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt index 0bb0bfeb2c..7f50aebe1b 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt @@ -7,7 +7,6 @@ import net.corda.node.utilities.configureDatabase import net.corda.testing.* 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 @@ -27,7 +26,7 @@ class PersistentUniquenessProviderTests { @Before fun setUp() { LogHelper.setLevel(PersistentUniquenessProvider::class) - database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService) + database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), rigorousMock()) } @After 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 cd2d87fd0e..3e64120083 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 @@ -93,7 +93,7 @@ class VaultQueryTests { @Ignore @Test fun createPersistentTestDb() { - val database = configureDatabase(makePersistentDataSourceProperties(), makeTestDatabaseProperties(), { identitySvc }) + val database = configureDatabase(makePersistentDataSourceProperties(), makeTestDatabaseProperties(), identitySvc) setUpDb(database, 5000) database.close() diff --git a/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt b/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt index e7b9ca1623..b53f9a5f31 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt @@ -5,7 +5,7 @@ import net.corda.core.internal.bufferUntilSubscribed import net.corda.core.internal.tee 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 net.corda.testing.rigorousMock import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Test @@ -21,7 +21,7 @@ class ObservablesTests { val toBeClosed = mutableListOf() fun createDatabase(): CordaPersistence { - val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService) + val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), rigorousMock()) toBeClosed += database return database } 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 a5176e80fd..b34acb9a74 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 @@ -23,7 +23,6 @@ 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 -import net.corda.testing.node.MockServices.Companion.makeTestIdentityService import net.corda.testing.node.createMockCordaService import org.junit.After import org.junit.Assert.* @@ -69,7 +68,7 @@ class NodeInterestRatesTest { @Before fun setUp() { - database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService) + database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), rigorousMock()) database.transaction { oracle = createMockCordaService(services, NodeInterestRates::Oracle) oracle.knownFixes = TEST_DATA 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 f10b8f9e5e..5db9f55572 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 @@ -305,9 +305,11 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete override val serializationWhitelists: List get() = testSerializationWhitelists private var dbCloser: (() -> Any?)? = null - override fun initialiseDatabasePersistence(schemaService: SchemaService, insideTransaction: (CordaPersistence) -> T) = super.initialiseDatabasePersistence(schemaService) { database -> - dbCloser = database::close - insideTransaction(database) + override fun initialiseDatabasePersistence(schemaService: SchemaService, identityService: IdentityService, insideTransaction: (CordaPersistence) -> T): T { + return super.initialiseDatabasePersistence(schemaService, identityService) { database -> + dbCloser = database::close + insideTransaction(database) + } } fun disableDBCloseOnStop() { 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 696472bb9e..9d60a97d3d 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 @@ -107,9 +107,9 @@ open class MockServices( */ @JvmStatic fun makeTestDatabaseAndMockServices(keys: List = listOf(MEGA_CORP_KEY), - createIdentityService: () -> IdentityService = { makeTestIdentityService() }, + identityService: IdentityService = makeTestIdentityService(), cordappPackages: List = emptyList()): Pair - = makeTestDatabaseAndMockServices(keys, createIdentityService, cordappPackages, MEGA_CORP.name) + = makeTestDatabaseAndMockServices(keys, identityService, cordappPackages, MEGA_CORP.name) /** * Makes database and mock services appropriate for unit tests. @@ -120,17 +120,16 @@ open class MockServices( */ @JvmStatic fun makeTestDatabaseAndMockServices(keys: List = listOf(MEGA_CORP_KEY), - createIdentityService: () -> IdentityService = { makeTestIdentityService() }, + identityService: IdentityService = makeTestIdentityService(), cordappPackages: List = emptyList(), initialIdentityName: CordaX500Name): Pair { val cordappLoader = CordappLoader.createWithTestPackages(cordappPackages) val dataSourceProps = makeTestDataSourceProperties() val databaseProperties = makeTestDatabaseProperties() - val identityServiceRef: IdentityService by lazy { createIdentityService() } - val database = configureDatabase(dataSourceProps, databaseProperties, { identityServiceRef }, NodeSchemaService(cordappLoader)) + val database = configureDatabase(dataSourceProps, databaseProperties, identityService, NodeSchemaService(cordappLoader)) val mockService = database.transaction { object : MockServices(cordappLoader, initialIdentityName = initialIdentityName, keys = *(keys.toTypedArray())) { - override val identityService: IdentityService = database.transaction { identityServiceRef } + override val identityService get() = identityService override val vaultService: VaultServiceInternal = makeVaultService(database.hibernateConfig) override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { From c4b333c50c92a107eb84c55c098a7822432bbe5d Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Mon, 27 Nov 2017 13:36:52 +0000 Subject: [PATCH 11/13] Updates permissioning docs to address RGB review comments. --- docs/source/permissioning.rst | 117 +++++++++++++++++++++------------- 1 file changed, 71 insertions(+), 46 deletions(-) diff --git a/docs/source/permissioning.rst b/docs/source/permissioning.rst index 13d889cc43..dcffd0bb49 100644 --- a/docs/source/permissioning.rst +++ b/docs/source/permissioning.rst @@ -14,11 +14,28 @@ In development mode (i.e. when ``devMode = true``, see :doc:`corda-configuration pre-configured keystores are used if the required keystores do not exist. This ensures that developers can get the nodes working as quickly as possible. -However, these pre-configured keystores are not secure. For a real network, you need to create a certificate authority -that will be used in the creation of these keystores for each node joining the network. +However, these pre-configured keystores are not secure. Production deployments require a secure certificate authority. +Most production deployments will use an existing certificate authority or construct one using software that will be +made available in the coming months. Until then, the documentation below can be used to create your own certificate +authority. -Creating the network keypairs and certificates ----------------------------------------------- +Network structure +----------------- +A Corda network has three types of certificate authorities (CAs): + +* The **root network CA** +* The **intermediate network CA** + + * The intermediate network CA is used instead of the root network CA for day-to-day + key signing to reduce the risk of the root network CA's private key being compromised + +* The **node CAs** + + * Each node serves as its own CA in issuing the child certificates that it uses to sign its identity + keys, anonymous keys and TLS certificates + +Keypair and certificate formats +------------------------------- You can use any standard key tools or Corda's ``X509Utilities`` (which uses Bouncy Castle) to create the required public/private keypairs and certificates. The keypairs and certificates should obey the following restrictions: @@ -28,72 +45,80 @@ public/private keypairs and certificates. The keypairs and certificates should o * The TLS certificates must follow the `TLS v1.2 standard `_ -Creating the node certificate authority -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The node certificate authority is used to sign the node identity certificates. An intermediate CA is used instead of -the root CA for day-to-day key signing to reduce the risk of the root CA's private key being compromised. +Creating the root and intermediate network CAs +---------------------------------------------- -Creating the root CA's keystore and truststore -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Creating the root network CA's keystore and truststore +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1. Create a new keypair - * This will be used as the root CA's keypair + * This will be used as the root network CA's keypair 2. Create a self-signed certificate for the keypair. The basic constraints extension must be set to ``true`` - * This will be used as the root CA's certificate + * This will be used as the root network CA's certificate -3. Store the root CA's keypair and certificate in a keystore for later use +3. Create a new keystore and store the root network CA's keypair and certificate in it for later use -4. Store the root CA's certificate in a Java keystore named ``truststore.jks`` using the alias ``cordarootca`` + * This keystore will be used by the root network CA to sign the intermediate network CA's certificate -.. warning:: The root CA's private key should be protected and kept safe. +4. Create a new Java keystore named ``truststore.jks`` and store the root network CA's certificate in it using the + alias ``cordarootca`` -Creating the intermediate CA's keystore -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * This keystore will be provisioned to the individual nodes later + +.. warning:: The root network CA's private key should be protected and kept safe. + +Creating the intermediate network CA's keystore +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1. Create a new keypair - * This will be used as the intermediate CA's keypair + * This will be used as the intermediate network CA's keypair -2. Obtain a certificate for the keypair signed with the root CA key. The basic constraints extension must be set to - ``true`` - - * This will be used as the intermediate CA's certificate - -3. Store the intermediate CA's keypair and certificate chain (i.e. the intermediate CA certificate *and* the root CA - certificate) in a keystore for later use - -Creating the node CA keystores and TLS keystores -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Each node serves as its own "node CA" in issuing the child certificates that it uses to sign its identity keys, -anonymous keys and TLS certificates. - -Creating the node CA keystores -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -1. On each node, create a new keypair - -2. Obtain a certificate for the keypair signed with the intermediate CA key. The basic constraints extension must be +2. Obtain a certificate for the keypair signed with the root network CA key. The basic constraints extension must be set to ``true`` -3. Store the keypair in a Java keystore named ``nodekeystore.jks`` using the alias ``cordaclientca`` + * This will be used as the intermediate network CA's certificate + +3. Create a new keystore and store the intermediate network CA's keypair and certificate chain + (i.e. the intermediate network CA certificate *and* the root network CA certificate) in it for later use + + * This keystore will be used by the intermediate network CA to sign the nodes' identity certificates + +Creating the node CA keystores and TLS keystores +------------------------------------------------ + +Creating the node CA keystores +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. For each node, create a new keypair + +2. Obtain a certificate for the keypair signed with the intermediate network CA key. The basic constraints extension must be + set to ``true`` + +3. Create a new Java keystore named ``nodekeystore.jks`` and store the keypair in it using the alias ``cordaclientca`` + + * The node will store this keystore locally to sign its identity keys and anonymous keys Creating the node TLS keystores -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -1. On each node, create a new keypair +1. For each node, create a new keypair 2. Create a certificate for the keypair signed with the node CA key. The basic constraints extension must be set to ``false`` -3. Store the key and certificates in a Java keystore named ``sslkeystore.jks`` using the alias ``cordaclienttls`` +3. Create a new Java keystore named ``sslkeystore.jks`` and store the key and certificates in it using the alias + ``cordaclienttls`` + + * The node will store this keystore locally to sign its TLS certificates Installing the certificates on the nodes -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -For each node: +---------------------------------------- +For each node, copy the following files to the node's certificate directory (``/certificates/``): -1. Copy the node's ``nodekeystore.jks`` and ``sslkeystore.jks`` keystores to the node's certificate directory - -2. Copy the ``truststore.jks`` keystore created by the root CA to the node's certificate directory \ No newline at end of file +1. The node's ``nodekeystore.jks`` keystore +2. The node's ``sslkeystore.jks`` keystore +3. The root network CA's ``truststore.jks`` keystore From 1705df4d1f85917b9cc7b1d835b9dc8563059ee3 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Sat, 25 Nov 2017 15:55:31 +0000 Subject: [PATCH 12/13] Made the database config option typesafe, rather than relying on String properties --- .idea/compiler.xml | 4 +- .../net/corda/core/internal/InternalUtils.kt | 2 +- docs/source/changelog.rst | 3 ++ docs/source/corda-configuration-file.rst | 7 ++++ .../corda/nodeapi/config/ConfigUtilities.kt | 8 +++- .../corda/nodeapi/config/ConfigParsingTest.kt | 9 ++++ .../node/services/config/NodeConfiguration.kt | 27 ++++++++++-- .../persistence/HibernateConfiguration.kt | 20 ++++----- .../corda/node/utilities/CordaPersistence.kt | 42 +++++++------------ node/src/main/resources/reference.conf | 2 +- .../net/corda/node/InteractiveShellTest.kt | 6 +-- .../config/NodeConfigurationImplTest.kt | 4 +- .../events/NodeSchedulerServiceTest.kt | 10 +++-- .../messaging/ArtemisMessagingTests.kt | 4 +- .../persistence/DBCheckpointStorageTests.kt | 11 ++--- .../persistence/DBTransactionStorageTests.kt | 13 +++--- .../persistence/HibernateConfigurationTest.kt | 5 +-- .../persistence/NodeAttachmentStorageTest.kt | 14 +++---- .../services/schema/HibernateObserverTests.kt | 9 ++-- .../DistributedImmutableMapTests.kt | 14 ++++--- .../PersistentUniquenessProviderTests.kt | 11 ++--- .../node/services/vault/VaultQueryTests.kt | 29 +++++++------ .../corda/node/utilities/ObservablesTests.kt | 8 ++-- .../corda/irs/api/NodeInterestRatesTest.kt | 4 +- .../kotlin/net/corda/testing/NodeTestUtils.kt | 8 ++-- .../net/corda/testing/node/MockServices.kt | 20 +-------- 26 files changed, 155 insertions(+), 139 deletions(-) diff --git a/.idea/compiler.xml b/.idea/compiler.xml index e754504179..d5523e52b5 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -43,8 +43,6 @@ - - @@ -59,6 +57,8 @@ + + diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 562c3f916d..63582c164b 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -304,6 +304,6 @@ fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serial fun TransactionBuilder.toLedgerTransaction(services: ServiceHub, serializationContext: SerializationContext) = toLedgerTransactionWithContext(services, serializationContext) /** Convenience method to get the package name of a class literal. */ -val KClass<*>.packageName get() = java.`package`.name +val KClass<*>.packageName: String get() = java.`package`.name fun URL.openHttpConnection(): HttpURLConnection = openConnection() as HttpURLConnection diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index a8986d15eb..8404bd64c0 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -91,6 +91,9 @@ UNRELEASED * ``waitForAllNodesToFinish()`` method in ``DriverDSLExposedInterface`` has instead become a parameter on driver creation. +* ``database.transactionIsolationLevel`` values now follow the ``java.sql.Connection`` int constants but without the + "TRANSACTION_" prefix, i.e. "NONE", "READ_UNCOMMITTED", etc. + .. _changelog_v1: Release 1.0 diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index 0531431cc5..3d346683ff 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -68,6 +68,13 @@ path to the node's base directory. .. note:: Longer term these keys will be managed in secure hardware devices. +:database: Database configuration: + + :initDatabase: Boolean on whether to initialise the database or just validate the schema. Defaults to true. + :serverNameTablePrefix: Prefix string to apply to all the database tables. The default is no prefix. + :transactionIsolationLevel: Transaction isolation level as defined by the ``TRANSACTION_`` constants in + ``java.sql.Connection``, but without the "TRANSACTION_" prefix. Defaults to REPEATABLE_READ. + :dataSourceProperties: This section is used to configure the jdbc connection and database driver used for the nodes persistence. Currently the defaults in ``/node/src/main/resources/reference.conf`` are as shown in the first example. This is currently the only configuration that has been tested, although in the future full support for other storage layers will be validated. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/config/ConfigUtilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/config/ConfigUtilities.kt index cd1118928c..19987de81c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/config/ConfigUtilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/config/ConfigUtilities.kt @@ -131,7 +131,13 @@ private fun Config.defaultToOldPath(property: KProperty<*>): String { private fun parseEnum(enumType: Class<*>, name: String): Enum<*> = enumBridge(uncheckedCast(enumType), name) // Any enum will do -private fun > enumBridge(clazz: Class, name: String): T = java.lang.Enum.valueOf(clazz, name) +private fun > enumBridge(clazz: Class, name: String): T { + try { + return java.lang.Enum.valueOf(clazz, name) + } catch (e: IllegalArgumentException) { + throw IllegalArgumentException("$name is not one of { ${clazz.enumConstants.joinToString()} }") + } +} /** * Convert the receiver object into a [Config]. This does the inverse action of [parseAs]. diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/config/ConfigParsingTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/config/ConfigParsingTest.kt index 452e25ce78..5efdeaf439 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/config/ConfigParsingTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/config/ConfigParsingTest.kt @@ -8,6 +8,7 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test import java.net.URL import java.nio.file.Path @@ -47,6 +48,14 @@ class ConfigParsingTest { testPropertyType(TestEnum.Value2, TestEnum.Value1, valuesToString = true) } + @Test + fun `unknown Enum`() { + val config = config("value" to "UnknownValue") + assertThatThrownBy { config.parseAs() } + .hasMessageContaining(TestEnum.Value1.name) + .hasMessageContaining(TestEnum.Value2.name) + } + @Test fun `LocalDate`() { testPropertyType(LocalDate.now(), LocalDate.now().plusDays(1), valuesToString = true) 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 607f3927b8..aecd630021 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 @@ -12,8 +12,6 @@ import java.net.URL import java.nio.file.Path import java.util.* -data class DevModeOptions(val disableCheckpointChecker: Boolean = false) - interface NodeConfiguration : NodeSSLConfiguration { // myLegalName should be only used in the initial network registration, we should use the name from the certificate instead of this. // TODO: Remove this so we don't accidentally use this identity in the code? @@ -21,7 +19,7 @@ interface NodeConfiguration : NodeSSLConfiguration { val emailAddress: String val exportJMXto: String val dataSourceProperties: Properties - val database: Properties? + val database: DatabaseConfig val rpcUsers: List val devMode: Boolean val devModeOptions: DevModeOptions? @@ -42,6 +40,27 @@ interface NodeConfiguration : NodeSSLConfiguration { val sshd: SSHDConfiguration? } +data class DevModeOptions(val disableCheckpointChecker: Boolean = false) + +data class DatabaseConfig( + val initDatabase: Boolean = true, + val serverNameTablePrefix: String = "", + val transactionIsolationLevel: TransactionIsolationLevel = TransactionIsolationLevel.REPEATABLE_READ +) + +enum class TransactionIsolationLevel { + NONE, + READ_UNCOMMITTED, + READ_COMMITTED, + REPEATABLE_READ, + SERIALIZABLE; + + /** + * The JDBC constant value of the same name but with prefixed with TRANSACTION_ defined in [java.sql.Connection]. + */ + val jdbcValue: Int = java.sql.Connection::class.java.getField("TRANSACTION_$name").get(null) as Int +} + fun NodeConfiguration.shouldCheckCheckpoints(): Boolean { return this.devMode && this.devModeOptions?.disableCheckpointChecker != true } @@ -89,7 +108,7 @@ data class NodeConfigurationImpl( override val keyStorePassword: String, override val trustStorePassword: String, override val dataSourceProperties: Properties, - override val database: Properties?, + override val database: DatabaseConfig = DatabaseConfig(), override val compatibilityZoneURL: URL? = null, override val rpcUsers: List, override val verifierType: VerifierType, 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 1b205b41c5..d48c301301 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 @@ -6,8 +6,8 @@ import net.corda.core.schemas.MappedSchema import net.corda.core.utilities.contextLogger import net.corda.core.utilities.toHexString import net.corda.node.services.api.SchemaService +import net.corda.node.services.config.DatabaseConfig import net.corda.node.utilities.DatabaseTransactionManager -import net.corda.node.utilities.parserTransactionIsolationLevel import org.hibernate.SessionFactory import org.hibernate.boot.MetadataSources import org.hibernate.boot.model.naming.Identifier @@ -23,10 +23,9 @@ 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 -class HibernateConfiguration(val schemaService: SchemaService, private val databaseProperties: Properties, private val identityService: IdentityService) { +class HibernateConfiguration(val schemaService: SchemaService, private val databaseConfig: DatabaseConfig, private val identityService: IdentityService) { companion object { private val logger = contextLogger() } @@ -34,7 +33,6 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab // TODO: make this a guava cache or similar to limit ability for this to grow forever. private val sessionFactories = ConcurrentHashMap, SessionFactory>() - private val transactionIsolationLevel = parserTransactionIsolationLevel(databaseProperties.getProperty("transactionIsolationLevel") ?: "") val sessionFactoryForRegisteredSchemas = schemaService.schemaOptions.keys.let { logger.info("Init HibernateConfiguration for schemas: $it") // Register the AbstractPartyDescriptor so Hibernate doesn't warn when encountering AbstractParty. Unfortunately @@ -56,16 +54,16 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab // necessarily remain and would likely be replaced by something like Liquibase. For now it is very convenient though. // TODO: replace auto schema generation as it isn't intended for production use, according to Hibernate docs. val config = Configuration(metadataSources).setProperty("hibernate.connection.provider_class", NodeDatabaseConnectionProvider::class.java.name) - .setProperty("hibernate.hbm2ddl.auto", if (databaseProperties.getProperty("initDatabase", "true") == "true") "update" else "validate") + .setProperty("hibernate.hbm2ddl.auto", if (databaseConfig.initDatabase) "update" else "validate") .setProperty("hibernate.format_sql", "true") - .setProperty("hibernate.connection.isolation", transactionIsolationLevel.toString()) + .setProperty("hibernate.connection.isolation", databaseConfig.transactionIsolationLevel.jdbcValue.toString()) schemas.forEach { schema -> // TODO: require mechanism to set schemaOptions (databaseSchema, tablePrefix) which are not global to session schema.mappedTypes.forEach { config.addAnnotatedClass(it) } } - val sessionFactory = buildSessionFactory(config, metadataSources, databaseProperties.getProperty("serverNameTablePrefix", "")) + val sessionFactory = buildSessionFactory(config, metadataSources, databaseConfig.serverNameTablePrefix) logger.info("Created session factory for schemas: $schemas") return sessionFactory } @@ -132,11 +130,13 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab private val LOG_SIZE_LIMIT = 1024 override fun extractLoggableRepresentation(value: ByteArray?): String { - return if (value == null) super.extractLoggableRepresentation(value) else { + return if (value == null) { + super.extractLoggableRepresentation(value) + } else { if (value.size <= LOG_SIZE_LIMIT) { - return "[size=${value.size}, value=${value.toHexString()}]" + "[size=${value.size}, value=${value.toHexString()}]" } else { - return "[size=${value.size}, value=${value.copyOfRange(0, LOG_SIZE_LIMIT).toHexString()}...truncated...]" + "[size=${value.size}, value=${value.copyOfRange(0, LOG_SIZE_LIMIT).toHexString()}...truncated...]" } } } diff --git a/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt b/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt index c02f5a4416..6e9d7995d6 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt @@ -4,6 +4,7 @@ import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariDataSource import net.corda.core.node.services.IdentityService import net.corda.node.services.api.SchemaService +import net.corda.node.services.config.DatabaseConfig import net.corda.node.services.persistence.HibernateConfiguration import net.corda.node.services.schema.NodeSchemaService import rx.Observable @@ -21,20 +22,23 @@ import java.util.concurrent.CopyOnWriteArrayList const val NODE_DATABASE_PREFIX = "node_" //HikariDataSource implements Closeable which allows CordaPersistence to be Closeable -class CordaPersistence(var dataSource: HikariDataSource, private val schemaService: SchemaService, - private val identityService: IdentityService, databaseProperties: Properties) : Closeable { - var transactionIsolationLevel = parserTransactionIsolationLevel(databaseProperties.getProperty("transactionIsolationLevel")) - +class CordaPersistence( + val dataSource: HikariDataSource, + private val schemaService: SchemaService, + private val identityService: IdentityService, + databaseConfig: DatabaseConfig +) : Closeable { + val transactionIsolationLevel = databaseConfig.transactionIsolationLevel.jdbcValue val hibernateConfig: HibernateConfiguration by lazy { transaction { - HibernateConfiguration(schemaService, databaseProperties, identityService) + HibernateConfiguration(schemaService, databaseConfig, identityService) } } val entityManagerFactory get() = hibernateConfig.sessionFactoryForRegisteredSchemas companion object { - fun connect(dataSource: HikariDataSource, schemaService: SchemaService, identityService: IdentityService, databaseProperties: Properties): CordaPersistence { - return CordaPersistence(dataSource, schemaService, identityService, databaseProperties).apply { + fun connect(dataSource: HikariDataSource, schemaService: SchemaService, identityService: IdentityService, databaseConfig: DatabaseConfig): CordaPersistence { + return CordaPersistence(dataSource, schemaService, identityService, databaseConfig).apply { DatabaseTransactionManager(this) } } @@ -53,9 +57,7 @@ class CordaPersistence(var dataSource: HikariDataSource, private val schemaServi /** * Creates an instance of [DatabaseTransaction], with the transaction isolation level specified at the creation time. */ - fun createTransaction(): DatabaseTransaction { - return createTransaction(transactionIsolationLevel) - } + fun createTransaction(): DatabaseTransaction = createTransaction(transactionIsolationLevel) fun createSession(): Connection { // We need to set the database for the current [Thread] or [Fiber] here as some tests share threads across databases. @@ -78,9 +80,7 @@ class CordaPersistence(var dataSource: HikariDataSource, private val schemaServi * Executes given statement in the scope of transaction with the transaction level specified at the creation time. * @param statement to be executed in the scope of this transaction. */ - fun transaction(statement: DatabaseTransaction.() -> T): T { - return transaction(transactionIsolationLevel, statement) - } + fun transaction(statement: DatabaseTransaction.() -> T): T = transaction(transactionIsolationLevel, statement) private fun transaction(transactionIsolation: Int, repetitionAttempts: Int, statement: DatabaseTransaction.() -> T): T { val outer = DatabaseTransactionManager.currentOrNull() @@ -120,10 +120,10 @@ class CordaPersistence(var dataSource: HikariDataSource, private val schemaServi } } -fun configureDatabase(dataSourceProperties: Properties, databaseProperties: Properties?, identityService: IdentityService, schemaService: SchemaService = NodeSchemaService(null)): CordaPersistence { +fun configureDatabase(dataSourceProperties: Properties, databaseConfig: DatabaseConfig, identityService: IdentityService, schemaService: SchemaService = NodeSchemaService(null)): CordaPersistence { val config = HikariConfig(dataSourceProperties) val dataSource = HikariDataSource(config) - val persistence = CordaPersistence.connect(dataSource, schemaService, identityService, databaseProperties ?: Properties()) + val persistence = CordaPersistence.connect(dataSource, schemaService, identityService, databaseConfig) // Check not in read-only mode. persistence.transaction { persistence.dataSource.connection.use { @@ -216,15 +216,3 @@ fun rx.Observable.wrapWithDatabaseTransaction(db: CordaPersistence? } } } - -fun parserTransactionIsolationLevel(property: String?): Int = - when (property) { - "none" -> Connection.TRANSACTION_NONE - "readUncommitted" -> Connection.TRANSACTION_READ_UNCOMMITTED - "readCommitted" -> Connection.TRANSACTION_READ_COMMITTED - "repeatableRead" -> Connection.TRANSACTION_REPEATABLE_READ - "serializable" -> Connection.TRANSACTION_SERIALIZABLE - else -> { - Connection.TRANSACTION_REPEATABLE_READ - } - } diff --git a/node/src/main/resources/reference.conf b/node/src/main/resources/reference.conf index ef16577ae1..ac62cce4b3 100644 --- a/node/src/main/resources/reference.conf +++ b/node/src/main/resources/reference.conf @@ -10,7 +10,7 @@ dataSourceProperties = { "dataSource.password" = "" } database = { - transactionIsolationLevel = "repeatableRead" + transactionIsolationLevel = "REPEATABLE_READ" initDatabase = true } devMode = true diff --git a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt index f8168afeb6..93a2d39dc5 100644 --- a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt +++ b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt @@ -7,10 +7,10 @@ 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.messaging.FlowProgressHandleImpl import net.corda.core.utilities.ProgressTracker +import net.corda.node.services.config.DatabaseConfig import net.corda.node.services.identity.InMemoryIdentityService import net.corda.node.shell.InteractiveShell import net.corda.node.utilities.configureDatabase @@ -29,7 +29,7 @@ import kotlin.test.assertEquals class InteractiveShellTest { @Before fun setup() { - InteractiveShell.database = configureDatabase(MockServices.makeTestDataSourceProperties(), MockServices.makeTestDatabaseProperties(), rigorousMock()) + InteractiveShell.database = configureDatabase(MockServices.makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) } @After @@ -92,6 +92,4 @@ class InteractiveShellTest { @Test fun party() = check("party: \"${MEGA_CORP.name}\"", MEGA_CORP.name.toString()) - - class DummyFSM(override val logic: FlowA) : FlowStateMachine by rigorousMock() } diff --git a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt index bca85ad551..6af672ceae 100644 --- a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt @@ -3,10 +3,8 @@ package net.corda.node.services.config import net.corda.core.utilities.NetworkHostAndPort import net.corda.testing.ALICE import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties -import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test -import java.net.URL import java.nio.file.Paths import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -40,7 +38,7 @@ class NodeConfigurationImplTest { keyStorePassword = "cordacadevpass", trustStorePassword = "trustpass", dataSourceProperties = makeTestDataSourceProperties(ALICE.name.organisation), - database = makeTestDatabaseProperties(), + database = DatabaseConfig(), rpcUsers = emptyList(), verifierType = VerifierType.InMemory, useHTTPS = false, 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 e33accb68e..92127f94f9 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 @@ -23,6 +23,7 @@ import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.node.services.api.MonitoringService import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.config.DatabaseConfig import net.corda.node.services.identity.InMemoryIdentityService import net.corda.node.services.network.NetworkMapCacheImpl import net.corda.node.services.persistence.DBCheckpointStorage @@ -37,9 +38,11 @@ import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.node.* import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties -import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties import org.assertj.core.api.Assertions.assertThat -import org.junit.* +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test import java.nio.file.Paths import java.security.PublicKey import java.time.Clock @@ -90,8 +93,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { smmHasRemovedAllFlows = CountDownLatch(1) calls = 0 val dataSourceProps = makeTestDataSourceProperties() - val databaseProperties = makeTestDatabaseProperties() - database = configureDatabase(dataSourceProps, databaseProperties, rigorousMock()) + database = configureDatabase(dataSourceProps, DatabaseConfig(), rigorousMock()) val identityService = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT) kms = MockKeyManagementService(identityService, ALICE_KEY) val configuration = testNodeConfiguration(Paths.get("."), CordaX500Name("Alice", "London", "GB")) 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 41a96f7f58..150234674a 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 @@ -7,6 +7,7 @@ import net.corda.core.internal.concurrent.openFuture import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.RPCUserService import net.corda.node.services.RPCUserServiceImpl +import net.corda.node.services.config.DatabaseConfig import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.configureWithDevSSLCertificate import net.corda.node.services.network.NetworkMapCacheImpl @@ -18,7 +19,6 @@ import net.corda.node.utilities.configureDatabase import net.corda.testing.* import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties -import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.After @@ -67,7 +67,7 @@ class ArtemisMessagingTests { baseDirectory = baseDirectory, myLegalName = ALICE.name) LogHelper.setLevel(PersistentUniquenessProvider::class) - database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), rigorousMock()) + database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) networkMapRegistrationFuture = doneFuture(Unit) networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database), rigorousMock()) } diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt index 8b97d53de6..52a91ef87f 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt @@ -4,13 +4,13 @@ import com.google.common.primitives.Ints import net.corda.core.serialization.SerializedBytes import net.corda.node.services.api.Checkpoint import net.corda.node.services.api.CheckpointStorage +import net.corda.node.services.config.DatabaseConfig import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase import net.corda.testing.LogHelper -import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties -import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties import net.corda.testing.SerializationEnvironmentRule +import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.rigorousMock import org.assertj.core.api.Assertions.assertThat import org.junit.After @@ -31,13 +31,14 @@ class DBCheckpointStorageTests { @Rule @JvmField val testSerialization = SerializationEnvironmentRule() - lateinit var checkpointStorage: DBCheckpointStorage - lateinit var database: CordaPersistence + + private lateinit var checkpointStorage: DBCheckpointStorage + private lateinit var database: CordaPersistence @Before fun setUp() { LogHelper.setLevel(PersistentUniquenessProvider::class) - database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), rigorousMock()) + database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) newCheckpointStorage() } diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt index 3f14110397..fcd85e05c6 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt @@ -6,11 +6,11 @@ import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SignatureMetadata import net.corda.core.crypto.TransactionSignature import net.corda.core.node.StatesToRecord -import net.corda.core.node.services.VaultService import net.corda.core.toFuture import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction import net.corda.node.services.api.VaultServiceInternal +import net.corda.node.services.config.DatabaseConfig import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.services.vault.NodeVaultService @@ -19,7 +19,6 @@ import net.corda.node.utilities.configureDatabase import net.corda.testing.* import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties -import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -32,16 +31,16 @@ class DBTransactionStorageTests { @Rule @JvmField val testSerialization = SerializationEnvironmentRule() - lateinit var database: CordaPersistence - lateinit var transactionStorage: DBTransactionStorage - lateinit var services: MockServices - val vault: VaultService get() = services.vaultService + + private lateinit var database: CordaPersistence + private lateinit var transactionStorage: DBTransactionStorage + private lateinit var services: MockServices @Before fun setUp() { LogHelper.setLevel(PersistentUniquenessProvider::class) val dataSourceProps = makeTestDataSourceProperties() - database = configureDatabase(dataSourceProps, makeTestDatabaseProperties(), rigorousMock()) + database = configureDatabase(dataSourceProps, DatabaseConfig(), rigorousMock()) database.transaction { services = object : MockServices(BOB_KEY) { override val vaultService: VaultServiceInternal 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 dd5a051229..1715bf6c23 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 @@ -25,6 +25,7 @@ import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.schemas.SampleCashSchemaV2 import net.corda.finance.schemas.SampleCashSchemaV3 import net.corda.finance.utils.sumCash +import net.corda.node.services.config.DatabaseConfig import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.vault.VaultSchemaV1 import net.corda.node.utilities.CordaPersistence @@ -36,7 +37,6 @@ import net.corda.testing.contracts.fillWithSomeTestDeals import net.corda.testing.contracts.fillWithSomeTestLinearStates import net.corda.testing.node.MockServices 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 net.corda.testing.schemas.DummyLinearStateSchemaV1 import net.corda.testing.schemas.DummyLinearStateSchemaV2 @@ -84,8 +84,7 @@ class HibernateConfigurationTest { 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 = configureDatabase(dataSourceProps, DatabaseConfig(), makeTestIdentityService()) database.transaction { hibernateConfig = database.hibernateConfig // `consumeCash` expects we can self-notarise transactions diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt index 5d898383e9..658be5b8c8 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt @@ -13,12 +13,12 @@ import net.corda.core.node.services.vault.AttachmentQueryCriteria import net.corda.core.node.services.vault.AttachmentSort import net.corda.core.node.services.vault.Builder import net.corda.core.node.services.vault.Sort +import net.corda.node.services.config.DatabaseConfig import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase import net.corda.testing.LogHelper import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties -import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties import net.corda.testing.rigorousMock import org.junit.After import org.junit.Before @@ -36,15 +36,15 @@ import kotlin.test.assertNull class NodeAttachmentStorageTest { // Use an in memory file system for testing attachment storage. - lateinit var fs: FileSystem - lateinit var database: CordaPersistence + private lateinit var fs: FileSystem + private lateinit var database: CordaPersistence @Before fun setUp() { LogHelper.setLevel(PersistentUniquenessProvider::class) val dataSourceProperties = makeTestDataSourceProperties() - database = configureDatabase(dataSourceProperties, makeTestDatabaseProperties(), rigorousMock()) + database = configureDatabase(dataSourceProperties, DatabaseConfig(), rigorousMock()) fs = Jimfs.newFileSystem(Configuration.unix()) } @@ -82,9 +82,9 @@ class NodeAttachmentStorageTest { @Test fun `metadata can be used to search`() { - val (jarA,hashA) = makeTestJar() - val (jarB,hashB) = makeTestJar(listOf(Pair("file","content"))) - val (jarC,hashC) = makeTestJar(listOf(Pair("magic_file","magic_content_puff"))) + val (jarA, _) = makeTestJar() + val (jarB, hashB) = makeTestJar(listOf(Pair("file","content"))) + val (jarC, hashC) = makeTestJar(listOf(Pair("magic_file","magic_content_puff"))) database.transaction { val storage = NodeAttachmentService(MetricRegistry()) diff --git a/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt b/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt index 20dd2149fe..8ba9fb8f49 100644 --- a/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt @@ -11,13 +11,13 @@ import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.schemas.QueryableState import net.corda.node.services.api.SchemaService +import net.corda.node.services.config.DatabaseConfig import net.corda.node.utilities.DatabaseTransactionManager import net.corda.node.utilities.configureDatabase import net.corda.testing.LogHelper import net.corda.testing.MEGA_CORP import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties -import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties import net.corda.testing.rigorousMock import org.junit.After import org.junit.Before @@ -25,9 +25,7 @@ import org.junit.Test import rx.subjects.PublishSubject import kotlin.test.assertEquals - class HibernateObserverTests { - @Before fun setUp() { LogHelper.setLevel(HibernateObserver::class) @@ -51,9 +49,8 @@ class HibernateObserverTests { get() = throw UnsupportedOperationException() } - // This method does not use back quotes for a nice name since it seems to kill the kotlin compiler. @Test - fun testChildObjectsArePersisted() { + fun `test child objects are persisted`() { val testSchema = TestSchema val rawUpdatesPublisher = PublishSubject.create>() val schemaService = object : SchemaService { @@ -68,7 +65,7 @@ class HibernateObserverTests { return parent } } - val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), rigorousMock(), schemaService) + val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock(), schemaService) HibernateObserver.install(rawUpdatesPublisher, database.hibernateConfig) database.transaction { rawUpdatesPublisher.onNext(Vault.Update(emptySet(), setOf(StateAndRef(TransactionState(TestState(), DummyContract.PROGRAM_ID, MEGA_CORP), StateRef(SecureHash.sha256("dummy"), 0))))) 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 8177cee077..4aff179a2f 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 @@ -10,16 +10,18 @@ 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.services.config.DatabaseConfig import net.corda.node.utilities.CordaPersistence -import net.corda.node.utilities.DatabaseTransaction import net.corda.node.utilities.configureDatabase import net.corda.testing.LogHelper import net.corda.testing.SerializationEnvironmentRule 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.rigorousMock -import org.junit.* +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test import java.util.concurrent.CompletableFuture import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -30,8 +32,8 @@ class DistributedImmutableMapTests { @Rule @JvmField val testSerialization = SerializationEnvironmentRule(true) - lateinit var cluster: List - lateinit var transaction: DatabaseTransaction + + private lateinit var cluster: List private val databases: MutableList = mutableListOf() @Before @@ -86,7 +88,7 @@ class DistributedImmutableMapTests { private fun createReplica(myAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): CompletableFuture { val storage = Storage.builder().withStorageLevel(StorageLevel.MEMORY).build() val address = Address(myAddress.host, myAddress.port) - val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties("serverNameTablePrefix", "PORT_${myAddress.port}_"), rigorousMock()) + val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(serverNameTablePrefix = "PORT_${myAddress.port}_"), rigorousMock()) databases.add(database) val stateMachineFactory = { DistributedImmutableMap(database, RaftUniquenessProvider.Companion::createMap) } diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt index 7f50aebe1b..4c15bb37e7 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt @@ -2,11 +2,11 @@ package net.corda.node.services.transactions import net.corda.core.crypto.SecureHash import net.corda.core.node.services.UniquenessException +import net.corda.node.services.config.DatabaseConfig import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase import net.corda.testing.* import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties -import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties import org.junit.After import org.junit.Before import org.junit.Rule @@ -18,15 +18,16 @@ class PersistentUniquenessProviderTests { @Rule @JvmField val testSerialization = SerializationEnvironmentRule() - val identity = MEGA_CORP - val txID = SecureHash.randomSHA256() - lateinit var database: CordaPersistence + private val identity = MEGA_CORP + private val txID = SecureHash.randomSHA256() + + private lateinit var database: CordaPersistence @Before fun setUp() { LogHelper.setLevel(PersistentUniquenessProvider::class) - database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), rigorousMock()) + database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) } @After 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 3e64120083..70629c705c 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 @@ -27,15 +27,14 @@ import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.schemas.CashSchemaV1.PersistentCashState import net.corda.finance.schemas.CommercialPaperSchemaV1 import net.corda.finance.schemas.SampleCashSchemaV3 +import net.corda.node.services.config.DatabaseConfig import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase import net.corda.testing.* 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.schemas.DummyLinearStateSchemaV1 -import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.* @@ -53,9 +52,16 @@ class VaultQueryTests { @Rule @JvmField val testSerialization = SerializationEnvironmentRule() - private val cordappPackages = setOf( - "net.corda.testing.contracts", "net.corda.finance.contracts", - CashSchemaV1::class.packageName, CommercialPaperSchemaV1::class.packageName, DummyLinearStateSchemaV1::class.packageName).toMutableList() + + @Rule + @JvmField + val expectedEx: ExpectedException = ExpectedException.none() + + private val cordappPackages = mutableListOf( + "net.corda.testing.contracts", + "net.corda.finance.contracts", + CashSchemaV1::class.packageName, + DummyLinearStateSchemaV1::class.packageName) private lateinit var services: MockServices private lateinit var notaryServices: MockServices private val vaultService: VaultService get() = services.vaultService @@ -93,7 +99,7 @@ class VaultQueryTests { @Ignore @Test fun createPersistentTestDb() { - val database = configureDatabase(makePersistentDataSourceProperties(), makeTestDatabaseProperties(), identitySvc) + val database = configureDatabase(makePersistentDataSourceProperties(), DatabaseConfig(), identitySvc) setUpDb(database, 5000) database.close() @@ -129,9 +135,6 @@ class VaultQueryTests { return props } - @get:Rule - val expectedEx = ExpectedException.none()!! - /** * Query API tests */ @@ -1597,8 +1600,8 @@ class VaultQueryTests { val result = vaultService.queryBy(criteria1) - Assertions.assertThat(result.states).hasSize(1) - Assertions.assertThat(result.statesMetadata).hasSize(1) + assertThat(result.states).hasSize(1) + assertThat(result.statesMetadata).hasSize(1) } } @@ -1642,8 +1645,8 @@ class VaultQueryTests { vaultService.queryBy(criteria1.and(criteria3).and(criteria2)) } - Assertions.assertThat(result.states).hasSize(1) - Assertions.assertThat(result.statesMetadata).hasSize(1) + assertThat(result.states).hasSize(1) + assertThat(result.statesMetadata).hasSize(1) } } diff --git a/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt b/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt index b53f9a5f31..85c7be0eb3 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt @@ -3,8 +3,8 @@ package net.corda.node.utilities import com.google.common.util.concurrent.SettableFuture import net.corda.core.internal.bufferUntilSubscribed import net.corda.core.internal.tee +import net.corda.node.services.config.DatabaseConfig import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties -import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties import net.corda.testing.rigorousMock import org.assertj.core.api.Assertions.assertThat import org.junit.After @@ -18,10 +18,10 @@ class ObservablesTests { private fun isInDatabaseTransaction(): Boolean = (DatabaseTransactionManager.currentOrNull() != null) - val toBeClosed = mutableListOf() + private val toBeClosed = mutableListOf() - fun createDatabase(): CordaPersistence { - val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), rigorousMock()) + private fun createDatabase(): CordaPersistence { + val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) toBeClosed += database return database } 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 b34acb9a74..de27d5517e 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 @@ -15,6 +15,7 @@ import net.corda.finance.contracts.FixOf import net.corda.finance.contracts.asset.CASH import net.corda.finance.contracts.asset.Cash import net.corda.irs.flows.RatesFixFlow +import net.corda.node.services.config.DatabaseConfig import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase import net.corda.testing.* @@ -22,7 +23,6 @@ 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 import net.corda.testing.node.createMockCordaService import org.junit.After import org.junit.Assert.* @@ -68,7 +68,7 @@ class NodeInterestRatesTest { @Before fun setUp() { - database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), rigorousMock()) + database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) database.transaction { oracle = createMockCordaService(services, NodeInterestRates::Oracle) oracle.knownFixes = TEST_DATA diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt index acd655123a..b76d47efa3 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt @@ -6,24 +6,24 @@ import com.nhaarman.mockito_kotlin.doCallRealMethod import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.context.Actor +import net.corda.core.context.AuthServiceId import net.corda.core.context.InvocationContext import net.corda.core.context.Origin -import net.corda.core.context.AuthServiceId import net.corda.core.flows.FlowLogic import net.corda.core.identity.CordaX500Name import net.corda.core.internal.FlowStateMachine import net.corda.core.node.ServiceHub import net.corda.core.transactions.TransactionBuilder -import net.corda.core.utilities.seconds import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.seconds import net.corda.node.services.api.StartedNodeServices import net.corda.node.services.config.CertChainPolicyConfig +import net.corda.node.services.config.DatabaseConfig import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.VerifierType import net.corda.nodeapi.User import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties -import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties import java.nio.file.Path /** @@ -64,7 +64,7 @@ fun testNodeConfiguration( doReturn(emptyList()).whenever(it).rpcUsers doReturn(null).whenever(it).notary doReturn(makeTestDataSourceProperties(myLegalName.organisation)).whenever(it).dataSourceProperties - doReturn(makeTestDatabaseProperties()).whenever(it).database + doReturn(DatabaseConfig()).whenever(it).database doReturn("").whenever(it).emailAddress doReturn("").whenever(it).exportJMXto doReturn(true).whenever(it).devMode 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 9d60a97d3d..1c88471207 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 @@ -21,6 +21,7 @@ import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage import net.corda.node.services.api.VaultServiceInternal import net.corda.node.services.api.WritableTransactionStorage +import net.corda.node.services.config.DatabaseConfig import net.corda.node.services.identity.InMemoryIdentityService import net.corda.node.services.keys.freshCertificate import net.corda.node.services.keys.getSigner @@ -76,22 +77,6 @@ open class MockServices( return props } - /** - * Make properties appropriate for creating a Database for unit tests. - * - * @param key (optional) key of a database property to be set. - * @param value (optional) value of a database property to be set. - */ - @JvmStatic - fun makeTestDatabaseProperties(key: String? = null, value: String? = null): Properties { - val props = Properties() - props.setProperty("transactionIsolationLevel", "repeatableRead") //for other possible values see net.corda.node.utilities.CordaPeristence.parserTransactionIsolationLevel(String) - if (key != null) { - props.setProperty(key, value) - } - return props - } - /** * Creates an instance of [InMemoryIdentityService] with [MOCK_IDENTITIES]. */ @@ -125,8 +110,7 @@ open class MockServices( initialIdentityName: CordaX500Name): Pair { val cordappLoader = CordappLoader.createWithTestPackages(cordappPackages) val dataSourceProps = makeTestDataSourceProperties() - val databaseProperties = makeTestDatabaseProperties() - val database = configureDatabase(dataSourceProps, databaseProperties, identityService, NodeSchemaService(cordappLoader)) + val database = configureDatabase(dataSourceProps, DatabaseConfig(), identityService, NodeSchemaService(cordappLoader)) val mockService = database.transaction { object : MockServices(cordappLoader, initialIdentityName = initialIdentityName, keys = *(keys.toTypedArray())) { override val identityService get() = identityService From 0e3713237b156b0c28c562dd029cee030036cf93 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Mon, 27 Nov 2017 15:08:52 +0000 Subject: [PATCH 13/13] rename table "link_nodeinfo_party" to "node_link_nodeinfo_party" (#2135) --- .../kotlin/net/corda/core/internal/schemas/NodeInfoSchema.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/kotlin/net/corda/core/internal/schemas/NodeInfoSchema.kt b/core/src/main/kotlin/net/corda/core/internal/schemas/NodeInfoSchema.kt index b2e73bb359..9cb84357b5 100644 --- a/core/src/main/kotlin/net/corda/core/internal/schemas/NodeInfoSchema.kt +++ b/core/src/main/kotlin/net/corda/core/internal/schemas/NodeInfoSchema.kt @@ -37,7 +37,7 @@ object NodeInfoSchemaV1 : MappedSchema( @Column(name = "legal_identities_certs") @ManyToMany(cascade = arrayOf(CascadeType.ALL)) - @JoinTable(name = "link_nodeinfo_party", + @JoinTable(name = "node_link_nodeinfo_party", joinColumns = arrayOf(JoinColumn(name = "node_info_id")), inverseJoinColumns = arrayOf(JoinColumn(name = "party_name"))) val legalIdentitiesAndCerts: List,