Merge remote-tracking branch 'open/master' into features/ENT-1153/merge_OS2ENT

# Conflicts:
#	.idea/compiler.xml
#	docs/source/changelog.rst
#	node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt
This commit is contained in:
Tudor Malene 2017-11-27 16:52:29 +00:00 committed by tudor.malene@gmail.com
commit e8d833c2fe
65 changed files with 952 additions and 563 deletions

2
.idea/compiler.xml generated
View File

@ -59,6 +59,8 @@
<module name="finance_test" target="1.8" />
<module name="gradle-plugins-cordform-common_main" target="1.8" />
<module name="gradle-plugins-cordform-common_test" target="1.8" />
<module name="gradle-plugins-cordapp_main" target="1.8" />
<module name="gradle-plugins-cordapp_test" target="1.8" />
<module name="graphs_main" target="1.8" />
<module name="graphs_test" target="1.8" />
<module name="intellij-plugin_main" target="1.8" />

View File

@ -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",

View File

@ -348,6 +348,12 @@ abstract class FlowLogic<out T> {
}
}
/**
* 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<Int, Int>? {
// TODO this is not threadsafe, needs an atomic get-step-and-subscribe
return progressTracker?.let {
@ -355,6 +361,12 @@ abstract class FlowLogic<out T> {
}
}
/**
* 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<Pair<Int,String>>, List<Pair<Int,String>>>? {
// TODO this is not threadsafe, needs an atomic get-step-and-subscribe
return progressTracker?.let {

View File

@ -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 " }

View File

@ -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

View File

@ -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<in T> {
companion object {
val legalNameRules: List<Rule<String>> = listOf(
val baseNameRules: List<Rule<String>> = 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<Rule<String>> = baseNameRules + listOf(
CapitalLetterRule(),
X500NameRule(),
MustHaveAtLeastTwoLettersRule()
)
}
@ -52,7 +76,7 @@ object LegalNameValidator {
private class UnicodeNormalizationRule : Rule<String>() {
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." }
}
}

View File

@ -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<DBPartyAndCertificate>,

View File

@ -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<A> : AutoCloseable {
/**
* The started state machine's ID.
*/
val id: StateMachineRunId
/**
* A [CordaFuture] of the flow's return value.
*/
val returnValue: CordaFuture<A>
/**
@ -25,15 +29,23 @@ interface FlowHandle<A> : 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<A> : FlowHandle<A> {
/**
* The stream of progress tracker events.
*/
val progress: Observable<String>
/**
* [DataFeed] of current step in the steps tree, see [ProgressTracker]
*/
val stepsTreeIndexFeed: DataFeed<Int, Int>?
/**
* [DataFeed] of current steps tree, see [ProgressTracker]
*/
val stepsTreeFeed: DataFeed<List<Pair<Int, String>>, List<Pair<Int, String>>>?
/**
* Use this function for flows whose returnValue and progress are not going to be used or tracked, so as to free up
* server resources.

View File

@ -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<Pair<Int, Step>> 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<Pair<Int, String>> get() = _allStepsLabels()
private var curChangeSubscription: Subscription? = null
@ -245,8 +250,14 @@ class ProgressTracker(vararg steps: Step) {
*/
val changes: Observable<Change> get() = _changes
/**
* An observable stream of changes to the [allStepsLabels]
*/
val stepsTreeChanges: Observable<List<Pair<Int,String>>> get() = _stepsTreeChanges
/**
* An observable stream of changes to the [stepsTreeIndex]
*/
val stepsTreeIndexChanges: Observable<Int> get() = _stepsTreeIndexChanges
/** Returns true if the progress tracker has ended, either by reaching the [DONE] step or prematurely with an error */

View File

@ -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")
}
}
}

View File

