From dc3bd8de7a881b9655d7432b3e43410a8b2b77b5 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Fri, 13 Jul 2018 10:37:51 +0100 Subject: [PATCH 01/10] Retired BankOfCordaRPCClientTest as it's mostly duplicated by BankOfCordaCordformTest (#3565) --- .../finance/utils/StateSummingUtilities.kt | 8 +- .../net/corda/bank/BankOfCordaCordformTest.kt | 22 ++++- .../corda/bank/BankOfCordaRPCClientTest.kt | 94 ------------------- .../net/corda/bank/BankOfCordaCordform.kt | 18 ++-- .../corda/bank/api/BankOfCordaClientApi.kt | 4 +- .../internal/demorun/CordformNodeRunner.kt | 4 +- 6 files changed, 42 insertions(+), 108 deletions(-) delete mode 100644 samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt diff --git a/finance/src/main/kotlin/net/corda/finance/utils/StateSummingUtilities.kt b/finance/src/main/kotlin/net/corda/finance/utils/StateSummingUtilities.kt index 0665664309..ee155bbaf4 100644 --- a/finance/src/main/kotlin/net/corda/finance/utils/StateSummingUtilities.kt +++ b/finance/src/main/kotlin/net/corda/finance/utils/StateSummingUtilities.kt @@ -38,10 +38,14 @@ fun Iterable.sumCashOrZero(currency: Issued): Amount Iterable.sumFungibleOrNull() = filterIsInstance>().map { it.amount }.sumOrNull() +fun Iterable.sumFungibleOrNull(): Amount>? { + return filterIsInstance>().map { it.amount }.sumOrNull() +} /** Sums the asset states in the list, returning zero of the given token if there are none. */ -fun Iterable.sumFungibleOrZero(token: Issued) = filterIsInstance>().map { it.amount }.sumOrZero(token) +fun Iterable.sumFungibleOrZero(token: Issued): Amount> { + return filterIsInstance>().map { it.amount }.sumOrZero(token) +} /** * Sums the obligation states in the list, throwing an exception if there are none. All state objects in the diff --git a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaCordformTest.kt b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaCordformTest.kt index e5235553b6..beefbcfc09 100644 --- a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaCordformTest.kt +++ b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaCordformTest.kt @@ -1,16 +1,32 @@ package net.corda.bank +import net.corda.client.rpc.CordaRPCClient +import net.corda.core.contracts.withoutIssuer +import net.corda.core.utilities.NetworkHostAndPort import net.corda.finance.DOLLARS -import net.corda.finance.POUNDS +import net.corda.finance.contracts.asset.Cash +import net.corda.finance.utils.sumCash +import net.corda.testing.core.BOC_NAME import net.corda.testing.node.internal.demorun.nodeRunner +import org.assertj.core.api.Assertions.assertThat import org.junit.Test class BankOfCordaCordformTest { @Test fun `run demo`() { - BankOfCordaCordform().nodeRunner().scanPackages(listOf("net.corda.finance")).deployAndRunNodesThen { - IssueCash.requestWebIssue(30000.POUNDS) + BankOfCordaCordform().nodeRunner().scanPackages(listOf("net.corda.finance")).deployAndRunNodesAndThen { IssueCash.requestRpcIssue(20000.DOLLARS) + CordaRPCClient(NetworkHostAndPort("localhost", BOC_RPC_PORT)).use(BOC_RPC_USER, BOC_RPC_PWD) { + assertThat(it.proxy.vaultQuery(Cash.State::class.java).states).isEmpty() // All of the issued cash is transferred + } + CordaRPCClient(NetworkHostAndPort("localhost", BIGCORP_RPC_PORT)).use(BIGCORP_RPC_USER, BIGCORP_RPC_PWD) { + val cashStates = it.proxy.vaultQuery(Cash.State::class.java).states.map { it.state.data } + val knownOwner = it.proxy.wellKnownPartyFromAnonymous(cashStates.map { it.owner }.toSet().single()) + assertThat(knownOwner?.name).isEqualTo(BIGCORP_NAME) + val totalCash = cashStates.sumCash() + assertThat(totalCash.token.issuer.party.nameOrNull()).isEqualTo(BOC_NAME) + assertThat(totalCash.withoutIssuer()).isEqualTo(20000.DOLLARS) + } } } } diff --git a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt deleted file mode 100644 index d247b64ab6..0000000000 --- a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt +++ /dev/null @@ -1,94 +0,0 @@ -package net.corda.bank - -import net.corda.client.rpc.CordaRPCClient -import net.corda.core.messaging.CordaRPCOps -import net.corda.core.messaging.startFlow -import net.corda.core.node.services.Vault -import net.corda.core.node.services.vault.QueryCriteria -import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.getOrThrow -import net.corda.finance.DOLLARS -import net.corda.finance.contracts.asset.Cash -import net.corda.finance.flows.CashIssueAndPaymentFlow -import net.corda.node.services.Permissions.Companion.invokeRpc -import net.corda.node.services.Permissions.Companion.startFlow -import net.corda.testing.core.BOC_NAME -import net.corda.testing.core.expect -import net.corda.testing.core.expectEvents -import net.corda.testing.core.sequence -import net.corda.testing.driver.DriverParameters -import net.corda.testing.driver.driver -import net.corda.testing.node.User -import org.junit.Test - -class BankOfCordaRPCClientTest { - @Test - fun `issuer flow via RPC`() { - val commonPermissions = setOf( - invokeRpc("vaultTrackByCriteria"), - invokeRpc(CordaRPCOps::wellKnownPartyFromX500Name), - invokeRpc(CordaRPCOps::notaryIdentities) - ) - driver(DriverParameters(extraCordappPackagesToScan = listOf("net.corda.finance"))) { - val bocManager = User("bocManager", "password1", permissions = setOf( - startFlow()) + commonPermissions) - val bigCorpCFO = User("bigCorpCFO", "password2", permissions = emptySet() + commonPermissions) - val (nodeBankOfCorda, nodeBigCorporation) = listOf( - startNode(providedName = BOC_NAME, rpcUsers = listOf(bocManager)), - startNode(providedName = BIGCORP_NAME, rpcUsers = listOf(bigCorpCFO)) - ).map { it.getOrThrow() } - - // Bank of Corda RPC Client - val bocClient = CordaRPCClient(nodeBankOfCorda.rpcAddress) - val bocProxy = bocClient.start("bocManager", "password1").proxy - - // Big Corporation RPC Client - val bigCorpClient = CordaRPCClient(nodeBigCorporation.rpcAddress) - val bigCorpProxy = bigCorpClient.start("bigCorpCFO", "password2").proxy - - // Register for Bank of Corda Vault updates - val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL) - val vaultUpdatesBoc = bocProxy.vaultTrackByCriteria(Cash.State::class.java, criteria).updates - - // Register for Big Corporation Vault updates - val vaultUpdatesBigCorp = bigCorpProxy.vaultTrackByCriteria(Cash.State::class.java, criteria).updates - - val bigCorporation = bigCorpProxy.wellKnownPartyFromX500Name(BIGCORP_NAME)!! - - // Kick-off actual Issuer Flow - val anonymous = true - bocProxy.startFlow(::CashIssueAndPaymentFlow, - 1000.DOLLARS, OpaqueBytes.of(1), - bigCorporation, - anonymous, - defaultNotaryIdentity).returnValue.getOrThrow() - - // Check Bank of Corda Vault Updates - vaultUpdatesBoc.expectEvents { - sequence( - // ISSUE - expect { update -> - require(update.consumed.isEmpty()) { "Expected 0 consumed states, actual: $update" } - require(update.produced.size == 1) { "Expected 1 produced states, actual: $update" } - }, - // MOVE - expect { update -> - require(update.consumed.size == 1) { "Expected 1 consumed states, actual: $update" } - require(update.produced.isEmpty()) { "Expected 0 produced states, actual: $update" } - } - ) - } - - // Check Big Corporation Vault Updates - vaultUpdatesBigCorp.expectEvents { - sequence( - // MOVE - expect { (consumed, produced) -> - require(consumed.isEmpty()) { consumed.size } - require(produced.size == 1) { produced.size } - } - ) - } - } - } -} \ No newline at end of file diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaCordform.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaCordform.kt index 1e2d724dbc..c84434e94a 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaCordform.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaCordform.kt @@ -21,10 +21,17 @@ import kotlin.system.exitProcess val BIGCORP_NAME = CordaX500Name(organisation = "BigCorporation", locality = "New York", country = "US") private val NOTARY_NAME = CordaX500Name(organisation = "Notary Service", locality = "Zurich", country = "CH") -private const val BOC_RPC_PORT = 10006 +const val BOC_RPC_PORT = 10006 +const val BIGCORP_RPC_PORT = 10009 private const val BOC_RPC_ADMIN_PORT = 10015 private const val BOC_WEB_PORT = 10007 +const val BOC_RPC_USER = "bankUser" +const val BOC_RPC_PWD = "test" + +const val BIGCORP_RPC_USER = "bigCorpUser" +const val BIGCORP_RPC_PWD = "test" + class BankOfCordaCordform : CordformDefinition() { init { @@ -47,18 +54,18 @@ class BankOfCordaCordform : CordformDefinition() { adminAddress("localhost:$BOC_RPC_ADMIN_PORT") } webPort(BOC_WEB_PORT) - rpcUsers(User("bankUser", "test", setOf(all()))) + rpcUsers(User(BOC_RPC_USER, BOC_RPC_PWD, setOf(all()))) devMode(true) } node { name(BIGCORP_NAME) p2pPort(10008) rpcSettings { - address("localhost:10009") + address("localhost:$BIGCORP_RPC_PORT") adminAddress("localhost:10011") } webPort(10010) - rpcUsers(User("bigCorpUser", "test", setOf(all()))) + rpcUsers(User(BIGCORP_RPC_USER, BIGCORP_RPC_PWD, setOf(all()))) devMode(true) } } @@ -113,8 +120,7 @@ object IssueCash { return BankOfCordaClientApi.requestRPCIssue(NetworkHostAndPort("localhost", BOC_RPC_PORT), createParams(amount, NOTARY_NAME)) } - @VisibleForTesting - fun requestWebIssue(amount: Amount) { + private fun requestWebIssue(amount: Amount) { BankOfCordaClientApi.requestWebIssue(NetworkHostAndPort("localhost", BOC_WEB_PORT), createParams(amount, NOTARY_NAME)) } diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaClientApi.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaClientApi.kt index 231e6781d9..3daf76c8f7 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaClientApi.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaClientApi.kt @@ -1,5 +1,7 @@ package net.corda.bank.api +import net.corda.bank.BOC_RPC_PWD +import net.corda.bank.BOC_RPC_USER import net.corda.bank.api.BankOfCordaWebApi.IssueRequestParams import net.corda.client.rpc.CordaRPCClient import net.corda.core.messaging.startFlow @@ -31,7 +33,7 @@ object BankOfCordaClientApi { fun requestRPCIssue(rpcAddress: NetworkHostAndPort, params: IssueRequestParams): SignedTransaction { val client = CordaRPCClient(rpcAddress) // TODO: privileged security controls required - client.start("bankUser", "test").use { connection -> + client.start(BOC_RPC_USER, BOC_RPC_PWD).use { connection -> val rpc = connection.proxy rpc.waitUntilNetworkReady().getOrThrow() diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/demorun/CordformNodeRunner.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/demorun/CordformNodeRunner.kt index fa5b9b5150..225688a2fe 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/demorun/CordformNodeRunner.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/demorun/CordformNodeRunner.kt @@ -17,7 +17,7 @@ fun CordformDefinition.nodeRunner() = CordformNodeRunner(this) /** * A node runner creates and runs nodes for a given [[CordformDefinition]]. */ -class CordformNodeRunner(val cordformDefinition: CordformDefinition) { +class CordformNodeRunner(private val cordformDefinition: CordformDefinition) { private var extraPackagesToScan = emptyList() /** @@ -45,7 +45,7 @@ class CordformNodeRunner(val cordformDefinition: CordformDefinition) { * Deploy the nodes specified in the given [CordformDefinition] and then execute the given [block] once all the nodes * and webservers are up. After execution all these processes will be terminated. */ - fun deployAndRunNodesThen(block: () -> Unit) { + fun deployAndRunNodesAndThen(block: () -> Unit) { runNodes(waitForAllNodesToFinish = false, block = block) } From 5dd85e08bf5ae0b24d73cdefa96f16dc5003f400 Mon Sep 17 00:00:00 2001 From: Michal Kit Date: Fri, 13 Jul 2018 12:46:50 +0100 Subject: [PATCH 02/10] CORDA-1805 Fixing the NPE with path parent (#3591) --- .../net/corda/nodeapi/internal/crypto/KeyStoreUtilities.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreUtilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreUtilities.kt index 981d16cf94..617a5df6e8 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreUtilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreUtilities.kt @@ -30,7 +30,7 @@ fun loadOrCreateKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStor keyStoreFilePath.read { keyStore.load(it, pass) } } else { keyStore.load(null, pass) - keyStoreFilePath.parent.createDirectories() + keyStoreFilePath.toAbsolutePath().parent?.createDirectories() keyStoreFilePath.write { keyStore.store(it, pass) } } return keyStore From 4fb7f7d3d0f4ca5998dc134a2beeee7b2a73345d Mon Sep 17 00:00:00 2001 From: Katarzyna Streich Date: Fri, 13 Jul 2018 13:44:29 +0100 Subject: [PATCH 03/10] CORDA-1733 X500Principal configuration parsing (#3580) CORDA-1733 X500Principal configuration parsing Change the NodeConfiguration.tlsCertCrlIssuer type from String to X500Principal --- .../corda/nodeapi/internal/config/ConfigUtilities.kt | 6 +++++- .../nodeapi/internal/config/ConfigParsingTest.kt | 8 ++++++++ .../corda/node/services/config/NodeConfiguration.kt | 11 +++-------- .../registration/NetworkRegistrationHelper.kt | 10 +++++----- .../node/services/config/NodeConfigurationImplTest.kt | 10 ++-------- 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt index b3ee7f06ee..de1311ef78 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt @@ -22,6 +22,7 @@ import java.time.Instant import java.time.LocalDate import java.time.temporal.Temporal import java.util.* +import javax.security.auth.x500.X500Principal import kotlin.reflect.KClass import kotlin.reflect.KProperty import kotlin.reflect.KType @@ -128,6 +129,7 @@ private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set Path::class -> Paths.get(getString(path)) URL::class -> URL(getString(path)) UUID::class -> UUID.fromString(getString(path)) + X500Principal::class -> X500Principal(getString(path)) CordaX500Name::class -> { when (getValue(path).valueType()) { ConfigValueType.OBJECT -> getConfig(path).parseAs(onUnknownKeys) @@ -173,6 +175,7 @@ private fun Config.getCollectionValue(path: String, type: KType, onUnknownKeys: NetworkHostAndPort::class -> getStringList(path).map(NetworkHostAndPort.Companion::parse) Path::class -> getStringList(path).map { Paths.get(it) } URL::class -> getStringList(path).map(::URL) + X500Principal::class -> getStringList(path).map(::X500Principal) UUID::class -> getStringList(path).map { UUID.fromString(it) } CordaX500Name::class -> getStringList(path).map(CordaX500Name.Companion::parse) Properties::class -> getConfigList(path).map(Config::toProperties) @@ -226,7 +229,7 @@ private fun Any.toConfigMap(): Map { val configValue = if (value is String || value is Boolean || value is Number) { // These types are supported by Config as use as is value - } else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name || value is Path || value is URL || value is UUID) { + } else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name || value is Path || value is URL || value is UUID || value is X500Principal) { // These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs value.toString() } else if (value is Enum<*>) { @@ -261,6 +264,7 @@ private fun Iterable<*>.toConfigIterable(field: Field): Iterable { NetworkHostAndPort::class.java -> map(Any?::toString) Path::class.java -> map(Any?::toString) URL::class.java -> map(Any?::toString) + X500Principal::class.java -> map(Any?::toString) UUID::class.java -> map(Any?::toString) CordaX500Name::class.java -> map(Any?::toString) Properties::class.java -> map { ConfigFactory.parseMap(uncheckedCast(it)).root() } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt index 07698b83c3..07de31c51d 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt @@ -14,6 +14,7 @@ import java.nio.file.Path import java.time.Instant import java.time.LocalDate import java.util.* +import javax.security.auth.x500.X500Principal import kotlin.reflect.full.primaryConstructor class ConfigParsingTest { @@ -84,6 +85,11 @@ class ConfigParsingTest { testPropertyType(URL("http://localhost:1234"), URL("http://localhost:1235"), valuesToString = true) } + @Test + fun X500Principal() { + testPropertyType(X500Principal("C=US, L=New York, CN=Corda Root CA, OU=Corda, O=R3 HoldCo LLC"), X500Principal("O=Bank A,L=London,C=GB"), valuesToString = true) + } + @Test fun UUID() { testPropertyType(UUID.randomUUID(), UUID.randomUUID(), valuesToString = true) @@ -324,6 +330,8 @@ class ConfigParsingTest { data class PathListData(override val values: List) : ListData data class URLData(override val value: URL) : SingleData data class URLListData(override val values: List) : ListData + data class X500PrincipalData(override val value: X500Principal) : SingleData + data class X500PrincipalListData(override val values: List) : ListData data class UUIDData(override val value: UUID) : SingleData data class UUIDListData(override val values: List) : ListData data class CordaX500NameData(override val value: CordaX500Name) : SingleData diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index b4de9fbea4..b4087a2870 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -16,12 +16,12 @@ import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.parseAs import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.tools.shell.SSHDConfiguration -import org.bouncycastle.asn1.x500.X500Name import org.slf4j.Logger import java.net.URL import java.nio.file.Path import java.time.Duration import java.util.* +import javax.security.auth.x500.X500Principal val Int.MB: Long get() = this * 1024L * 1024L @@ -63,7 +63,7 @@ interface NodeConfiguration : NodeSSLConfiguration { val drainingModePollPeriod: Duration get() = Duration.ofSeconds(5) val extraNetworkMapKeys: List val tlsCertCrlDistPoint: URL? - val tlsCertCrlIssuer: String? + val tlsCertCrlIssuer: X500Principal? val effectiveH2Settings: NodeH2Settings? val flowMonitorPeriodMillis: Duration get() = DEFAULT_FLOW_MONITOR_PERIOD_MILLIS val flowMonitorSuspensionLoggingThresholdMillis: Duration get() = DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS @@ -168,7 +168,7 @@ data class NodeConfigurationImpl( override val compatibilityZoneURL: URL? = null, override var networkServices: NetworkServicesConfig? = null, override val tlsCertCrlDistPoint: URL? = null, - override val tlsCertCrlIssuer: String? = null, + override val tlsCertCrlIssuer: X500Principal? = null, override val rpcUsers: List, override val security: SecurityConfiguration? = null, override val verifierType: VerifierType, @@ -237,11 +237,6 @@ data class NodeConfigurationImpl( if (tlsCertCrlDistPoint == null) { errors += "tlsCertCrlDistPoint needs to be specified when tlsCertCrlIssuer is not NULL" } - try { - X500Name(tlsCertCrlIssuer) - } catch (e: Exception) { - errors += "Error when parsing tlsCertCrlIssuer: ${e.message}" - } } if (!crlCheckSoftFail && tlsCertCrlDistPoint == null) { errors += "tlsCertCrlDistPoint needs to be specified when crlCheckSoftFail is FALSE" diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt index 451550f0e2..b8637baace 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt @@ -283,20 +283,20 @@ class NodeRegistrationHelper(private val config: NodeConfiguration, certService: } override fun validateAndGetTlsCrlIssuerCert(): X509Certificate? { - config.tlsCertCrlIssuer ?: return null - val tlsCertCrlIssuerPrincipal = X500Principal(config.tlsCertCrlIssuer) - if (principalMatchesCertificatePrincipal(tlsCertCrlIssuerPrincipal, rootCert)) { + val tlsCertCrlIssuer = config.tlsCertCrlIssuer + tlsCertCrlIssuer ?: return null + if (principalMatchesCertificatePrincipal(tlsCertCrlIssuer, rootCert)) { return rootCert } return if (config.trustStoreFile.exists()) { - findMatchingCertificate(tlsCertCrlIssuerPrincipal, config.loadTrustStore()) + findMatchingCertificate(tlsCertCrlIssuer, config.loadTrustStore()) } else { null } } override fun isTlsCrlIssuerCertRequired(): Boolean { - return !config.tlsCertCrlIssuer.isNullOrEmpty() + return config.tlsCertCrlIssuer != null } private fun findMatchingCertificate(principal: X500Principal, trustStore: X509KeyStore): X509Certificate? { diff --git a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt index 9f183d0b9a..22d68fab17 100644 --- a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt @@ -14,6 +14,7 @@ import org.junit.Test import java.net.URI import java.net.URL import java.nio.file.Paths +import javax.security.auth.x500.X500Principal import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -34,13 +35,6 @@ class NodeConfigurationImplTest { assertThat(configValidationResult.first()).contains("tlsCertCrlDistPoint needs to be specified when tlsCertCrlIssuer is not NULL") } - @Test - fun `tlsCertCrlIssuer validation fails when misconfigured`() { - val configValidationResult = configTlsCertCrlOptions(URL("http://test.com/crl"), "Corda Root CA").validate() - assertTrue { configValidationResult.isNotEmpty() } - assertThat(configValidationResult.first()).contains("Error when parsing tlsCertCrlIssuer:") - } - @Test fun `can't have tlsCertCrlDistPoint null when crlCheckSoftFail is false`() { val configValidationResult = configTlsCertCrlOptions(null, null, false).validate() @@ -218,7 +212,7 @@ class NodeConfigurationImplTest { } private fun configTlsCertCrlOptions(tlsCertCrlDistPoint: URL?, tlsCertCrlIssuer: String?, crlCheckSoftFail: Boolean = true): NodeConfiguration { - return testConfiguration.copy(tlsCertCrlDistPoint = tlsCertCrlDistPoint, tlsCertCrlIssuer = tlsCertCrlIssuer, crlCheckSoftFail = crlCheckSoftFail) + return testConfiguration.copy(tlsCertCrlDistPoint = tlsCertCrlDistPoint, tlsCertCrlIssuer = tlsCertCrlIssuer?.let { X500Principal(it) }, crlCheckSoftFail = crlCheckSoftFail) } private val testConfiguration = testNodeConfiguration() From 92f59f2ad2f47cdcf433304ab0bbd6241f593e80 Mon Sep 17 00:00:00 2001 From: bpaunescu Date: Fri, 13 Jul 2018 14:00:02 +0100 Subject: [PATCH 04/10] Revert " Create Artemis p2p.inbound addresses before starting the broker (#3407)" (#3590) This reverts commit ed3944c54606e876abd6483ff5d66a764f9cf577. --- .../kotlin/net/corda/node/internal/Node.kt | 2 +- .../messaging/ArtemisMessagingServer.kt | 68 ++++++------------- 2 files changed, 23 insertions(+), 47 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index 08aabad98c..74837dda5a 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -190,7 +190,7 @@ open class Node(configuration: NodeConfiguration, if (!configuration.messagingServerExternal) { val brokerBindAddress = configuration.messagingServerAddress ?: NetworkHostAndPort("0.0.0.0", configuration.p2pAddress.port) - messageBroker = ArtemisMessagingServer(configuration, brokerBindAddress, networkParameters.maxMessageSize, info.legalIdentities.map { it.owningKey }) + messageBroker = ArtemisMessagingServer(configuration, brokerBindAddress, networkParameters.maxMessageSize) } val serverAddress = configuration.messagingServerAddress diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt index 7b4d697a2c..170dfd1b37 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt @@ -15,18 +15,14 @@ import net.corda.node.services.config.NodeConfiguration import net.corda.nodeapi.ArtemisTcpTransport.Companion.p2pAcceptorTcpTransport import net.corda.nodeapi.internal.AmqpMessageSizeChecksInterceptor import net.corda.nodeapi.internal.ArtemisMessageSizeChecksInterceptor -import net.corda.nodeapi.internal.ArtemisMessagingComponent import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.JOURNAL_HEADER_SIZE import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX import net.corda.nodeapi.internal.requireOnDefaultFileSystem -import org.apache.activemq.artemis.api.core.RoutingType import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl import org.apache.activemq.artemis.core.config.Configuration -import org.apache.activemq.artemis.core.config.CoreAddressConfiguration -import org.apache.activemq.artemis.core.config.CoreQueueConfiguration import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration import org.apache.activemq.artemis.core.security.Role @@ -35,7 +31,6 @@ import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager import java.io.IOException import java.security.KeyStoreException -import java.security.PublicKey import javax.annotation.concurrent.ThreadSafe import javax.security.auth.login.AppConfigurationEntry import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag.REQUIRED @@ -56,8 +51,7 @@ import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag.RE @ThreadSafe class ArtemisMessagingServer(private val config: NodeConfiguration, private val messagingServerAddress: NetworkHostAndPort, - private val maxMessageSize: Int, - private val identities: List = emptyList()) : ArtemisBroker, SingletonSerializeAsToken() { + private val maxMessageSize: Int) : ArtemisBroker, SingletonSerializeAsToken() { companion object { private val log = contextLogger() } @@ -121,47 +115,29 @@ class ArtemisMessagingServer(private val config: NodeConfiguration, log.info("P2P messaging server listening on $messagingServerAddress") } - private fun createArtemisConfig(): Configuration { - val addressConfigs = identities.map { - val queueName = ArtemisMessagingComponent.RemoteInboxAddress(it).queueName - log.info("Configuring address $queueName") - val queueConfig = CoreQueueConfiguration().apply { - address = queueName - name = queueName - routingType = RoutingType.ANYCAST - isExclusive = true - } - CoreAddressConfiguration().apply { - name = queueName - queueConfigurations = listOf(queueConfig) - addRoutingType(RoutingType.ANYCAST) - } + private fun createArtemisConfig() = SecureArtemisConfiguration().apply { + val artemisDir = config.baseDirectory / "artemis" + bindingsDirectory = (artemisDir / "bindings").toString() + journalDirectory = (artemisDir / "journal").toString() + largeMessagesDirectory = (artemisDir / "large-messages").toString() + acceptorConfigurations = mutableSetOf(p2pAcceptorTcpTransport(NetworkHostAndPort(messagingServerAddress.host, messagingServerAddress.port), config)) + // Enable built in message deduplication. Note we still have to do our own as the delayed commits + // and our own definition of commit mean that the built in deduplication cannot remove all duplicates. + idCacheSize = 2000 // Artemis Default duplicate cache size i.e. a guess + isPersistIDCache = true + isPopulateValidatedUser = true + journalBufferSize_NIO = maxMessageSize + JOURNAL_HEADER_SIZE // Artemis default is 490KiB - required to address IllegalArgumentException (when Artemis uses Java NIO): Record is too large to store. + journalBufferSize_AIO = maxMessageSize + JOURNAL_HEADER_SIZE // Required to address IllegalArgumentException (when Artemis uses Linux Async IO): Record is too large to store. + journalFileSize = maxMessageSize + JOURNAL_HEADER_SIZE// The size of each journal file in bytes. Artemis default is 10MiB. + managementNotificationAddress = SimpleString(NOTIFICATIONS_ADDRESS) + + // JMX enablement + if (config.jmxMonitoringHttpPort != null) { + isJMXManagementEnabled = true + isJMXUseBrokerName = true } - return SecureArtemisConfiguration().apply { - val artemisDir = config.baseDirectory / "artemis" - bindingsDirectory = (artemisDir / "bindings").toString() - journalDirectory = (artemisDir / "journal").toString() - largeMessagesDirectory = (artemisDir / "large-messages").toString() - acceptorConfigurations = mutableSetOf(p2pAcceptorTcpTransport(NetworkHostAndPort(messagingServerAddress.host, messagingServerAddress.port), config)) - // Enable built in message deduplication. Note we still have to do our own as the delayed commits - // and our own definition of commit mean that the built in deduplication cannot remove all duplicates. - idCacheSize = 2000 // Artemis Default duplicate cache size i.e. a guess - isPersistIDCache = true - isPopulateValidatedUser = true - journalBufferSize_NIO = maxMessageSize + JOURNAL_HEADER_SIZE // Artemis default is 490KiB - required to address IllegalArgumentException (when Artemis uses Java NIO): Record is too large to store. - journalBufferSize_AIO = maxMessageSize + JOURNAL_HEADER_SIZE // Required to address IllegalArgumentException (when Artemis uses Linux Async IO): Record is too large to store. - journalFileSize = maxMessageSize + JOURNAL_HEADER_SIZE// The size of each journal file in bytes. Artemis default is 10MiB. - managementNotificationAddress = SimpleString(NOTIFICATIONS_ADDRESS) - addressConfigurations = addressConfigs - // JMX enablement - if (config.jmxMonitoringHttpPort != null) { - isJMXManagementEnabled = true - isJMXUseBrokerName = true - } - - }.configureAddressSecurity() - } + }.configureAddressSecurity() /** * Authenticated clients connecting to us fall in one of the following groups: From 1d91272698b6e6ef3fad9cfd22829378ed192bd3 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Fri, 13 Jul 2018 14:17:56 +0100 Subject: [PATCH 05/10] Moved a bunch of tests that were in the integration bucket into unit (#3576) --- .../net/corda/finance/compat/CompatibilityTest.kt | 0 .../compatibilityData/v3/node_transaction.dat | Bin .../cordapp}/CordappConfigFileProviderTests.kt | 3 +-- .../net/corda/node/services/TimedFlowTests.kt | 3 +-- .../node/services/network/NodeInfoWatcherTest.kt | 0 5 files changed, 2 insertions(+), 4 deletions(-) rename finance/src/{integration-test => test}/kotlin/net/corda/finance/compat/CompatibilityTest.kt (100%) rename finance/src/{integration-test => test}/resources/compatibilityData/v3/node_transaction.dat (100%) rename node/src/{integration-test/kotlin/net/corda/node => test/kotlin/net/corda/node/internal/cordapp}/CordappConfigFileProviderTests.kt (95%) rename node/src/{integration-test => test}/kotlin/net/corda/node/services/TimedFlowTests.kt (98%) rename node/src/{integration-test => test}/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt (100%) diff --git a/finance/src/integration-test/kotlin/net/corda/finance/compat/CompatibilityTest.kt b/finance/src/test/kotlin/net/corda/finance/compat/CompatibilityTest.kt similarity index 100% rename from finance/src/integration-test/kotlin/net/corda/finance/compat/CompatibilityTest.kt rename to finance/src/test/kotlin/net/corda/finance/compat/CompatibilityTest.kt diff --git a/finance/src/integration-test/resources/compatibilityData/v3/node_transaction.dat b/finance/src/test/resources/compatibilityData/v3/node_transaction.dat similarity index 100% rename from finance/src/integration-test/resources/compatibilityData/v3/node_transaction.dat rename to finance/src/test/resources/compatibilityData/v3/node_transaction.dat diff --git a/node/src/integration-test/kotlin/net/corda/node/CordappConfigFileProviderTests.kt b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappConfigFileProviderTests.kt similarity index 95% rename from node/src/integration-test/kotlin/net/corda/node/CordappConfigFileProviderTests.kt rename to node/src/test/kotlin/net/corda/node/internal/cordapp/CordappConfigFileProviderTests.kt index 05bd36412b..f3ea03a72e 100644 --- a/node/src/integration-test/kotlin/net/corda/node/CordappConfigFileProviderTests.kt +++ b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappConfigFileProviderTests.kt @@ -1,4 +1,4 @@ -package net.corda.node +package net.corda.node.internal.cordapp import com.typesafe.config.Config import com.typesafe.config.ConfigException @@ -6,7 +6,6 @@ import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigRenderOptions import net.corda.core.internal.div import net.corda.core.internal.writeText -import net.corda.node.internal.cordapp.CordappConfigFileProvider import org.assertj.core.api.Assertions.assertThat import org.junit.Test import java.nio.file.Paths diff --git a/node/src/integration-test/kotlin/net/corda/node/services/TimedFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/TimedFlowTests.kt similarity index 98% rename from node/src/integration-test/kotlin/net/corda/node/services/TimedFlowTests.kt rename to node/src/test/kotlin/net/corda/node/services/TimedFlowTests.kt index d8a1b710bd..9d9bcf7f56 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/TimedFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/TimedFlowTests.kt @@ -45,7 +45,6 @@ import org.junit.Test import org.slf4j.MDC import java.security.PublicKey import java.util.concurrent.Future -import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger import kotlin.test.assertNotEquals @@ -198,7 +197,7 @@ class TimedFlowTests { val stx = requestPayload.signedTransaction subFlow(ResolveTransactionsFlow(stx, otherSideSession)) - if (TimedFlowTests.requestsReceived.getAndIncrement() == 0) { + if (requestsReceived.getAndIncrement() == 0) { logger.info("Ignoring") // Waiting forever stateMachine.suspend(FlowIORequest.WaitForLedgerCommit(SecureHash.randomSHA256()), false) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt similarity index 100% rename from node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt rename to node/src/test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt From 2b4865f884f0cf5c134e23c13ce28a9b7d5f717e Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Fri, 13 Jul 2018 14:50:37 +0100 Subject: [PATCH 06/10] Update CONTRIBUTORS.md --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 2e5bfbc3cb..481c32e989 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -59,6 +59,7 @@ see changes to this list. * Clay Ratliff (Thoughtworks) * Clemens Wan (R3) * Clinton Alexander (R3) +* Clyde D'Cruz (Persistent Systems Limited) * cncorda * Credit Suisse * cyrsis From ce787df5e5b170402cdb15f94b5ffea223ff494e Mon Sep 17 00:00:00 2001 From: bpaunescu Date: Fri, 13 Jul 2018 14:52:43 +0100 Subject: [PATCH 07/10] ENT-2053: add exclusive flag to created P2P queues (except service ones); this is a sync with ENT (#3592) --- .../node/services/messaging/P2PMessagingClient.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt index bec83e4d75..ed72cfaaaf 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt @@ -38,6 +38,7 @@ import net.corda.nodeapi.internal.bridging.BridgeControl import net.corda.nodeapi.internal.bridging.BridgeEntry import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.requireMessageSize +import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration import org.apache.activemq.artemis.api.core.ActiveMQObjectClosedException import org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID import org.apache.activemq.artemis.api.core.Message.HDR_VALIDATED_USER @@ -168,7 +169,8 @@ class P2PMessagingClient(val config: NodeConfiguration, inboxes += RemoteInboxAddress(it).queueName } - inboxes.forEach { createQueueIfAbsent(it, producerSession!!) } + inboxes.forEach { createQueueIfAbsent(it, producerSession!!, exclusive = true) } + p2pConsumer = P2PMessagingConsumer(inboxes, createNewSession, isDrainingModeOn, drainingModeWasChangedEvents) messagingExecutor = MessagingExecutor( @@ -469,14 +471,14 @@ class P2PMessagingClient(val config: NodeConfiguration, val internalTargetQueue = (address as? ArtemisAddress)?.queueName ?: throw IllegalArgumentException("Not an Artemis address") state.locked { - createQueueIfAbsent(internalTargetQueue, producerSession!!) + createQueueIfAbsent(internalTargetQueue, producerSession!!, exclusive = address !is ServiceAddress) } internalTargetQueue } } /** Attempts to create a durable queue on the broker which is bound to an address of the same name. */ - private fun createQueueIfAbsent(queueName: String, session: ClientSession) { + private fun createQueueIfAbsent(queueName: String, session: ClientSession, exclusive: Boolean) { fun sendBridgeCreateMessage() { val keyHash = queueName.substring(PEERS_PREFIX.length) val peers = networkMap.getNodesByOwningKeyIndex(keyHash) @@ -495,7 +497,9 @@ class P2PMessagingClient(val config: NodeConfiguration, val queueQuery = session.queueQuery(SimpleString(queueName)) if (!queueQuery.isExists) { log.info("Create fresh queue $queueName bound on same address") - session.createQueue(queueName, RoutingType.ANYCAST, queueName, true) + session.createQueue(queueName, RoutingType.ANYCAST, queueName, null, true, false, + ActiveMQDefaultConfiguration.getDefaultMaxQueueConsumers(), + ActiveMQDefaultConfiguration.getDefaultPurgeOnNoConsumers(), exclusive, null) sendBridgeCreateMessage() } } From 208ac49da0caaf0dfc5ade9f8bd8a49d13f17aae Mon Sep 17 00:00:00 2001 From: szymonsztuka Date: Fri, 13 Jul 2018 16:23:20 +0100 Subject: [PATCH 08/10] CORDA-1804 Corda node stops when detecting not migrated node_attachments_contracts table name (#3593) Database table NODE_ATTACHMENTS_CONTRACT_CLASS_NAME in v3.0 was changed to NODE_ATTCHMENTS_CONTRACTS in v3.1 and then finally NODE_ATTACHMENTS_CONTRACTS on current master. Users may omit the upgrade note and run into errors. After the change the node will not start if the new table name is not found and any other older ones is found. --- docs/source/upgrade-notes.rst | 2 + .../internal/persistence/CordaPersistence.kt | 19 +++++- ...gratedAttachmentContractsTableNameTests.kt | 66 +++++++++++++++++++ .../net/corda/node/internal/AbstractNode.kt | 6 +- .../net/corda/node/internal/NodeStartup.kt | 5 ++ 5 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 node/src/integration-test/kotlin/net/corda/node/persistence/FailNodeOnNotMigratedAttachmentContractsTableNameTests.kt diff --git a/docs/source/upgrade-notes.rst b/docs/source/upgrade-notes.rst index fa01427ec4..dc99a4416f 100644 --- a/docs/source/upgrade-notes.rst +++ b/docs/source/upgrade-notes.rst @@ -73,6 +73,8 @@ UNRELEASED Schema is optional, run SQL when the node is not running. + Corda node will fail on startup if the correct table name is not present. + v3.0 to v3.1 ------------ diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt index 48087de79b..03a03c83e1 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt @@ -80,6 +80,8 @@ class CordaPersistence( // Check not in read-only mode. transaction { check(!connection.metaData.isReadOnly) { "Database should not be readonly." } + + checkCorrectAttachmentsContractsTableName(connection) } } @@ -266,4 +268,19 @@ private fun Throwable.hasSQLExceptionCause(): Boolean = else -> cause?.hasSQLExceptionCause() ?: false } -class CouldNotCreateDataSourceException(override val message: String?, override val cause: Throwable? = null) : Exception() \ No newline at end of file +class CouldNotCreateDataSourceException(override val message: String?, override val cause: Throwable? = null) : Exception() + +class IncompatibleAttachmentsContractsTableName(override val message: String?, override val cause: Throwable? = null) : Exception() + +private fun checkCorrectAttachmentsContractsTableName(connection: Connection) { + val correctName = "NODE_ATTACHMENTS_CONTRACTS" + val incorrectV30Name = "NODE_ATTACHMENTS_CONTRACT_CLASS_NAME" + val incorrectV31Name = "NODE_ATTCHMENTS_CONTRACTS" + + fun warning(incorrectName: String, version: String) = "The database contains the older table name $incorrectName instead of $correctName, see upgrade notes to migrate from Corda database version $version https://docs.corda.net/head/upgrade-notes.html." + + if (!connection.metaData.getTables(null, null, correctName, null).next()) { + if (connection.metaData.getTables(null, null, incorrectV30Name, null).next()) { throw IncompatibleAttachmentsContractsTableName(warning(incorrectV30Name, "3.0")) } + if (connection.metaData.getTables(null, null, incorrectV31Name, null).next()) { throw IncompatibleAttachmentsContractsTableName(warning(incorrectV31Name, "3.1")) } + } +} diff --git a/node/src/integration-test/kotlin/net/corda/node/persistence/FailNodeOnNotMigratedAttachmentContractsTableNameTests.kt b/node/src/integration-test/kotlin/net/corda/node/persistence/FailNodeOnNotMigratedAttachmentContractsTableNameTests.kt new file mode 100644 index 0000000000..7ee592193c --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/persistence/FailNodeOnNotMigratedAttachmentContractsTableNameTests.kt @@ -0,0 +1,66 @@ +package net.corda.node.persistence + +import net.corda.client.rpc.CordaRPCClient +import net.corda.core.internal.packageName +import net.corda.core.messaging.startFlow +import net.corda.core.utilities.getOrThrow +import net.corda.node.services.Permissions +import net.corda.testMessage.Message +import net.corda.testMessage.MessageState +import net.corda.testing.core.singleIdentity +import net.corda.testing.driver.DriverParameters +import net.corda.testing.driver.driver +import net.corda.testing.driver.internal.RandomFree +import net.corda.testing.node.User +import org.junit.Test +import java.nio.file.Path +import java.sql.DriverManager +import kotlin.test.* + +class FailNodeOnNotMigratedAttachmentContractsTableNameTests { + @Test + fun `node fails when detecting table name not migrated from version 3 dot 0`() { + `node fails when not detecting compatible table name`("NODE_ATTACHMENTS_CONTRACTS", "NODE_ATTACHMENTS_CONTRACT_CLASS_NAME") + } + + @Test + fun `node fails when detecting table name not migrated from version 3 dot 1`() { + `node fails when not detecting compatible table name`("NODE_ATTACHMENTS_CONTRACTS", "NODE_ATTCHMENTS_CONTRACTS") + } + + fun `node fails when not detecting compatible table name`(tableNameFromMapping: String, tableNameInDB: String) { + val user = User("mark", "dadada", setOf(Permissions.startFlow(), Permissions.invokeRpc("vaultQuery"))) + val message = Message("Hello world!") + val baseDir: Path = driver(DriverParameters(inMemoryDB = false, startNodesInProcess = isQuasarAgentSpecified(), + portAllocation = RandomFree, extraCordappPackagesToScan = listOf(MessageState::class.packageName))) { + val (nodeName, baseDir) = { + val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow() + val nodeName = nodeHandle.nodeInfo.singleIdentity().name + CordaRPCClient(nodeHandle.rpcAddress).start(user.username, user.password).use { + it.proxy.startFlow(::SendMessageFlow, message, defaultNotaryIdentity).returnValue.getOrThrow() + } + nodeHandle.stop() + Pair(nodeName, nodeHandle.baseDirectory) + }() + + // replace the correct table name with one from the former release + DriverManager.getConnection("jdbc:h2:file://$baseDir/persistence", "sa", "").use { + it.createStatement().execute("ALTER TABLE $tableNameFromMapping RENAME TO $tableNameInDB") + it.commit() + } + assertFailsWith(net.corda.nodeapi.internal.persistence.IncompatibleAttachmentsContractsTableName::class) { + val nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user)).getOrThrow() + nodeHandle.stop() + } + baseDir + } + + // check that the node didn't recreated the correct table matching it's entity mapping + val (hasTableFromMapping, hasTableFromDB) = DriverManager.getConnection("jdbc:h2:file://$baseDir/persistence", "sa", "").use { + Pair(it.metaData.getTables(null, null, tableNameFromMapping, null).next(), + it.metaData.getTables(null, null, tableNameInDB, null).next()) + } + assertFalse(hasTableFromMapping) + assertTrue(hasTableFromDB) + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 3ac42322d1..32f6d70614 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -136,10 +136,7 @@ import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.NodeInfoAndSigned import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.crypto.X509Utilities -import net.corda.nodeapi.internal.persistence.CordaPersistence -import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException -import net.corda.nodeapi.internal.persistence.DatabaseConfig -import net.corda.nodeapi.internal.persistence.HibernateConfiguration +import net.corda.nodeapi.internal.persistence.* import net.corda.nodeapi.internal.storeLegalIdentity import net.corda.tools.shell.InteractiveShell import org.apache.activemq.artemis.utils.ReusableLatch @@ -1128,6 +1125,7 @@ fun configureDatabase(hikariProperties: Properties, when { ex is HikariPool.PoolInitializationException -> throw CouldNotCreateDataSourceException("Could not connect to the database. Please check your JDBC connection URL, or the connectivity to the database.", ex) ex.cause is ClassNotFoundException -> throw CouldNotCreateDataSourceException("Could not find the database driver class. Please add it to the 'drivers' folder. See: https://docs.corda.net/corda-configuration-file.html") + ex is IncompatibleAttachmentsContractsTableName -> throw ex else -> throw CouldNotCreateDataSourceException("Could not create the DataSource: ${ex.message}", ex) } } diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index a4e87cedc7..5c3e9f3de2 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -32,6 +32,7 @@ import net.corda.node.utilities.saveToTrustStore import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.config.UnknownConfigurationKeysException import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException +import net.corda.nodeapi.internal.persistence.IncompatibleAttachmentsContractsTableName import net.corda.tools.shell.InteractiveShell import org.fusesource.jansi.Ansi import org.fusesource.jansi.AnsiConsole @@ -161,6 +162,10 @@ open class NodeStartup(val args: Array) { } catch (e: NetworkParametersReader.Error) { logger.error(e.message) return false + } catch (e: IncompatibleAttachmentsContractsTableName) { + e.message?.let { Node.printWarning(it) } + logger.error(e.message) + return false } catch (e: Exception) { if (e is Errors.NativeIoException && e.message?.contains("Address already in use") == true) { logger.error("One of the ports required by the Corda node is already in use.") From a4ad4c75637e90cc25df3b0c5c831094b5b0e096 Mon Sep 17 00:00:00 2001 From: Anthony Keenan Date: Fri, 13 Jul 2018 22:12:20 +0100 Subject: [PATCH 09/10] =?UTF-8?q?CORDA-1725=20Add=20documentation=20on=20b?= =?UTF-8?q?uilding=20Corda=20for=20Win/*nix=20and=20clean=20up=20doc=20bui?= =?UTF-8?q?=E2=80=A6=20(#3578)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add documentation on building Corda for Win/*nix and clean up doc building documentation * Split up building corda instructions from building docs instructions * Address review comments --- docs/source/building-corda.rst | 56 ++++++++++++++++ docs/source/building-the-docs.rst | 105 ++++++++++++++++++++++++------ docs/source/index.rst | 1 + 3 files changed, 142 insertions(+), 20 deletions(-) create mode 100644 docs/source/building-corda.rst diff --git a/docs/source/building-corda.rst b/docs/source/building-corda.rst new file mode 100644 index 0000000000..9c423ebd42 --- /dev/null +++ b/docs/source/building-corda.rst @@ -0,0 +1,56 @@ +Building Corda +============== + +These instructions are for downloading and building the Corda code locally. If you only wish to develop CorDapps for +use on Corda, you don't need to do this, follow the instructions at :doc:`getting-set-up` and use the precompiled binaries. + +Windows +------- + +Java +~~~~ +1. Visit http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html +2. Scroll down to "Java SE Development Kit 8uXXX" (where "XXX" is the latest minor version number) +3. Toggle "Accept License Agreement" +4. Click the download link for jdk-8uXXX-windows-x64.exe (where "XXX" is the latest minor version number) +5. Download and run the executable to install Java (use the default settings) +6. Add Java to the PATH environment variable by following the instructions at https://docs.oracle.com/javase/7/docs/webnotes/install/windows/jdk-installation-windows.html#path +7. Open a new command prompt and run ``java -version`` to test that Java is installed correctly + +Git +~~~ + +1. Visit https://git-scm.com/download/win +2. Click the "64-bit Git for Windows Setup" download link. +3. Download and run the executable to install Git (use the default installation values) and make a note of the installation directory. +4. Open a new command prompt and type ``git --version`` to test that Git is installed correctly + +Buillding Corda +~~~~~~~~~~~~~~~ + +1. Open a command prompt +2. Run ``git clone https://github.com/corda/corda.git`` +3. Run ``gradlew build`` + + +Debian/Ubuntu Linux +------------------- + +These instructions were tested on Ubuntu Server 18.04 LTS. This distribution includes ``git`` and ``python`` so only the following steps are required: + +Java +~~~~ +1. Run ``sudo add-apt-repository ppa:webupd8team/java`` from the terminal. Press ENTER when prompted. +2. Run ``sudo apt-get update`` +3. Then run ``sudo apt-get install oracle-java8-installer``. Press Y when prompted and agree to the licence terms. +4. Run ``java --version`` to verify that java is installed correctly + + +Building Corda +~~~~~~~~~~~~~~ + +1. Open the terminal +2. Run ``git clone https://github.com/corda/corda.git`` +3. Run ``./gradlew build`` + + diff --git a/docs/source/building-the-docs.rst b/docs/source/building-the-docs.rst index 4ece4e4997..2bf1b5dcdd 100644 --- a/docs/source/building-the-docs.rst +++ b/docs/source/building-the-docs.rst @@ -7,44 +7,109 @@ is pre-generated, as well as code documentation, and this can be done automatica Requirements ------------ -To build the documentation, you will need: +In order to build the documentation you will need a development environment set up as described under :doc:`building-corda`. -* GNU Make -* Python and pip (tested with Python 2.7.10) -* Sphinx: http://www.sphinx-doc.org/ -* sphinx_rtd_theme: https://github.com/snide/sphinx_rtd_theme +You will also need additional dependencies based on your O/S which are detailed below. -Note that to install under OS X El Capitan, you will need to tell pip to install under ``/usr/local``, which can be -done by specifying the installation target on the command line: +Windows +------- -.. sourcecode:: shell +Git, bash and make +~~~~~~~~~~~~~~~~~~ + +In order to build the documentation for Corda you need a ``bash`` emulator with ``make`` installed and accessible from the command prompt. Git for +Windows ships with a version of MinGW that contains a ``bash`` emulator, to which you can download and add a Windows port of +``make``, instructions for which are provided below. Alternatively you can install a full version of MinGW from `here `_. + +1. Go to `ezwinports `_ and click the download for ``make-4.2.1-without-guile-w32-bin.zip`` +2. Navigate to the Git installation directory (by default ``C:\Program Files\Git``), open ``mingw64`` +3. Unzip the downloaded file into this directory, but do NOT overwrite/replace any existing files +4. Add the Git ``bin`` directory to your system PATH environment variable (by default ``C:\Program Files\Git\bin``) +5. Open a new command prompt and run ``bash`` to test that you can access the Git bash emulator +6. Type ``make`` to make sure it has been installed successfully (you should get an error + like ``make: *** No targets specified and no makefile found. Stop.``) + + +Python, Pip and VirtualEnv +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. Visit https://www.python.org/downloads +2. Scroll down to the most recent v2 release (tested with v.2.7.15) and click the download link +3. Download the "Windows x86-64 MSI installer" +4. Run the installation, making a note of the Python installation directory (defaults to ``c:\Python27``) +5. Add the Python installation directory (e.g. ``c:\Python27``) to your system PATH environment variable +6. Add the Python scripts sub-directory (e.g. ``c:\Python27\scripts``) to your system PATH environment variable +7. Open a new command prompt and check you can run Python by running ``python --version`` +8. Check you can run pip by running ``pip --version`` +9. Install ``virtualenv`` by running ``pip install virtualenv`` from the commandline +10. Check you can run ``virualenv`` by running ``virtualenv --version`` from the commandline. + +LaTeX +~~~~~ + +Corda requires LaTeX to be available for building the documentation. The instructions below are for installing TeX Live +but other distributions are available. + +1. Visit https://tug.org/texlive/ +2. Click download +3. Download and run ``install-tl-windows.exe`` +4. Keep the default options (simple installation is fine) +5. Open a new command prompt and check you can run ``pdflatex`` by running ``pdflatex --version`` + + +Debian/Ubuntu Linux +------------------- + +These instructions were tested on Ubuntu Server 18.04 LTS. This distribution includes ``git`` and ``python`` so only the following steps are required: + +Pip/VirtualEnv +~~~~~~~~~~~~~~ + +1. Run ``sudo apt-get install python-pip`` +2. Run ``pip install virtualenv`` +3. Run ``pip --version`` to verify that pip is installed correctly +4. Run ``virtualenv --version`` to verify that virtualenv is installed correctly + +LaTeX +~~~~~ + +Corda requires LaTeX to be available for building the documentation. The instructions below are for installing TeX Live +but other distributions are available. + +1. Run ``sudo apt-get install texlive-full`` - sudo -H pip install --install-option '--install-data=/usr/local' Sphinx - sudo -H pip install --install-option '--install-data=/usr/local' sphinx_rtd_theme - -.. warning:: When installing Sphinx, you may see the following error message: "Found existing installation: six 1.4.1 - Cannot uninstall 'six'. It is a distutils installed project and thus we cannot accurately determine which files - belong to it which would lead to only a partial uninstall.". If so, run the install with the - ``--ignore-installed six`` flag. Build ----- -Once the requirements are installed, you can automatically build the HTML format user documentation and the API -documentation by running the following script: +Once the requirements are installed, you can automatically build the HTML format user documentation, PDF, and +the API documentation by running the following script: .. sourcecode:: shell + // On Windows + gradlew buildDocs + + // On Mac and Linux ./gradlew buildDocs -Alternatively you can build non-HTML formats from the ``docs`` folder. Change directory to the folder and then run the -following to see a list of all available formats: +Alternatively you can build non-HTML formats from the ``docs`` folder. + +However, running ``make`` from the command line requires further dependencies to be installed. When building in Gradle they +are installed in a `python virtualenv `_, so they will need explicitly installing +by running: + +.. sourcecode:: shell + + pip install -r requirements.txt + +Change directory to the ``docs`` folder and then run the following to see a list of all available formats: .. sourcecode:: shell make -For example to produce the documentation in HTML format: +For example to produce the documentation in HTML format run: .. sourcecode:: shell diff --git a/docs/source/index.rst b/docs/source/index.rst index 1ba5e5757b..04b65a85d9 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -84,6 +84,7 @@ We look forward to seeing what you can do with Corda! :caption: Participate :maxdepth: 2 + building-corda.rst release-process-index.rst corda-repo-layout.rst deterministic-modules.rst From bf4f30fd39ec84f3722bcc1ae1e517845ce30bac Mon Sep 17 00:00:00 2001 From: Konstantinos Chalkias Date: Sat, 14 Jul 2018 05:21:02 +0100 Subject: [PATCH 10/10] Use newSecureRandom instead of SecureRandom (#3606) --- .../corda/nodeapi/internal/protonwrapper/netty/SSLHelper.kt | 5 +++-- .../net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt | 3 ++- .../kotlin/net/corda/node/amqp/ProtonWrapperTests.kt | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/SSLHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/SSLHelper.kt index 0f799a8164..1f4328a8ee 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/SSLHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/SSLHelper.kt @@ -1,6 +1,7 @@ package net.corda.nodeapi.internal.protonwrapper.netty import io.netty.handler.ssl.SslHandler +import net.corda.core.crypto.newSecureRandom import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger import net.corda.core.utilities.toHex @@ -107,7 +108,7 @@ internal fun createClientSslHelper(target: NetworkHostAndPort, val sslContext = SSLContext.getInstance("TLS") val keyManagers = keyManagerFactory.keyManagers val trustManagers = trustManagerFactory.trustManagers.filterIsInstance(X509ExtendedTrustManager::class.java).map { LoggingTrustManagerWrapper(it) }.toTypedArray() - sslContext.init(keyManagers, trustManagers, SecureRandom()) + sslContext.init(keyManagers, trustManagers, newSecureRandom()) val sslEngine = sslContext.createSSLEngine(target.host, target.port) sslEngine.useClientMode = true sslEngine.enabledProtocols = ArtemisTcpTransport.TLS_VERSIONS.toTypedArray() @@ -121,7 +122,7 @@ internal fun createServerSslHelper(keyManagerFactory: KeyManagerFactory, val sslContext = SSLContext.getInstance("TLS") val keyManagers = keyManagerFactory.keyManagers val trustManagers = trustManagerFactory.trustManagers.filterIsInstance(X509ExtendedTrustManager::class.java).map { LoggingTrustManagerWrapper(it) }.toTypedArray() - sslContext.init(keyManagers, trustManagers, SecureRandom()) + sslContext.init(keyManagers, trustManagers, newSecureRandom()) val sslEngine = sslContext.createSSLEngine() sslEngine.useClientMode = false sslEngine.needClientAuth = true diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt index 530829bedd..1df89e1070 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt @@ -3,6 +3,7 @@ package net.corda.nodeapi.internal.crypto import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512 import net.corda.core.crypto.Crypto.generateKeyPair +import net.corda.core.crypto.newSecureRandom import net.corda.core.identity.CordaX500Name import net.corda.core.internal.div import net.corda.core.serialization.SerializationContext @@ -238,7 +239,7 @@ class X509UtilitiesTest { val trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) trustMgrFactory.init(trustStore) val trustManagers = trustMgrFactory.trustManagers - context.init(keyManagers, trustManagers, SecureRandom()) + context.init(keyManagers, trustManagers, newSecureRandom()) val serverSocketFactory = context.serverSocketFactory val clientSocketFactory = context.socketFactory diff --git a/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt b/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt index 7293e8df8d..e88376fea0 100644 --- a/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt @@ -4,6 +4,7 @@ import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import io.netty.channel.EventLoopGroup import io.netty.channel.nio.NioEventLoopGroup +import net.corda.core.crypto.newSecureRandom import net.corda.core.identity.CordaX500Name import net.corda.core.internal.div import net.corda.core.toFuture @@ -129,7 +130,7 @@ class ProtonWrapperTests { val trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) trustMgrFactory.init(trustStore) val trustManagers = trustMgrFactory.trustManagers - context.init(keyManagers, trustManagers, SecureRandom()) + context.init(keyManagers, trustManagers, newSecureRandom()) val serverSocketFactory = context.serverSocketFactory