@ -98,7 +98,7 @@ class ProgressTrackerTest {
val allSteps = pt.allSteps
//capture notifications
// Capture notifications.
val stepsIndexNotifications = LinkedList<Int>()
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<Int>()
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<Int>()
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

View File

@ -10,4 +10,5 @@ CorDapps
building-against-master
corda-api
flow-cookbook
cheat-sheet
cheat-sheet
building-a-cordapp-samples

View File

@ -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 <https://www.corda.net/samples/>`_. In particular, new developers
should start with the :doc:`example CorDapp <tutorial-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 <https://github.com/corda/corda/issues>`_.

View File

@ -93,6 +93,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.
* Enterprise Corda only: Compatibility with SQL Server 2017 and SQL Azure databases.
* Enterprise Corda only: node configuration property ``database.schema`` and documented existing database properties.

View File

@ -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.

View File

@ -6,4 +6,5 @@ Corda networks
setting-up-a-corda-network
permissioning
versioning
network-map
versioning

View File

@ -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
------------

View File

@ -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.
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<NodeInfo>`` 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.

View File

@ -1,104 +1,124 @@
Network permissioning (Doorman)
===============================
Network Permissioning
=====================
The keystore located in ``<workspace>/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
``<workspace>/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 nodes identity keypairs and certificates
* ``sslkeystore.jks``, which stores the nodes 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. 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.
:emailAddress: e.g. "admin@company.com"
Network structure
-----------------
A Corda network has three types of certificate authorities (CAs):
:compatibilityZoneURL: Corda compatibility zone network management service root URL.
* The **root network CA**
* The **intermediate network CA**
A new pair of private and public keys generated by the Corda node will be used to create the request.
* 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 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 **node CAs**
.. 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.
* 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
This process only is needed when the node connects to the network for the first time, or when the certificate expires.
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:
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.
* The certificates must follow the `X.509 standard <https://tools.ietf.org/html/rfc5280>`_
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.
* We recommend X.509 v3 for forward compatibility
The normalize function performs the following transformations:
* The TLS certificates must follow the `TLS v1.2 standard <https://tools.ietf.org/html/rfc5246>`_
* Remove leading and trailing whitespaces.
Creating the root and intermediate network CAs
----------------------------------------------
* Replace multiple whitespaces with a single space.
Creating the root network CA's keystore and truststore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Normalize the string according to `NFKC normalization form <https://en.wikipedia.org/wiki/Unicode_equivalence#Normalization>`_.
1. Create a new keypair
The validation function will validate the input string using the following rules:
* This will be used as the root network CA's keypair
* No blacklisted words like "node", "server".
2. Create a self-signed certificate for the keypair. The basic constraints extension must be set to ``true``
* 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.
* This will be used as the root network CA's certificate
* Should start with a capital letter.
3. Create a new keystore and store the root network CA's keypair and certificate in it for later use
* No commas or equals signs.
* This keystore will be used by the root network CA to sign the intermediate network CA's certificate
* No dollars or quote marks, although we may relax the quote mark constraint in future to handle Irish company names.
4. Create a new Java keystore named ``truststore.jks`` and store the root network CA's certificate in it using the
alias ``cordarootca``
Starting the Registration
-------------------------
* This keystore will be provisioned to the individual nodes later
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.
.. warning:: The root network CA's private key should be protected and kept safe.
**To start the registration**::
Creating the intermediate network CA's keystore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
java -jar corda.jar --initial-registration --base-dir <<optional>> --config-file <<optional>>
1. 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.
* This will be used as the intermediate network CA's keypair
.. 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.
2. Obtain a certificate for the keypair signed with the root network CA key. The basic constraints extension must be
set to ``true``
* This will be used as the intermediate network CA's certificate
Protocol Design
---------------
.. note:: This section is intended for developers who want to implement their own doorman service.
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
The certificate signing protocol:
* This keystore will be used by the intermediate network CA to sign the nodes' identity certificates
* Generate a keypair, save it to disk.
Creating the node CA keystores and TLS keystores
------------------------------------------------
* 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.
Creating the node CA keystores
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* 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. For each node, create a new keypair
* Store that URL to disk.
2. Obtain a certificate for the keypair signed with the intermediate network CA key. The basic constraints extension must be
set to ``true``
* 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.
3. Create a new Java keystore named ``nodekeystore.jks`` and store the keypair in it using the alias ``cordaclientca``
The initial registration process uses the following web api to communicate with the doorman service:
* The node will store this keystore locally to sign its identity keys and anonymous keys
+----------------+------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
| 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. |
+----------------+------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
Creating the node TLS keystores
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
See ``NetworkRegistrationHelper`` and ``X509Utilities`` for examples of certificate signing request creation and certificate signing using Bouncy Castle.
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. 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, copy the following files to the node's certificate directory (``<workspace>/certificates/``):
1. The node's ``nodekeystore.jks`` keystore
2. The node's ``sslkeystore.jks`` keystore
3. The root network CA's ``truststore.jks`` keystore

View File

@ -7,4 +7,5 @@ Quickstart
getting-set-up
tutorial-cordapp
Sample CorDapps <https://www.corda.net/samples/>
building-against-master
CLI-vs-IDE

View File

@ -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
~~~~~~~~~~~~~

View File

@ -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.

View File

@ -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

View File

@ -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 <changelog_m12>`), to :ref:`V1.0 <changelog_v1>`
These notes provide instructions for upgrading your CorDapps from previous versions, starting with the upgrade from our
first public Beta (:ref:`Milestone 12 <changelog_m12>`), to :ref:`V1.0 <changelog_v1>`.
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 <changelog_m14>`
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 <changelog_m14>`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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 <changelog_m13>`
------------
:ref:`From Milestone 13 <changelog_m13>`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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 <changelog_m12>` (First Public Beta)
-----------------------------------
:ref:`From Milestone 12 (First Public Beta) <changelog_m12>`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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.

View File

@ -131,7 +131,13 @@ private fun Config.defaultToOldPath(property: KProperty<*>): String {
private fun parseEnum(enumType: Class<*>, name: String): Enum<*> = enumBridge<Proxy.Type>(uncheckedCast(enumType), name) // Any enum will do
private fun <T : Enum<T>> enumBridge(clazz: Class<T>, name: String): T = java.lang.Enum.valueOf(clazz, name)
private fun <T : Enum<T>> enumBridge(clazz: Class<T>, 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].

View File

@ -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<EnumData, EnumListData, TestEnum>(TestEnum.Value2, TestEnum.Value1, valuesToString = true)
}
@Test
fun `unknown Enum`() {
val config = config("value" to "UnknownValue")
assertThatThrownBy { config.parseAs<EnumData>() }
.hasMessageContaining(TestEnum.Value1.name)
.hasMessageContaining(TestEnum.Value2.name)
}
@Test
fun `LocalDate`() {
testPropertyType<LocalDateData, LocalDateListData, LocalDate>(LocalDate.now(), LocalDate.now().plusDays(1), valuesToString = true)

View File

@ -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)
}
}

View File

@ -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

View File

@ -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<Map> context, StringToMethodCallParser<CordaRPCOps> parser) {

View File

@ -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<String> 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));
}
}

View File

@ -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<KeyPair>, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, stateLoader: StateLoader, database: CordaPersistence, info: NodeInfo): MutableList<Any> {
private fun makeServices(keyPairs: Set<KeyPair>, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, stateLoader: StateLoader, database: CordaPersistence, info: NodeInfo, identityService: IdentityService): MutableList<Any> {
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 <T> initialiseDatabasePersistence(schemaService: SchemaService, insideTransaction: (CordaPersistence) -> T): T {
protected open fun <T> 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

View File

@ -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
@ -232,7 +233,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 <T> initialiseDatabasePersistence(schemaService: SchemaService, insideTransaction: (CordaPersistence) -> T): T {
override fun <T> 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)) {
@ -249,7 +250,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<Unit>()

View File

@ -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<User>
val devMode: Boolean
val devModeOptions: DevModeOptions?
@ -43,6 +41,27 @@ interface NodeConfiguration : NodeSSLConfiguration {
val relay: RelayConfiguration?
}
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
}
@ -90,7 +109,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<User>,
override val verifierType: VerifierType,

View File

@ -26,13 +26,9 @@ import javax.persistence.Id
import javax.persistence.Lob
@ThreadSafe
class PersistentIdentityService(identities: Iterable<PartyAndCertificate> = emptySet(),
confidentialIdentities: Iterable<PartyAndCertificate> = emptySet(),
override val trustRoot: X509Certificate,
class PersistentIdentityService(override val trustRoot: X509Certificate,
vararg caCertificates: X509Certificate) : SingletonSerializeAsToken(), IdentityService {
constructor(wellKnownIdentities: Iterable<PartyAndCertificate> = emptySet(),
confidentialIdentities: Iterable<PartyAndCertificate> = 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<PartyAndCertificate> = empt
init {
val caCertificatesWithRoot: Set<X509Certificate> = caCertificates.toSet() + trustRoot
caCertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(caCertificatesWithRoot))
}
/** Requires a database transaction. */
fun loadIdentities(identities: Iterable<PartyAndCertificate> = emptySet(), confidentialIdentities: Iterable<PartyAndCertificate> = emptySet()) {
identities.forEach {
val key = mapToKey(it)
keyToParties.addWithDuplicatesAllowed(key, it, false)
@ -136,7 +136,7 @@ class PersistentIdentityService(identities: Iterable<PartyAndCertificate> = 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))
}

View File

@ -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>(AbstractParty::class.java) {
class AbstractPartyDescriptor(private val identityService: IdentityService) : AbstractTypeDescriptor<AbstractParty>(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))

View File

@ -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<AbstractParty, String> {
class AbstractPartyToX500NameAsStringConverter(private val identityService: IdentityService) : AttributeConverter<AbstractParty, String> {
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()

View File

@ -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 createIdentityService: () -> IdentityService) {
class HibernateConfiguration(val schemaService: SchemaService, private val databaseConfig: DatabaseConfig, private val identityService: IdentityService) {
companion object {
private val logger = contextLogger()
}
@ -34,14 +33,13 @@ 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<Set<MappedSchema>, 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
// 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)
}
@ -56,9 +54,9 @@ 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())
if (databaseProperties.getProperty("schema") != null) {
// This property helps 'hibernate.hbm2ddl.auto' to work properly when many schemas have similar table names.
@ -70,7 +68,7 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab
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
}
@ -85,7 +83,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)
@ -137,11 +135,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...]"
}
}
}

View File

@ -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;

View File

@ -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<String>, out: RenderPrintWriter, context: InvocationContext<out Any>): Any? {
fun runRPCFromString(input: List<String>, out: RenderPrintWriter, context: InvocationContext<out Any>, 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)

View File

@ -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<T>(val invocationContext:InvocationContext, val permissions:RpcPermissions, val block:() -> T) : Thread() {
private var result: CompletableFuture<T> = CompletableFuture()
override fun run() {
CURRENT_RPC_CONTEXT.set(RpcAuthContext(invocationContext, permissions))
try {
result.complete(block())
} catch (e:Throwable) {
result.completeExceptionally(e)
}
private class RPCContextRunner<T>(val invocationContext:InvocationContext, val rpcPermissions: RpcPermissions, val block:() -> T) : Thread() {
private var result: CompletableFuture<T> = 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<T> {
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<AttachmentId> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.queryAttachments(query, sorting) }.get().getOrThrow()
}
override fun <T : ContractState> vaultTrackByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackByWithSorting(contractStateType, criteria, sorting) }.get().getOrThrow()
}
override fun <T : ContractState> vaultTrackByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackByWithPagingSpec(contractStateType, criteria, paging) }.get().getOrThrow()
}
override fun <T : ContractState> vaultTrackByCriteria(contractStateType: Class<out T>, criteria: QueryCriteria): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackByCriteria(contractStateType, criteria) }.get().getOrThrow()
}
override fun <T : ContractState> vaultTrack(contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrack(contractStateType) }.get().getOrThrow()
}
override fun <T : ContractState> vaultQueryByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): Vault.Page<T> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryByWithSorting(contractStateType, criteria, sorting) }.get().getOrThrow()
}
override fun <T : ContractState> vaultQueryByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): Vault.Page<T> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryByWithPagingSpec(contractStateType, criteria, paging) }.get().getOrThrow()
}
override fun <T : ContractState> vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class<out T>): Vault.Page<T> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryByCriteria(criteria, contractStateType) }.get().getOrThrow()
}
override fun <T : ContractState> vaultQuery(contractStateType: Class<out T>): Vault.Page<T> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQuery(contractStateType) }.get().getOrThrow()
}
override fun stateMachinesSnapshot(): List<StateMachineInfo> {
return RPCContextRunner(invocationContext, rpcPermissions, cordaRPCOps::stateMachinesSnapshot).get().getOrThrow()
}
override fun stateMachinesFeed(): DataFeed<List<StateMachineInfo>, StateMachineUpdate> {
return RPCContextRunner(invocationContext, rpcPermissions, cordaRPCOps::stateMachinesFeed).get().getOrThrow()
}
override fun <T : ContractState> vaultQueryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class<out T>): Vault.Page<T> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryBy(criteria, paging, sorting, contractStateType) }.get().getOrThrow()
}
override fun <T : ContractState> vaultTrackBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackBy(criteria, paging, sorting, contractStateType) }.get().getOrThrow()
}
override fun internalVerifiedTransactionsSnapshot(): List<SignedTransaction> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.internalVerifiedTransactionsSnapshot() }.get().getOrThrow()
}
override fun internalVerifiedTransactionsFeed(): DataFeed<List<SignedTransaction>, SignedTransaction> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.internalVerifiedTransactionsFeed() }.get().getOrThrow()
}
override fun stateMachineRecordedTransactionMappingSnapshot(): List<StateMachineTransactionMapping> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.stateMachineRecordedTransactionMappingSnapshot() }.get().getOrThrow()
}
override fun stateMachineRecordedTransactionMappingFeed(): DataFeed<List<StateMachineTransactionMapping>, StateMachineTransactionMapping> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.stateMachineRecordedTransactionMappingFeed() }.get().getOrThrow()
}
override fun networkMapSnapshot(): List<NodeInfo> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.networkMapSnapshot() }.get().getOrThrow()
}
override fun networkMapFeed(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.networkMapFeed() }.get().getOrThrow()
}
override fun <T> startFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowHandle<T> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.startFlowDynamic(logicType, *args) }.get().getOrThrow()
}
override fun <T> startTrackedFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowProgressHandle<T> {
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<Party> {
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<String> {
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<Void?> {
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<Party> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.partiesFromName(query, exactMatch) }.get().getOrThrow()
}
override fun registeredFlows(): List<String> {
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<T> {
start()
join()
return result
}
}

View File

@ -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
}

View File

@ -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 createIdentityService: () -> 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, createIdentityService)
HibernateConfiguration(schemaService, databaseConfig, 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, 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 <T> transaction(statement: DatabaseTransaction.() -> T): T {
return transaction(transactionIsolationLevel, statement)
}
fun <T> transaction(statement: DatabaseTransaction.() -> T): T = transaction(transactionIsolationLevel, statement)
private fun <T> transaction(transactionIsolation: Int, repetitionAttempts: Int, statement: DatabaseTransaction.() -> T): T {
val outer = DatabaseTransactionManager.currentOrNull()
@ -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, databaseConfig: DatabaseConfig, 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, databaseConfig)
// Check not in read-only mode.
persistence.transaction {
persistence.dataSource.connection.use {
@ -217,15 +216,3 @@ fun <T : Any> rx.Observable<T>.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
}
}

View File

@ -10,7 +10,7 @@ dataSourceProperties = {
dataSource.password = ""
}
database = {
transactionIsolationLevel = "repeatableRead"
transactionIsolationLevel = "REPEATABLE_READ"
initDatabase = true
}
devMode = true

View File

@ -69,7 +69,7 @@ public class VaultQueryJavaTests {
keys.add(getDUMMY_NOTARY_KEY());
IdentityService identitySvc = makeTestIdentityService();
@SuppressWarnings("unchecked")
Pair<CordaPersistence, MockServices> databaseAndServices = makeTestDatabaseAndMockServices(keys, () -> identitySvc, cordappPackages);
Pair<CordaPersistence, MockServices> 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();

View File

@ -2,18 +2,15 @@ package net.corda.node
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import net.corda.client.jackson.JacksonSupport
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.Amount
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.Party
import net.corda.core.internal.FlowStateMachine
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.objectOrNewInstance
import net.corda.core.messaging.FlowProgressHandle
import net.corda.core.messaging.FlowProgressHandleImpl
import net.corda.core.utilities.ProgressTracker
import net.corda.node.services.config.DatabaseConfig
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.shell.InteractiveShell
import net.corda.node.utilities.configureDatabase
@ -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(), DatabaseConfig(), rigorousMock())
}
@After
@ -96,6 +92,4 @@ class InteractiveShellTest {
@Test
fun party() = check("party: \"${MEGA_CORP.name}\"", MEGA_CORP.name.toString())
class DummyFSM(override val logic: FlowA) : FlowStateMachine<Any?> by rigorousMock()
}

View File

@ -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,

View File

@ -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,10 +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 net.corda.testing.node.MockServices.Companion.makeTestIdentityService
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
@ -91,8 +93,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
smmHasRemovedAllFlows = CountDownLatch(1)
calls = 0
val dataSourceProps = makeTestDataSourceProperties()
val databaseProperties = makeTestDatabaseProperties()
database = configureDatabase(dataSourceProps, databaseProperties, ::makeTestIdentityService)
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"))

View File

@ -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 {

View File

@ -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,8 +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 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(), DatabaseConfig(), rigorousMock())
networkMapRegistrationFuture = doneFuture(Unit)
networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database), rigorousMock())
}

View File

@ -4,14 +4,14 @@ 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.node.MockServices.Companion.makeTestIdentityService
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
import org.junit.Before
@ -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(), ::makeTestIdentityService)
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock())
newCheckpointStorage()
}

View File

@ -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,8 +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 net.corda.testing.node.MockServices.Companion.makeTestIdentityService
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
@ -33,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(), ::makeTestIdentityService)
database = configureDatabase(dataSourceProps, DatabaseConfig(), rigorousMock())
database.transaction {
services = object : MockServices(BOB_KEY) {
override val vaultService: VaultServiceInternal

View File

@ -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

View File

@ -13,13 +13,13 @@ 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.node.MockServices.Companion.makeTestIdentityService
import net.corda.testing.rigorousMock
import org.junit.After
import org.junit.Before
import org.junit.Ignore
@ -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(), ::makeTestIdentityService)
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())

View File

@ -11,23 +11,21 @@ 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.node.MockServices.Companion.makeTestIdentityService
import net.corda.testing.rigorousMock
import org.junit.After
import org.junit.Before
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<Vault.Update<ContractState>>()
val schemaService = object : SchemaService {
@ -68,7 +65,7 @@ class HibernateObserverTests {
return parent
}
}
val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService, 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)))))

View File

@ -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.node.MockServices.Companion.makeTestIdentityService
import org.junit.*
import net.corda.testing.rigorousMock
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<Member>
lateinit var transaction: DatabaseTransaction
private lateinit var cluster: List<Member>
private val databases: MutableList<CordaPersistence> = mutableListOf()
@Before
@ -86,7 +88,7 @@ class DistributedImmutableMapTests {
private fun createReplica(myAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): CompletableFuture<Member> {
val storage = Storage.builder().withStorageLevel(StorageLevel.MEMORY).build()
val address = Address(myAddress.host, myAddress.port)
val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService)
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(serverNameTablePrefix = "PORT_${myAddress.port}_"), rigorousMock())
databases.add(database)
val stateMachineFactory = { DistributedImmutableMap(database, RaftUniquenessProvider.Companion::createMap) }

View File

@ -2,12 +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 net.corda.testing.node.MockServices.Companion.makeTestIdentityService
import org.junit.After
import org.junit.Before
import org.junit.Rule
@ -19,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(), ::makeTestIdentityService)
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock())
}
@After

View File

@ -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<CommercialPaper.State>(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<CommercialPaper.State>(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)
}
}

View File

@ -3,9 +3,9 @@ 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.node.MockServices.Companion.makeTestIdentityService
import net.corda.testing.rigorousMock
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Test
@ -18,10 +18,10 @@ class ObservablesTests {
private fun isInDatabaseTransaction(): Boolean = (DatabaseTransactionManager.currentOrNull() != null)
val toBeClosed = mutableListOf<Closeable>()
private val toBeClosed = mutableListOf<Closeable>()
fun createDatabase(): CordaPersistence {
val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService)
private fun createDatabase(): CordaPersistence {
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock())
toBeClosed += database
return database
}

View File

@ -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<SSLServerSocketFactory, SSLSocketFactory> {
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<String> = CORDA_TLS_CIPHER_SUITES,
cipherSuitesClient: Array<String> = CORDA_TLS_CIPHER_SUITES
): Pair<SSLServerSocket, SSLSocket> {
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 <hostname> resolves to(as that's what the server binds to). In particular on Debian <hostname>
// 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()) }
}
}

View File

@ -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,8 +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.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(), DatabaseConfig(), rigorousMock())
database.transaction {
oracle = createMockCordaService(services, NodeInterestRates::Oracle)
oracle.knownFixes = TEST_DATA

View File

@ -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<User>()).whenever(it).rpcUsers
doReturn(null).whenever(it).notary
doReturn(makeTestDataSourceProperties(myLegalName.organisation)).whenever(it).dataSourceProperties
doReturn(makeTestDatabaseProperties(myLegalName.organisation)).whenever(it).database
doReturn(DatabaseConfig(myLegalName.organisation)).whenever(it).database
doReturn("").whenever(it).emailAddress
doReturn("").whenever(it).exportJMXto
doReturn(true).whenever(it).devMode

View File

@ -306,9 +306,11 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
override val serializationWhitelists: List<SerializationWhitelist>
get() = testSerializationWhitelists
private var dbCloser: (() -> Any?)? = null
override fun <T> initialiseDatabasePersistence(schemaService: SchemaService, insideTransaction: (CordaPersistence) -> T) = super.initialiseDatabasePersistence(schemaService) { database ->
dbCloser = database::close
insideTransaction(database)
override fun <T> initialiseDatabasePersistence(schemaService: SchemaService, identityService: IdentityService, insideTransaction: (CordaPersistence) -> T): T {
return super.initialiseDatabasePersistence(schemaService, identityService) { database ->
dbCloser = database::close
insideTransaction(database)
}
}
fun disableDBCloseOnStop() {

View File

@ -25,6 +25,7 @@ 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.configOf
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
@ -102,21 +103,6 @@ open class MockServices(
return props
}
/**
* Make properties appropriate for creating a Database for unit tests.
*
* @param nodeName Reflects the "instance" of the in-memory database or database username/schema.
*/
@JvmStatic
fun makeTestDatabaseProperties(nodeName: String? = null): Properties {
val props = Properties()
props.setProperty("transactionIsolationLevel", "repeatableRead") //for other possible values see net.corda.node.utilities.CordaPeristence.parserTransactionIsolationLevel(String)
if (nodeName != null) {
props.setProperty("nodeOrganizationName", nodeName)
}
return props
}
/**
* Creates an instance of [InMemoryIdentityService] with [MOCK_IDENTITIES].
*/
@ -132,9 +118,9 @@ open class MockServices(
*/
@JvmStatic
fun makeTestDatabaseAndMockServices(keys: List<KeyPair> = listOf(MEGA_CORP_KEY),
createIdentityService: () -> IdentityService = { makeTestIdentityService() },
identityService: IdentityService = makeTestIdentityService(),
cordappPackages: List<String> = emptyList()): Pair<CordaPersistence, MockServices>
= makeTestDatabaseAndMockServices(keys, createIdentityService, cordappPackages, MEGA_CORP.name)
= makeTestDatabaseAndMockServices(keys, identityService, cordappPackages, MEGA_CORP.name)
/**
* Makes database and mock services appropriate for unit tests.
@ -145,17 +131,15 @@ open class MockServices(
*/
@JvmStatic
fun makeTestDatabaseAndMockServices(keys: List<KeyPair> = listOf(MEGA_CORP_KEY),
createIdentityService: () -> IdentityService = { makeTestIdentityService() },
identityService: IdentityService = makeTestIdentityService(),
cordappPackages: List<String> = emptyList(),
initialIdentityName: CordaX500Name): Pair<CordaPersistence, MockServices> {
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, DatabaseConfig(), 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<SignedTransaction>) {

View File

@ -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)