From 61fedb5fd241a840ac728a5418cadec798991393 Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Thu, 24 May 2018 13:20:04 +0100 Subject: [PATCH 01/13] [CORDA-1528]: Node configuration not containing property "rpcSettings.address" fails with error "No configuration setting found for key 'address'" (fix). (#3229) --- .../internal/config/ConfigUtilities.kt | 128 ++++++++++-------- .../node/services/config/NodeConfiguration.kt | 14 +- .../config/NodeConfigurationImplTest.kt | 28 +++- node/src/test/resources/working-config.conf | 31 +++++ 4 files changed, 139 insertions(+), 62 deletions(-) create mode 100644 node/src/test/resources/working-config.conf 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 e7c7e885cf..7d511ac61c 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 @@ -2,7 +2,12 @@ package net.corda.nodeapi.internal.config -import com.typesafe.config.* +import com.typesafe.config.Config +import com.typesafe.config.ConfigException +import com.typesafe.config.ConfigFactory +import com.typesafe.config.ConfigUtil +import com.typesafe.config.ConfigValueFactory +import com.typesafe.config.ConfigValueType import net.corda.core.identity.CordaX500Name import net.corda.core.internal.noneOrSingle import net.corda.core.internal.uncheckedCast @@ -39,7 +44,7 @@ operator fun Config.getValue(receiver: Any, metadata: KProperty<*>): T return getValueInternal(metadata.name, metadata.returnType, UnknownConfigKeysPolicy.IGNORE::handle) } -fun Config.parseAs(clazz: KClass, onUnknownKeys: ((Set, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle): T { +fun Config.parseAs(clazz: KClass, onUnknownKeys: ((Set, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle, nestedPath: String? = null): T { require(clazz.isData) { "Only Kotlin data classes can be parsed. Offending: ${clazz.qualifiedName}" } val constructor = clazz.primaryConstructor!! val parameters = constructor.parameters @@ -59,11 +64,11 @@ fun Config.parseAs(clazz: KClass, onUnknownKeys: ((Set, log onUnknownKeys.invoke(unknownConfigurationKeys, logger) val args = parameters.filterNot { it.isOptional && !hasPath(it.name!!) }.associateBy({ it }) { param -> - // Get the matching property for this parameter - val property = clazz.memberProperties.first { it.name == param.name } - val path = defaultToOldPath(property) - getValueInternal(path, param.type, onUnknownKeys) - } + // Get the matching property for this parameter + val property = clazz.memberProperties.first { it.name == param.name } + val path = defaultToOldPath(property) + getValueInternal(path, param.type, onUnknownKeys, nestedPath) + } try { return constructor.callBy(args) } catch (e: InvocationTargetException) { @@ -91,68 +96,83 @@ fun Config.toProperties(): Properties { { it.value.unwrapped().toString() }) } -private fun Config.getValueInternal(path: String, type: KType, onUnknownKeys: ((Set, logger: Logger) -> Unit)): T { - return uncheckedCast(if (type.arguments.isEmpty()) getSingleValue(path, type, onUnknownKeys) else getCollectionValue(path, type, onUnknownKeys)) +private fun Config.getValueInternal(path: String, type: KType, onUnknownKeys: ((Set, logger: Logger) -> Unit), nestedPath: String? = null): T { + return uncheckedCast(if (type.arguments.isEmpty()) getSingleValue(path, type, onUnknownKeys, nestedPath) else getCollectionValue(path, type, onUnknownKeys, nestedPath)) } -private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set, logger: Logger) -> Unit): Any? { +private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set, logger: Logger) -> Unit, nestedPath: String? = null): Any? { if (type.isMarkedNullable && !hasPath(path)) return null val typeClass = type.jvmErasure - return when (typeClass) { - String::class -> getString(path) - Int::class -> getInt(path) - Long::class -> getLong(path) - Double::class -> getDouble(path) - Boolean::class -> getBoolean(path) - LocalDate::class -> LocalDate.parse(getString(path)) - Duration::class -> getDuration(path) - Instant::class -> Instant.parse(getString(path)) - NetworkHostAndPort::class -> NetworkHostAndPort.parse(getString(path)) - Path::class -> Paths.get(getString(path)) - URL::class -> URL(getString(path)) - UUID::class -> UUID.fromString(getString(path)) - CordaX500Name::class -> { - when (getValue(path).valueType()) { - ConfigValueType.OBJECT -> getConfig(path).parseAs(onUnknownKeys) - else -> CordaX500Name.parse(getString(path)) + return try { + when (typeClass) { + String::class -> getString(path) + Int::class -> getInt(path) + Long::class -> getLong(path) + Double::class -> getDouble(path) + Boolean::class -> getBoolean(path) + LocalDate::class -> LocalDate.parse(getString(path)) + Duration::class -> getDuration(path) + Instant::class -> Instant.parse(getString(path)) + NetworkHostAndPort::class -> NetworkHostAndPort.parse(getString(path)) + Path::class -> Paths.get(getString(path)) + URL::class -> URL(getString(path)) + UUID::class -> UUID.fromString(getString(path)) + CordaX500Name::class -> { + when (getValue(path).valueType()) { + ConfigValueType.OBJECT -> getConfig(path).parseAs(onUnknownKeys) + else -> CordaX500Name.parse(getString(path)) + } + } + Properties::class -> getConfig(path).toProperties() + Config::class -> getConfig(path) + else -> if (typeClass.java.isEnum) { + parseEnum(typeClass.java, getString(path)) + } else { + getConfig(path).parseAs(typeClass, onUnknownKeys, nestedPath?.let { "$it.$path" } ?: path) } } - Properties::class -> getConfig(path).toProperties() - Config::class -> getConfig(path) - else -> if (typeClass.java.isEnum) { - parseEnum(typeClass.java, getString(path)) - } else { - getConfig(path).parseAs(typeClass, onUnknownKeys) - } + } catch (e: ConfigException.Missing) { + throw e.relative(path, nestedPath) } } -private fun Config.getCollectionValue(path: String, type: KType, onUnknownKeys: (Set, logger: Logger) -> Unit): Collection { +private fun ConfigException.Missing.relative(path: String, nestedPath: String?): ConfigException.Missing { + return when { + nestedPath != null -> throw ConfigException.Missing("$nestedPath.$path") + else -> this + } +} + +private fun Config.getCollectionValue(path: String, type: KType, onUnknownKeys: (Set, logger: Logger) -> Unit, nestedPath: String? = null): Collection { val typeClass = type.jvmErasure require(typeClass == List::class || typeClass == Set::class) { "$typeClass is not supported" } val elementClass = type.arguments[0].type?.jvmErasure ?: throw IllegalArgumentException("Cannot work with star projection: $type") if (!hasPath(path)) { return if (typeClass == List::class) emptyList() else emptySet() } - val values: List = when (elementClass) { - String::class -> getStringList(path) - Int::class -> getIntList(path) - Long::class -> getLongList(path) - Double::class -> getDoubleList(path) - Boolean::class -> getBooleanList(path) - LocalDate::class -> getStringList(path).map(LocalDate::parse) - Instant::class -> getStringList(path).map(Instant::parse) - NetworkHostAndPort::class -> getStringList(path).map(NetworkHostAndPort.Companion::parse) - Path::class -> getStringList(path).map { Paths.get(it) } - URL::class -> getStringList(path).map(::URL) - UUID::class -> getStringList(path).map { UUID.fromString(it) } - CordaX500Name::class -> getStringList(path).map(CordaX500Name.Companion::parse) - Properties::class -> getConfigList(path).map(Config::toProperties) - else -> if (elementClass.java.isEnum) { - getStringList(path).map { parseEnum(elementClass.java, it) } - } else { - getConfigList(path).map { it.parseAs(elementClass, onUnknownKeys) } + val values: List = try { + when (elementClass) { + String::class -> getStringList(path) + Int::class -> getIntList(path) + Long::class -> getLongList(path) + Double::class -> getDoubleList(path) + Boolean::class -> getBooleanList(path) + LocalDate::class -> getStringList(path).map(LocalDate::parse) + Instant::class -> getStringList(path).map(Instant::parse) + NetworkHostAndPort::class -> getStringList(path).map(NetworkHostAndPort.Companion::parse) + Path::class -> getStringList(path).map { Paths.get(it) } + URL::class -> getStringList(path).map(::URL) + UUID::class -> getStringList(path).map { UUID.fromString(it) } + CordaX500Name::class -> getStringList(path).map(CordaX500Name.Companion::parse) + Properties::class -> getConfigList(path).map(Config::toProperties) + else -> if (elementClass.java.isEnum) { + getStringList(path).map { parseEnum(elementClass.java, it) } + } else { + getConfigList(path).map { it.parseAs(elementClass, onUnknownKeys) } + } } + } catch (e: ConfigException.Missing) { + throw e.relative(path, nestedPath) } return if (typeClass == Set::class) values.toSet() else values } @@ -219,7 +239,7 @@ private fun Any.toConfigMap(): Map { private fun Iterable<*>.toConfigIterable(field: Field): Iterable { val elementType = (field.genericType as ParameterizedType).actualTypeArguments[0] as Class<*> return when (elementType) { - // For the types already supported by Config we can use the Iterable as is + // For the types already supported by Config we can use the Iterable as is String::class.java -> this Integer::class.java -> this java.lang.Long::class.java -> this 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 e5fc1213a5..bedb27badd 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 @@ -1,6 +1,7 @@ package net.corda.node.services.config import com.typesafe.config.Config +import com.typesafe.config.ConfigException import net.corda.core.context.AuthServiceId import net.corda.core.identity.CordaX500Name import net.corda.core.internal.div @@ -184,7 +185,10 @@ data class NodeConfigurationImpl( logger.warn("Top-level declaration of property 'rpcAddress' is deprecated. Please use 'rpcSettings.address' instead.") settings.copy(address = explicitAddress) } - else -> settings + else -> { + settings.address ?: throw ConfigException.Missing("rpcSettings.address") + settings + } }.asOptions(fallbackSslOptions) } @@ -256,16 +260,16 @@ data class NodeConfigurationImpl( } data class NodeRpcSettings( - val address: NetworkHostAndPort, - val adminAddress: NetworkHostAndPort, + val address: NetworkHostAndPort?, + val adminAddress: NetworkHostAndPort?, val standAloneBroker: Boolean = false, val useSsl: Boolean = false, val ssl: BrokerRpcSslOptions? ) { fun asOptions(fallbackSslOptions: BrokerRpcSslOptions): NodeRpcOptions { return object : NodeRpcOptions { - override val address = this@NodeRpcSettings.address - override val adminAddress = this@NodeRpcSettings.adminAddress + override val address = this@NodeRpcSettings.address!! + override val adminAddress = this@NodeRpcSettings.adminAddress!! override val standAloneBroker = this@NodeRpcSettings.standAloneBroker override val useSsl = this@NodeRpcSettings.useSsl override val sslConfig = this@NodeRpcSettings.ssl ?: fallbackSslOptions 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 2b51199b56..6bf6553cbb 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 @@ -1,22 +1,23 @@ package net.corda.node.services.config import com.typesafe.config.Config +import com.typesafe.config.ConfigException import com.typesafe.config.ConfigFactory -import net.corda.core.internal.div +import com.typesafe.config.ConfigParseOptions +import com.typesafe.config.ConfigValueFactory import net.corda.core.internal.toPath import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.seconds -import net.corda.nodeapi.BrokerRpcSslOptions import net.corda.testing.core.ALICE_NAME import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.tools.shell.SSHDConfiguration import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatCode import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test import java.net.URI import java.net.URL import java.nio.file.Paths -import java.util.* import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -133,6 +134,27 @@ class NodeConfigurationImplTest { assertThat(errors).hasOnlyOneElementSatisfying { error -> error.contains("compatibilityZoneURL") && error.contains("devMode") } } + @Test + fun `errors for nested config keys contain path`() { + var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false)) + val missingPropertyPath = "rpcSettings.address" + rawConfig = rawConfig.withoutPath(missingPropertyPath) + + assertThatThrownBy { rawConfig.parseAsNodeConfiguration() }.isInstanceOfSatisfying(ConfigException.Missing::class.java) { exception -> + assertThat(exception.message).isNotNull() + assertThat(exception.message).contains(missingPropertyPath) + } + } + + @Test + fun `rpcAddress and rpcSettings_address are equivalent`() { + var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false)) + rawConfig = rawConfig.withoutPath("rpcSettings.address") + rawConfig = rawConfig.withValue("rpcAddress", ConfigValueFactory.fromAnyRef("localhost:4444")) + + assertThatCode { rawConfig.parseAsNodeConfiguration() }.doesNotThrowAnyException() + } + private fun configDebugOptions(devMode: Boolean, devModeOptions: DevModeOptions?): NodeConfiguration { return testConfiguration.copy(devMode = devMode, devModeOptions = devModeOptions) } diff --git a/node/src/test/resources/working-config.conf b/node/src/test/resources/working-config.conf new file mode 100644 index 0000000000..45ca6ef647 --- /dev/null +++ b/node/src/test/resources/working-config.conf @@ -0,0 +1,31 @@ +myLegalName = "O=Alice Corp, L=Madrid, C=ES" +emailAddress = "admin@company.com" +keyStorePassword = "cordacadevpass" +trustStorePassword = "trustpass" +crlCheckSoftFail = true +baseDirectory = "/opt/corda" +dataSourceProperties = { + dataSourceClassName = org.h2.jdbcx.JdbcDataSource + dataSource.url = "jdbc:h2:file:blah" + dataSource.user = "sa" + dataSource.password = "" +} +database = { + transactionIsolationLevel = "REPEATABLE_READ" + exportHibernateJMXStatistics = "false" +} +p2pAddress = "localhost:2233" +h2port = 0 +useTestClock = false +verifierType = InMemory +rpcSettings = { + address = "locahost:3418" + adminAddress = "localhost:3419" + useSsl = false + standAloneBroker = false +} +p2pMessagingRetry { + messageRedeliveryDelay = 30 seconds + maxRetryCount = 3 + backoffBase = 2.0 +} \ No newline at end of file From 645163e9cc93d5e7d266f90f598715f7ce617ea7 Mon Sep 17 00:00:00 2001 From: Konstantinos Chalkias Date: Thu, 24 May 2018 14:30:36 +0100 Subject: [PATCH 02/13] Fixing node keys bullet points (#3207) --- docs/source/cipher-suites.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/source/cipher-suites.rst b/docs/source/cipher-suites.rst index 8991adcd4e..6ffa5f8d99 100644 --- a/docs/source/cipher-suites.rst +++ b/docs/source/cipher-suites.rst @@ -24,12 +24,15 @@ Certificate hierarchy A Corda network has 8 types of keys and a regular node requires 4 of them: +**Network Keys** + * The **root network CA** key * The **doorman CA** key * The **network map** key -* The **service identity** key(s) (per service, such as a notary cluster; it can be a Composite Key) +* The **service identity** key(s) (per service, such as a notary cluster; it can be a Composite key) + +**Node Keys** --- **Node Keys** -- * The **node CA** key(s) (one per node) * The **legal identity** key(s) (one per node) * The **tls** key(s) (per node) From 4adc0380a2f98d249dbec0cc1c41c4e1bbc099af Mon Sep 17 00:00:00 2001 From: Anthony Keenan Date: Thu, 24 May 2018 14:43:58 +0100 Subject: [PATCH 03/13] CORDA-1496 Add api-scanner documentation (#3184) Add api-scanner documentation --- docs/source/api-scanner.rst | 58 +++++++++++++++++++++++++++ docs/source/contributing.rst | 6 +++ docs/source/corda-api.rst | 2 + docs/source/release-process-index.rst | 3 +- 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 docs/source/api-scanner.rst diff --git a/docs/source/api-scanner.rst b/docs/source/api-scanner.rst new file mode 100644 index 0000000000..45e0d096c1 --- /dev/null +++ b/docs/source/api-scanner.rst @@ -0,0 +1,58 @@ +API stability check +=================== + +We have committed not to alter Corda's API so that developers will not have to keep rewriting their CorDapps with each +new Corda release. The stable Corda modules are listed :ref:`here `. Our CI process runs an "API Stability" +check for each GitHub pull request in order to check that we don't accidentally introduce an API-breaking change. + +Build Process +------------- + +As part of the build process the following commands are run for each PR: + +.. code-block:: shell + + $ gradlew generateApi + $ .ci/check-api-changes.sh + +This ``bash`` script has been tested on both MacOS and various Linux distributions, it can also be run on Windows with the +use of a suitable bash emulator such as git bash. The script's return value is the number of API-breaking changes that it +has detected, and this should be zero for the check to pass. The maximum return value is 255, although the script will still +correctly report higher numbers of breaking changes. + +There are three kinds of breaking change: + +* Removal or modification of existing API, i.e. an existing class, method or field has been either deleted or renamed, or + its signature somehow altered. +* Addition of a new method to an interface or abstract class. Types that have been annotated as ``@DoNotImplement`` are + excluded from this check. (This annotation is also inherited across subclasses and subinterfaces.) +* Exposure of an internal type via a public API. Internal types are considered to be anything in a ``*.internal.`` package + or anything in a module that isn't in the stable modules list :ref:`here `. + +Developers can execute these commands themselves before submitting their PR, to ensure that they haven't inadvertently +broken Corda's API. + + +How it works +------------ + +The ``generateApi`` Gradle task writes a summary of Corda's public API into the file ``build/api/api-corda-.txt``. +The ``.ci/check-api-changes.sh`` script then compares this file with the contents of ``.ci/api-current.txt``, which is a +managed file within the Corda repository. + +The Gradle task itself is implemented by the API Scanner plugin. More information on the API Scanner plugin is available `here `_. + + +Updating the API +---------------- + +As a rule, ``api-current.txt`` should only be updated by the release manager for each Corda release. + +We do not expect modifications to ``api-current.txt`` as part of normal development. However, we may sometimes need to adjust +the public API in ways that would not break developers' CorDapps but which would be blocked by the API Stabilty check. +For example, migrating a method from an interface into a superinterface. Any changes to the API summary file should be +included in the PR, which would then need explicit approval from either `Mike Hearn `_, `Rick Parker `_ or `Matthew Nesbit `_. + +.. note:: If you need to modify ``api-current.txt``, do not re-generate the file on the master branch. This will include new API that + hasn't been released or committed to, and may be subject to change. Manually change the specific line or lines of the + existing committed API that has changed. \ No newline at end of file diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index 3bfff458e4..0baf3c6efc 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -102,6 +102,12 @@ Building against the master branch You can test your changes against CorDapps defined in other repos by following the instructions :doc:`here `. +Running the API scanner +^^^^^^^^^^^^^^^^^^^^^^^ +Your changes must also not break compatibility with existing public API. We have an API scanning tool which runs as part of the build +process which can be used to flag up any accidental changes, which is detailed :doc:`here `. + + Updating the docs ----------------- diff --git a/docs/source/corda-api.rst b/docs/source/corda-api.rst index f0f803a227..bcb150c3c4 100644 --- a/docs/source/corda-api.rst +++ b/docs/source/corda-api.rst @@ -21,6 +21,8 @@ The following are the core APIs that are used in the development of CorDapps: Before reading this page, you should be familiar with the :doc:`key concepts of Corda `. +.. _internal-apis-and-stability-guarantees: + Internal APIs and stability guarantees -------------------------------------- diff --git a/docs/source/release-process-index.rst b/docs/source/release-process-index.rst index b269691a86..9ef0c82347 100644 --- a/docs/source/release-process-index.rst +++ b/docs/source/release-process-index.rst @@ -8,4 +8,5 @@ Release process changelog contributing codestyle - testing \ No newline at end of file + testing + api-scanner \ No newline at end of file From d1e147b1c13cc0fe488441c125d791a072062769 Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Thu, 24 May 2018 16:06:14 +0100 Subject: [PATCH 04/13] [CORDA-1638]: Audit log of failed transactions MVP. (#3231) --- .../net/corda/core/flows/FinalityFlow.kt | 21 ++++++++--- .../kotlin/net/corda/core/flows/NotaryFlow.kt | 4 ++ .../core/flows/ReceiveTransactionFlow.kt | 18 ++++++--- .../net/corda/core/internal/InternalUtils.kt | 37 +++++++++++++++++-- docs/source/changelog.rst | 2 + 5 files changed, 68 insertions(+), 14 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt b/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt index a958ef6a91..3e16992191 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt @@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.core.crypto.isFulfilledBy import net.corda.core.identity.Party import net.corda.core.identity.groupAbstractPartyByWellKnownParty +import net.corda.core.internal.pushToLoggingContext import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.ProgressTracker @@ -51,17 +52,24 @@ class FinalityFlow(val transaction: SignedTransaction, // // Lookup the resolved transactions and use them to map each signed transaction to the list of participants. // Then send to the notary if needed, record locally and distribute. + + transaction.pushToLoggingContext() + val commandDataTypes = transaction.tx.commands.map { it.value }.mapNotNull { it::class.qualifiedName }.distinct() + logger.info("Started finalization, commands are ${commandDataTypes.joinToString(", ", "[", "]")}.") val parties = getPartiesToSend(verifyTx()) val notarised = notariseAndRecord() // Each transaction has its own set of recipients, but extra recipients get them all. progressTracker.currentStep = BROADCASTING - for (party in parties) { - if (!serviceHub.myInfo.isLegalIdentity(party)) { - val session = initiateFlow(party) - subFlow(SendTransactionFlow(session, notarised)) - } + val recipients = parties.filterNot(serviceHub.myInfo::isLegalIdentity) + logger.info("Broadcasting transaction to parties ${recipients.map { it.name }.joinToString(", ", "[", "]")}.") + for (party in recipients) { + logger.info("Sending transaction to party ${party.name}.") + val session = initiateFlow(party) + subFlow(SendTransactionFlow(session, notarised)) + logger.info("Party ${party.name} received the transaction.") } + logger.info("All parties received the transaction successfully.") return notarised } @@ -73,9 +81,12 @@ class FinalityFlow(val transaction: SignedTransaction, val notarySignatures = subFlow(NotaryFlow.Client(transaction)) transaction + notarySignatures } else { + logger.info("No need to notarise this transaction.") transaction } + logger.info("Recording transaction locally.") serviceHub.recordTransactions(notarised) + logger.info("Recorded transaction locally successfully.") return notarised } diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt index b4a4f874ec..bfe80e7c7e 100644 --- a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt @@ -9,6 +9,7 @@ import net.corda.core.identity.Party import net.corda.core.internal.FetchDataFlow import net.corda.core.internal.notary.generateSignature import net.corda.core.internal.notary.validateSignatures +import net.corda.core.internal.pushToLoggingContext import net.corda.core.transactions.ContractUpgradeWireTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction @@ -44,9 +45,12 @@ class NotaryFlow { @Suspendable @Throws(NotaryException::class) override fun call(): List { + stx.pushToLoggingContext() val notaryParty = checkTransaction() progressTracker.currentStep = REQUESTING + logger.info("Sending transaction to notary: ${notaryParty.name}.") val response = notarise(notaryParty) + logger.info("Notary responded.") progressTracker.currentStep = VALIDATING return validateResponse(response, notaryParty) } diff --git a/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt b/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt index 76dc39dba7..66f17daccb 100644 --- a/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt @@ -3,6 +3,7 @@ package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.* import net.corda.core.internal.ResolveTransactionsFlow +import net.corda.core.internal.pushToLoggingContext import net.corda.core.node.StatesToRecord import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.unwrap @@ -36,18 +37,25 @@ class ReceiveTransactionFlow @JvmOverloads constructor(private val otherSideSess } else { logger.trace("Receiving a transaction (but without checking the signatures) from ${otherSideSession.counterparty}") } - val stx = otherSideSession.receive().unwrap { + it.pushToLoggingContext() + logger.info("Received transaction acknowledgement request from party ${otherSideSession.counterparty.name}.") subFlow(ResolveTransactionsFlow(it, otherSideSession)) - it.verify(serviceHub, checkSufficientSignatures) - it + logger.info("Transaction dependencies resolution completed.") + try { + it.verify(serviceHub, checkSufficientSignatures) + it + } catch (e: Exception) { + logger.warn("Transaction verification failed.") + throw e + } } - if (checkSufficientSignatures) { // We should only send a transaction to the vault for processing if we did in fact fully verify it, and // there are no missing signatures. We don't want partly signed stuff in the vault. - logger.trace("Successfully received fully signed tx ${stx.id}, sending to the vault for processing") + logger.info("Successfully received fully signed tx. Sending it to the vault for processing.") serviceHub.recordTransactions(statesToRecord, setOf(stx)) + logger.info("Successfully recorded received transaction locally.") } return stx } diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index c8059035da..5f4472b884 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -7,13 +7,19 @@ import com.google.common.hash.HashingInputStream import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.CordappConfig import net.corda.core.cordapp.CordappContext -import net.corda.core.crypto.* +import net.corda.core.crypto.Crypto +import net.corda.core.crypto.DigitalSignature +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.SignedData +import net.corda.core.crypto.sha256 +import net.corda.core.crypto.sign import net.corda.core.identity.CordaX500Name import net.corda.core.node.ServicesForResolution import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize +import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.OpaqueBytes @@ -21,11 +27,15 @@ import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500NameBuilder import org.bouncycastle.asn1.x500.style.BCStyle import org.slf4j.Logger +import org.slf4j.MDC import rx.Observable import rx.Observer import rx.subjects.PublishSubject import rx.subjects.UnicastSubject -import java.io.* +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream import java.lang.reflect.Field import java.lang.reflect.Modifier import java.math.BigDecimal @@ -41,11 +51,23 @@ import java.nio.file.Paths import java.security.KeyPair import java.security.PrivateKey import java.security.PublicKey -import java.security.cert.* +import java.security.cert.CertPath +import java.security.cert.CertPathValidator +import java.security.cert.CertPathValidatorException +import java.security.cert.PKIXCertPathValidatorResult +import java.security.cert.PKIXParameters +import java.security.cert.TrustAnchor +import java.security.cert.X509Certificate import java.time.Duration import java.time.temporal.Temporal import java.util.* -import java.util.Spliterator.* +import java.util.Spliterator.DISTINCT +import java.util.Spliterator.IMMUTABLE +import java.util.Spliterator.NONNULL +import java.util.Spliterator.ORDERED +import java.util.Spliterator.SIZED +import java.util.Spliterator.SORTED +import java.util.Spliterator.SUBSIZED import java.util.concurrent.ExecutorService import java.util.concurrent.TimeUnit import java.util.stream.IntStream @@ -459,3 +481,10 @@ val PublicKey.hash: SecureHash get() = encoded.sha256() * Extension method for providing a sumBy method that processes and returns a Long */ fun Iterable.sumByLong(selector: (T) -> Long): Long = this.map { selector(it) }.sum() + +/** + * Ensures each log entry from the current thread will contain id of the transaction in the MDC. + */ +internal fun SignedTransaction.pushToLoggingContext() { + MDC.put("tx_id", id.toString()) +} \ No newline at end of file diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 3c5b6a4a19..b79be24fa3 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -7,6 +7,8 @@ release, see :doc:`upgrade-notes`. Unreleased ========== +* Improved audit trail for ``FinalityFlow`` and related sub-flows. + * ``NodeStartup`` will now only print node's configuration if ``devMode`` is ``true``, avoiding the risk of printing passwords in a production setup. * SLF4J's MDC will now only be printed to the console if not empty. No more log lines ending with "{}". From 52eef5da5b217ac494033d7b92a03b05ae0cf4b5 Mon Sep 17 00:00:00 2001 From: Konstantinos Chalkias Date: Thu, 24 May 2018 16:06:33 +0100 Subject: [PATCH 05/13] Replace timestamp with time-window (#3211) --- .../core/transactions/WireTransaction.kt | 2 +- docs/source/api-flows.rst | 2 +- .../tutorial/contract/CommercialPaper.java | 2 +- docs/source/flow-state-machines.rst | 2 +- docs/source/key-concepts-contracts.rst | 2 +- docs/source/key-concepts-oracles.rst | 10 +++++----- docs/source/key-concepts-transactions.rst | 8 ++++---- docs/source/resources/full-tx.png | Bin 301549 -> 204660 bytes .../source/tutorial-building-transactions.rst | 2 +- docs/source/tutorial-contract.rst | 6 +++--- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt index cc59e541e4..c9c56f2a17 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -69,7 +69,7 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr val requiredSigningKeys: Set get() { val commandKeys = commands.flatMap { it.signers }.toSet() - // TODO: prevent notary field from being set if there are no inputs and no timestamp. + // TODO: prevent notary field from being set if there are no inputs and no time-window. return if (notary != null && (inputs.isNotEmpty() || timeWindow != null)) { commandKeys + notary.owningKey } else { diff --git a/docs/source/api-flows.rst b/docs/source/api-flows.rst index 111f349240..619090bcfe 100644 --- a/docs/source/api-flows.rst +++ b/docs/source/api-flows.rst @@ -30,7 +30,7 @@ In our flow, the Initiator flow class will be doing the majority of the work: 2. Create a transaction builder 3. Extract any input states from the vault and add them to the builder 4. Create any output states and add them to the builder -5. Add any commands, attachments and timestamps to the builder +5. Add any commands, attachments and time-window to the builder *Part 2 - Sign the transaction* diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/contract/CommercialPaper.java b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/contract/CommercialPaper.java index 3baa5fbcb6..b91ea13b9c 100644 --- a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/contract/CommercialPaper.java +++ b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/contract/CommercialPaper.java @@ -56,7 +56,7 @@ public class CommercialPaper implements Contract { }); } else if (cmd.getValue() instanceof Commands.Issue) { State output = outputs.get(0); - if (timeWindow == null) throw new IllegalArgumentException("Issuances must be timestamped"); + if (timeWindow == null) throw new IllegalArgumentException("Issuances must have a time-window"); Instant time = timeWindow.getUntilTime(); requireThat(require -> { // Don't allow people to issue commercial paper under other entities identities. diff --git a/docs/source/flow-state-machines.rst b/docs/source/flow-state-machines.rst index 5905fefef4..b811b21b0f 100644 --- a/docs/source/flow-state-machines.rst +++ b/docs/source/flow-state-machines.rst @@ -228,7 +228,7 @@ Next, we call another subflow called ``SignTransactionFlow``. ``SignTransactionF * Sending the transaction back to the buyer. The transaction then needs to be finalized. This is the the process of sending the transaction to a notary to assert -(with another signature) that the timestamp in the transaction (if any) is valid and there are no double spends. +(with another signature) that the time-window in the transaction (if any) is valid and there are no double spends. In this flow, finalization is handled by the buyer, so we just wait for the signed transaction to appear in our transaction storage. It will have the same ID as the one we started with but more signatures. diff --git a/docs/source/key-concepts-contracts.rst b/docs/source/key-concepts-contracts.rst index f4e768fad7..c430ea1123 100644 --- a/docs/source/key-concepts-contracts.rst +++ b/docs/source/key-concepts-contracts.rst @@ -36,7 +36,7 @@ We can picture this situation as follows: The contract code can be written in any JVM language, and has access to the full capabilities of the language, including: -* Checking the number of inputs, outputs, commands, timestamps, and/or attachments +* Checking the number of inputs, outputs, commands, time-window, and/or attachments * Checking the contents of any of these components * Looping constructs, variable assignment, function calls, helper methods, etc. * Grouping similar states to validate them as a group (e.g. imposing a rule on the combined value of all the cash diff --git a/docs/source/key-concepts-oracles.rst b/docs/source/key-concepts-oracles.rst index 9dca75a82f..b5091800c9 100644 --- a/docs/source/key-concepts-oracles.rst +++ b/docs/source/key-concepts-oracles.rst @@ -49,14 +49,14 @@ Transaction Merkle trees ^^^^^^^^^^^^^^^^^^^^^^^^ A Merkle tree is constructed from a transaction by splitting the transaction into leaves, where each leaf contains either an input, an output, a command, or an attachment. The Merkle tree also contains the other fields of the -``WireTransaction``, such as the timestamp, the notary, the type and the signers. +``WireTransaction``, such as the time-window, the notary, the type and the signers. Next, the Merkle tree is built in the normal way by hashing the concatenation of nodes’ hashes below the current one together. It’s visible on the example image below, where ``H`` denotes sha256 function, "+" - concatenation. .. image:: resources/merkleTree.png -The transaction has two input states, one output state, one attachment, one command and a timestamp. For brevity +The transaction has two input states, one output state, one attachment, one command and a time-window. For brevity we didn't include all leaves on the diagram (type, notary and signers are presented as one leaf labelled Rest - in reality they are separate leaves). Notice that if a tree is not a full binary tree, leaves are padded to the nearest power of 2 with zero hash (since finding a pre-image of sha256(x) == 0 is hard computational task) - marked light @@ -73,7 +73,7 @@ obtained belongs to that particular transaction. .. image:: resources/partialMerkle.png In the example above, the node ``H(f)`` is the one holding command data for signing by Oracle service. Blue leaf -``H(g)`` is also included since it's holding timestamp information. Nodes labelled ``Provided`` form the Partial -Merkle Tree, black ones are omitted. Having timestamp with the command that should be in a violet node place and +``H(g)`` is also included since it's holding time-window information. Nodes labelled ``Provided`` form the Partial +Merkle Tree, black ones are omitted. Having time-window with the command that should be in a violet node place and branch we are able to calculate root of this tree and compare it with original transaction identifier - we have a -proof that this command and timestamp belong to this transaction. \ No newline at end of file +proof that this command and time-window belong to this transaction. \ No newline at end of file diff --git a/docs/source/key-concepts-transactions.rst b/docs/source/key-concepts-transactions.rst index 32642572a7..ff38a8b12b 100644 --- a/docs/source/key-concepts-transactions.rst +++ b/docs/source/key-concepts-transactions.rst @@ -111,10 +111,10 @@ As well as input states and output states, transactions contain: * Commands * Attachments -* Timestamps +* Time-Window For example, a transaction where Alice pays off £5 of an IOU with Bob using a £5 cash payment, supported by two -attachments and a timestamp, may look as follows: +attachments and a time-window, may look as follows: .. image:: resources/full-tx.png :scale: 25% @@ -172,8 +172,8 @@ For this use case, we have *attachments*. Each transaction can refer to zero or attachments are ZIP/JAR files containing arbitrary content. The information in these files can then be used when checking the transaction's validity. -Time-windows -^^^^^^^^^^^^ +Time-window +^^^^^^^^^^^ In some cases, we want a transaction proposed to only be approved during a certain time-window. For example: * An option can only be exercised after a certain date diff --git a/docs/source/resources/full-tx.png b/docs/source/resources/full-tx.png index 63914961357d575b210dc0ee583a3251957eba30..354fc1c5e42a61bafbd4f66c1ee2c8607586259e 100644 GIT binary patch literal 204660 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 z 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 literal 301549 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 QECUdDy85}Sb4q9e0NVc(?*IS* diff --git a/docs/source/tutorial-building-transactions.rst b/docs/source/tutorial-building-transactions.rst index fdf28e5dce..324cb95ae2 100644 --- a/docs/source/tutorial-building-transactions.rst +++ b/docs/source/tutorial-building-transactions.rst @@ -44,7 +44,7 @@ Transactions in Corda contain a number of elements: transactions to migrate the states across to a consistent notary node before being allowed to mutate any states) -7. Optionally a timestamp that can used by the notary to bound the +7. Optionally a time-window that can used by the notary to bound the period during which the proposed transaction can be committed to the ledger diff --git a/docs/source/tutorial-contract.rst b/docs/source/tutorial-contract.rst index 23c1430286..5137127b70 100644 --- a/docs/source/tutorial-contract.rst +++ b/docs/source/tutorial-contract.rst @@ -299,13 +299,13 @@ logic. This loop is the core logic of the contract. -The first line simply gets the timestamp out of the transaction. Timestamping of transactions is optional, so a time +The first line simply gets the time-window out of the transaction. Setting a time-window in transactions is optional, so a time may be missing here. We check for it being null later. .. warning:: In the Kotlin version as long as we write a comparison with the transaction time first the compiler will verify we didn't forget to check if it's missing. Unfortunately due to the need for smooth Java interop, this check won't happen if we write e.g. ``someDate > time``, it has to be ``time < someDate``. So it's good practice to - always write the transaction timestamp first. + always write the transaction time-window first. Next, we take one of three paths, depending on what the type of the command object is. @@ -597,7 +597,7 @@ The time-lock contract mentioned above can be implemented very simply: class TestTimeLock : Contract { ... override fun verify(tx: LedgerTransaction) { - val time = tx.timestamp.before ?: throw IllegalStateException(...) + val time = tx.timeWindow?.untilTime ?: throw IllegalStateException(...) ... requireThat { "the time specified in the time-lock has passed" by From 7ff008d4e390a938da94c0862c5eed4d6625871b Mon Sep 17 00:00:00 2001 From: Matthew Nesbit Date: Thu, 24 May 2018 16:53:09 +0100 Subject: [PATCH 06/13] Prevent bridge reconnection attempts on targets that present invalid/misconfigured/different certificates to protect nodes from dead identities. (#3225) --- .../protonwrapper/netty/AMQPChannelHandler.kt | 8 +++-- .../protonwrapper/netty/AMQPClient.kt | 31 ++++++++++++++++--- .../protonwrapper/netty/ConnectionChange.kt | 2 +- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPChannelHandler.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPChannelHandler.kt index 4663a038a9..0c54eed36c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPChannelHandler.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPChannelHandler.kt @@ -41,6 +41,7 @@ internal class AMQPChannelHandler(private val serverMode: Boolean, private var localCert: X509Certificate? = null private var remoteCert: X509Certificate? = null private var eventProcessor: EventProcessor? = null + private var badCert: Boolean = false override fun channelActive(ctx: ChannelHandlerContext) { val ch = ctx.channel() @@ -72,7 +73,7 @@ internal class AMQPChannelHandler(private val serverMode: Boolean, override fun channelInactive(ctx: ChannelHandlerContext) { val ch = ctx.channel() log.info("Closed client connection ${ch.id()} from $remoteAddress to ${ch.localAddress()}") - onClose(Pair(ch as SocketChannel, ConnectionChange(remoteAddress, remoteCert, false))) + onClose(Pair(ch as SocketChannel, ConnectionChange(remoteAddress, remoteCert, false, badCert))) eventProcessor?.close() ctx.fireChannelInactive() } @@ -86,19 +87,22 @@ internal class AMQPChannelHandler(private val serverMode: Boolean, val remoteX500Name = try { CordaX500Name.build(remoteCert!!.subjectX500Principal) } catch (ex: IllegalArgumentException) { + badCert = true log.error("Certificate subject not a valid CordaX500Name", ex) ctx.close() return } if (allowedRemoteLegalNames != null && remoteX500Name !in allowedRemoteLegalNames) { + badCert = true log.error("Provided certificate subject $remoteX500Name not in expected set $allowedRemoteLegalNames") ctx.close() return } log.info("Handshake completed with subject: $remoteX500Name") createAMQPEngine(ctx) - onOpen(Pair(ctx.channel() as SocketChannel, ConnectionChange(remoteAddress, remoteCert, true))) + onOpen(Pair(ctx.channel() as SocketChannel, ConnectionChange(remoteAddress, remoteCert, true, false))) } else { + badCert = true log.error("Handshake failure ${evt.cause().message}") if (log.isTraceEnabled) { log.trace("Handshake failure", evt.cause()) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPClient.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPClient.kt index 52731380b0..b542ada21f 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPClient.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPClient.kt @@ -51,7 +51,7 @@ class AMQPClient(val targets: List, val log = contextLogger() const val MIN_RETRY_INTERVAL = 1000L - const val MAX_RETRY_INTERVAL = 60000L + const val MAX_RETRY_INTERVAL = 300000L const val BACKOFF_MULTIPLIER = 2L const val NUM_CLIENT_THREADS = 2 } @@ -66,9 +66,22 @@ class AMQPClient(val targets: List, private var targetIndex = 0 private var currentTarget: NetworkHostAndPort = targets.first() private var retryInterval = MIN_RETRY_INTERVAL + private val badCertTargets = mutableSetOf() private fun nextTarget() { - targetIndex = (targetIndex + 1).rem(targets.size) + val origIndex = targetIndex + targetIndex = -1 + for (offset in 1..targets.size) { + val newTargetIndex = (origIndex + offset).rem(targets.size) + if (targets[newTargetIndex] !in badCertTargets) { + targetIndex = newTargetIndex + break + } + } + if (targetIndex == -1) { + log.error("No targets have presented acceptable certificates for $allowedRemoteLegalNames. Halting retries") + return + } log.info("Retry connect to ${targets[targetIndex]}") retryInterval = min(MAX_RETRY_INTERVAL, retryInterval * BACKOFF_MULTIPLIER) } @@ -116,7 +129,8 @@ class AMQPClient(val targets: List, override fun initChannel(ch: SocketChannel) { val pipeline = ch.pipeline() - val handler = createClientSslHelper(parent.currentTarget, keyManagerFactory, trustManagerFactory) + val target = parent.currentTarget + val handler = createClientSslHelper(target, keyManagerFactory, trustManagerFactory) pipeline.addLast("sslHandler", handler) if (parent.trace) pipeline.addLast("logger", LoggingHandler(LogLevel.INFO)) pipeline.addLast(AMQPChannelHandler(false, @@ -128,7 +142,13 @@ class AMQPClient(val targets: List, parent.retryInterval = MIN_RETRY_INTERVAL // reset to fast reconnect if we connect properly parent._onConnection.onNext(it.second) }, - { parent._onConnection.onNext(it.second) }, + { + parent._onConnection.onNext(it.second) + if (it.second.badCert) { + log.error("Blocking future connection attempts to $target due to bad certificate on endpoint") + parent.badCertTargets += target + } + }, { rcv -> parent._onReceive.onNext(rcv) })) } } @@ -142,6 +162,9 @@ class AMQPClient(val targets: List, } private fun restart() { + if (targetIndex == -1) { + return + } val bootstrap = Bootstrap() // TODO Needs more configuration control when we profile. e.g. to use EPOLL on Linux bootstrap.group(workerGroup).channel(NioSocketChannel::class.java).handler(ClientChannelInitializer(this)) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/ConnectionChange.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/ConnectionChange.kt index 5488823de7..954e2c1f76 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/ConnectionChange.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/ConnectionChange.kt @@ -3,4 +3,4 @@ package net.corda.nodeapi.internal.protonwrapper.netty import java.net.InetSocketAddress import java.security.cert.X509Certificate -data class ConnectionChange(val remoteAddress: InetSocketAddress, val remoteCert: X509Certificate?, val connected: Boolean) \ No newline at end of file +data class ConnectionChange(val remoteAddress: InetSocketAddress, val remoteCert: X509Certificate?, val connected: Boolean, val badCert: Boolean) \ No newline at end of file From 2e72f784f11930e2efd1fdd1a727229c03504a74 Mon Sep 17 00:00:00 2001 From: Matthew Nesbit Date: Thu, 24 May 2018 16:54:09 +0100 Subject: [PATCH 07/13] Late start bridges (unless configured otherwise) if the queue is empty. (#3227) --- .../node/services/config/NodeConfiguration.kt | 4 +- .../services/messaging/P2PMessagingClient.kt | 57 ++++++++++--------- node/src/main/resources/reference.conf | 1 + 3 files changed, 33 insertions(+), 29 deletions(-) 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 bedb27badd..465c0b7e7d 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 @@ -10,9 +10,9 @@ import net.corda.core.utilities.loggerFor import net.corda.core.utilities.seconds import net.corda.node.internal.artemis.CertificateChainCheckPolicy import net.corda.node.services.config.rpc.NodeRpcOptions -import net.corda.nodeapi.internal.config.* import net.corda.nodeapi.BrokerRpcSslOptions import net.corda.nodeapi.internal.config.NodeSSLConfiguration +import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.parseAs import net.corda.nodeapi.internal.persistence.DatabaseConfig @@ -47,6 +47,7 @@ interface NodeConfiguration : NodeSSLConfiguration { val messagingServerExternal: Boolean // TODO Move into DevModeOptions val useTestClock: Boolean get() = false + val lazyBridgeStart: Boolean val detectPublicIp: Boolean get() = true val sshd: SSHDConfiguration? val database: DatabaseConfig @@ -158,6 +159,7 @@ data class NodeConfigurationImpl( override val noLocalShell: Boolean = false, override val devModeOptions: DevModeOptions? = null, override val useTestClock: Boolean = false, + override val lazyBridgeStart: Boolean = true, override val detectPublicIp: Boolean = true, // TODO See TODO above. Rename this to nodeInfoPollingFrequency and make it of type Duration override val additionalNodeInfoPollingFrequencyMsec: Long = 5.seconds.toMillis(), 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 aebc8b5832..c8e65c7c21 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 @@ -15,11 +15,7 @@ import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.deserialize import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.serialization.serialize -import net.corda.core.utilities.ByteSequence -import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.contextLogger -import net.corda.core.utilities.trace +import net.corda.core.utilities.* import net.corda.node.VersionInfo import net.corda.node.internal.LifecycleSupport import net.corda.node.internal.artemis.ReactiveArtemisConsumer @@ -31,15 +27,12 @@ import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.PersistentMap import net.corda.nodeapi.ArtemisTcpTransport.Companion.p2pConnectorTcpTransport import net.corda.nodeapi.internal.ArtemisMessagingComponent -import net.corda.nodeapi.internal.ArtemisMessagingComponent.ArtemisAddress +import net.corda.nodeapi.internal.ArtemisMessagingComponent.* import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_CONTROL import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_NOTIFY import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.JOURNAL_HEADER_SIZE import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2PMessagingHeaders import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX -import net.corda.nodeapi.internal.ArtemisMessagingComponent.NodeAddress -import net.corda.nodeapi.internal.ArtemisMessagingComponent.RemoteInboxAddress -import net.corda.nodeapi.internal.ArtemisMessagingComponent.ServiceAddress import net.corda.nodeapi.internal.bridging.BridgeControl import net.corda.nodeapi.internal.bridging.BridgeEntry import net.corda.nodeapi.internal.persistence.CordaPersistence @@ -50,12 +43,7 @@ import org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID import org.apache.activemq.artemis.api.core.Message.HDR_VALIDATED_USER import org.apache.activemq.artemis.api.core.RoutingType import org.apache.activemq.artemis.api.core.SimpleString -import org.apache.activemq.artemis.api.core.client.ActiveMQClient -import org.apache.activemq.artemis.api.core.client.ClientConsumer -import org.apache.activemq.artemis.api.core.client.ClientMessage -import org.apache.activemq.artemis.api.core.client.ClientProducer -import org.apache.activemq.artemis.api.core.client.ClientSession -import org.apache.activemq.artemis.api.core.client.ServerLocator +import org.apache.activemq.artemis.api.core.client.* import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import rx.Observable import rx.Subscription @@ -173,6 +161,7 @@ class P2PMessagingClient(val config: NodeConfiguration, private val messageRedeliveryDelaySeconds = config.p2pMessagingRetry.messageRedeliveryDelay.seconds private val state = ThreadBox(InnerState()) private val knownQueues = Collections.newSetFromMap(ConcurrentHashMap()) + private val delayStartQueues = Collections.newSetFromMap(ConcurrentHashMap()) private val handlers = ConcurrentHashMap() @@ -332,7 +321,12 @@ class P2PMessagingClient(val config: NodeConfiguration, val queues = session.addressQuery(SimpleString("$PEERS_PREFIX#")).queueNames for (queue in queues) { - createBridgeEntry(queue) + val queueQuery = session.queueQuery(queue) + if (!config.lazyBridgeStart || queueQuery.messageCount > 0) { + createBridgeEntry(queue) + } else { + delayStartQueues += queue.toString() + } } val startupMessage = BridgeControl.NodeToBridgeSnapshot(myIdentity.toStringShort(), inboxes, requiredBridges) sendBridgeControl(startupMessage) @@ -574,19 +568,26 @@ class P2PMessagingClient(val config: NodeConfiguration, /** Attempts to create a durable queue on the broker which is bound to an address of the same name. */ private fun createQueueIfAbsent(queueName: String, session: ClientSession) { + fun sendBridgeCreateMessage() { + val keyHash = queueName.substring(PEERS_PREFIX.length) + val peers = networkMap.getNodesByOwningKeyIndex(keyHash) + for (node in peers) { + val bridge = BridgeEntry(queueName, node.addresses, node.legalIdentities.map { it.name }) + val createBridgeMessage = BridgeControl.Create(myIdentity.toStringShort(), bridge) + sendBridgeControl(createBridgeMessage) + } + } if (!knownQueues.contains(queueName)) { - 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) - if (queueName.startsWith(PEERS_PREFIX)) { - val keyHash = queueName.substring(PEERS_PREFIX.length) - val peers = networkMap.getNodesByOwningKeyIndex(keyHash) - for (node in peers) { - val bridge = BridgeEntry(queueName, node.addresses, node.legalIdentities.map { it.name }) - val createBridgeMessage = BridgeControl.Create(myIdentity.toStringShort(), bridge) - sendBridgeControl(createBridgeMessage) - } + if (delayStartQueues.contains(queueName)) { + log.info("Start bridge for previously empty queue $queueName") + sendBridgeCreateMessage() + delayStartQueues -= queueName + } else { + 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) + sendBridgeCreateMessage() } } knownQueues += queueName diff --git a/node/src/main/resources/reference.conf b/node/src/main/resources/reference.conf index 808b1e1cee..9dea25e930 100644 --- a/node/src/main/resources/reference.conf +++ b/node/src/main/resources/reference.conf @@ -3,6 +3,7 @@ emailAddress = "admin@company.com" keyStorePassword = "cordacadevpass" trustStorePassword = "trustpass" crlCheckSoftFail = true +lazyBridgeStart = true dataSourceProperties = { dataSourceClassName = org.h2.jdbcx.JdbcDataSource dataSource.url = "jdbc:h2:file:"${baseDirectory}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;WRITE_DELAY=100;AUTO_SERVER_PORT="${h2port} From 15b262f25f4e29238828ef3207d41921562f1083 Mon Sep 17 00:00:00 2001 From: Thomas Schroeter Date: Thu, 24 May 2018 17:37:43 +0100 Subject: [PATCH 08/13] Remove default value for `myLegalName` (#3230) --- node/src/main/resources/reference.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/node/src/main/resources/reference.conf b/node/src/main/resources/reference.conf index 9dea25e930..ce33a39722 100644 --- a/node/src/main/resources/reference.conf +++ b/node/src/main/resources/reference.conf @@ -1,4 +1,3 @@ -myLegalName = "Vast Global MegaCorp, Ltd" emailAddress = "admin@company.com" keyStorePassword = "cordacadevpass" trustStorePassword = "trustpass" From 4e0378de9cb2ac46589f0b3f8754f4c82798569c Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Thu, 24 May 2018 18:26:55 +0100 Subject: [PATCH 09/13] CORDA-1238: Moved the blob inspector out of experimental and wired it to JackonSupport (#3224) The existing output format was not complete and so was deleted to avoid it becoming a tech debt. We can always resurrect it at a later point. --- .idea/compiler.xml | 4 +- build.gradle | 1 + client/jackson/build.gradle | 5 +- .../corda/client/jackson/JacksonSupport.kt | 128 ++++-- .../client/jackson/internal/CordaModule.kt | 39 +- .../client/jackson/JacksonSupportTest.kt | 73 +++- .../net/corda/core/internal/InternalUtils.kt | 11 + .../core/serialization/SerializationAPI.kt | 22 +- docs/source/blob-inspector.rst | 63 +++ docs/source/changelog.rst | 15 +- docs/source/tools-index.rst | 1 + experimental/blobinspector/build.gradle | 52 --- .../net/corda/blobinspector/BlobInspector.kt | 405 ------------------ .../net/corda/blobinspector/BlobLoader.kt | 40 -- .../kotlin/net/corda/blobinspector/Config.kt | 137 ------ .../kotlin/net/corda/blobinspector/Errors.kt | 3 - .../blobinspector/IndentingStringBuilder.kt | 44 -- .../kotlin/net/corda/blobinspector/Main.kt | 81 ---- .../net/corda/blobinspector/FileParseTests.kt | 87 ---- .../net/corda/blobinspector/InMemoryTests.kt | 91 ---- .../net/corda/blobinspector/ModeParse.kt | 83 ---- .../corda/blobinspector/SimplifyClassTests.kt | 28 -- .../blobinspector/FileParseTests.1Composite | Bin 537 -> 0 bytes .../corda/blobinspector/FileParseTests.1Int | Bin 244 -> 0 bytes .../blobinspector/FileParseTests.1String | Bin 262 -> 0 bytes .../blobinspector/FileParseTests.2Composite | Bin 1004 -> 0 bytes .../corda/blobinspector/FileParseTests.2Int | Bin 284 -> 0 bytes .../corda/blobinspector/FileParseTests.3Int | Bin 317 -> 0 bytes .../blobinspector/FileParseTests.IntList | Bin 426 -> 0 bytes .../blobinspector/FileParseTests.MapIntClass | Bin 759 -> 0 bytes .../blobinspector/FileParseTests.MapIntString | Bin 474 -> 0 bytes .../blobinspector/FileParseTests.StringList | Bin 448 -> 0 bytes .../net/corda/blobinspector/networkParams | Bin 3066 -> 0 bytes node/build.gradle | 2 +- .../serialization/kryo/CordaClassResolver.kt | 5 +- .../internal/amqp/SerializationHelper.kt | 56 +-- settings.gradle | 1 + tools/blobinspector/build.gradle | 27 ++ .../kotlin/net/corda/blobinspector/Main.kt | 125 ++++++ 39 files changed, 461 insertions(+), 1168 deletions(-) create mode 100644 docs/source/blob-inspector.rst delete mode 100644 experimental/blobinspector/build.gradle delete mode 100644 experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt delete mode 100644 experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobLoader.kt delete mode 100644 experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Config.kt delete mode 100644 experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Errors.kt delete mode 100644 experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/IndentingStringBuilder.kt delete mode 100644 experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt delete mode 100644 experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/FileParseTests.kt delete mode 100644 experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/InMemoryTests.kt delete mode 100644 experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/ModeParse.kt delete mode 100644 experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/SimplifyClassTests.kt delete mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.1Composite delete mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.1Int delete mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.1String delete mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.2Composite delete mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.2Int delete mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.3Int delete mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.IntList delete mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.MapIntClass delete mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.MapIntString delete mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.StringList delete mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/networkParams create mode 100644 tools/blobinspector/build.gradle create mode 100644 tools/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt diff --git a/.idea/compiler.xml b/.idea/compiler.xml index af6261dd62..9632eef3de 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -71,8 +71,6 @@ - - @@ -173,6 +171,8 @@ + + diff --git a/build.gradle b/build.gradle index bcd3f867d7..ea49fc62ba 100644 --- a/build.gradle +++ b/build.gradle @@ -79,6 +79,7 @@ buildscript { ext.protonj_version = '0.27.1' ext.snappy_version = '0.4' ext.fast_classpath_scanner_version = '2.12.3' + ext.jcabi_manifests_version = '1.1' // Update 121 is required for ObjectInputFilter and at time of writing 131 was latest: ext.java8_minUpdateVersion = '131' diff --git a/client/jackson/build.gradle b/client/jackson/build.gradle index 9c59b0ee99..be0b324ed2 100644 --- a/client/jackson/build.gradle +++ b/client/jackson/build.gradle @@ -6,11 +6,8 @@ apply plugin: 'com.jfrog.artifactory' dependencies { compile project(':serialization') - testCompile project(':test-utils') compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" - // Jackson and its plugins: parsing to/from JSON and other textual formats. compile "com.fasterxml.jackson.module:jackson-module-kotlin:$jackson_version" // Yaml is useful for parsing strings to method calls. @@ -19,7 +16,9 @@ dependencies { compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version" compile "com.google.guava:guava:$guava_version" + testCompile project(':test-utils') testCompile project(path: ':core', configuration: 'testArtifacts') + testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testCompile "junit:junit:$junit_version" } diff --git a/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt index 701092ef03..d360997fbf 100644 --- a/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt +++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt @@ -21,10 +21,7 @@ import net.corda.core.contracts.Amount import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateRef import net.corda.core.crypto.* -import net.corda.core.identity.AbstractParty -import net.corda.core.identity.AnonymousParty -import net.corda.core.identity.CordaX500Name -import net.corda.core.identity.Party +import net.corda.core.identity.* import net.corda.core.internal.CertRole import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.uncheckedCast @@ -55,12 +52,13 @@ import javax.security.auth.x500.X500Principal * * Note that Jackson can also be used to serialise/deserialise other formats such as Yaml and XML. */ -@Suppress("DEPRECATION") +@Suppress("DEPRECATION", "MemberVisibilityCanBePrivate") object JacksonSupport { // If you change this API please update the docs in the docsite (json.rst) @DoNotImplement interface PartyObjectMapper { + val isFullParties: Boolean fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? fun partyFromKey(owningKey: PublicKey): Party? fun partiesFromName(query: String): Set @@ -68,9 +66,11 @@ object JacksonSupport { } @Deprecated("This is an internal class, do not use", replaceWith = ReplaceWith("JacksonSupport.createDefaultMapper")) - class RpcObjectMapper(val rpc: CordaRPCOps, - factory: JsonFactory, - val fuzzyIdentityMatch: Boolean) : PartyObjectMapper, ObjectMapper(factory) { + class RpcObjectMapper + @JvmOverloads constructor(val rpc: CordaRPCOps, + factory: JsonFactory, + val fuzzyIdentityMatch: Boolean, + override val isFullParties: Boolean = false) : PartyObjectMapper, ObjectMapper(factory) { override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = rpc.wellKnownPartyFromX500Name(name) override fun partyFromKey(owningKey: PublicKey): Party? = rpc.partyFromKey(owningKey) override fun partiesFromName(query: String) = rpc.partiesFromName(query, fuzzyIdentityMatch) @@ -78,9 +78,11 @@ object JacksonSupport { } @Deprecated("This is an internal class, do not use") - class IdentityObjectMapper(val identityService: IdentityService, - factory: JsonFactory, - val fuzzyIdentityMatch: Boolean) : PartyObjectMapper, ObjectMapper(factory) { + class IdentityObjectMapper + @JvmOverloads constructor(val identityService: IdentityService, + factory: JsonFactory, + val fuzzyIdentityMatch: Boolean, + override val isFullParties: Boolean = false) : PartyObjectMapper, ObjectMapper(factory) { override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = identityService.wellKnownPartyFromX500Name(name) override fun partyFromKey(owningKey: PublicKey): Party? = identityService.partyFromKey(owningKey) override fun partiesFromName(query: String) = identityService.partiesFromName(query, fuzzyIdentityMatch) @@ -88,7 +90,9 @@ object JacksonSupport { } @Deprecated("This is an internal class, do not use", replaceWith = ReplaceWith("JacksonSupport.createNonRpcMapper")) - class NoPartyObjectMapper(factory: JsonFactory) : PartyObjectMapper, ObjectMapper(factory) { + class NoPartyObjectMapper + @JvmOverloads constructor(factory: JsonFactory, + override val isFullParties: Boolean = false) : PartyObjectMapper, ObjectMapper(factory) { override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = null override fun partyFromKey(owningKey: PublicKey): Party? = null override fun partiesFromName(query: String): Set = emptySet() @@ -102,22 +106,33 @@ object JacksonSupport { /** * Creates a Jackson ObjectMapper that uses RPC to deserialise parties from string names. * - * If [fuzzyIdentityMatch] is false, fields mapped to [Party] objects must be in X.500 name form and precisely + * @param fuzzyIdentityMatch If false, fields mapped to [Party] objects must be in X.500 name form and precisely * match an identity known from the network map. If true, the name is matched more leniently but if the match * is ambiguous a [JsonParseException] is thrown. + * + * @param fullParties If true then [Party] objects will be serialised as JSON objects, with the owning key serialised + * in addition to the name. For [PartyAndCertificate] objects the cert path will be included. */ @JvmStatic @JvmOverloads fun createDefaultMapper(rpc: CordaRPCOps, factory: JsonFactory = JsonFactory(), - fuzzyIdentityMatch: Boolean = false): ObjectMapper { - return configureMapper(RpcObjectMapper(rpc, factory, fuzzyIdentityMatch)) + fuzzyIdentityMatch: Boolean = false, + fullParties: Boolean = false): ObjectMapper { + return configureMapper(RpcObjectMapper(rpc, factory, fuzzyIdentityMatch, fullParties)) } - /** For testing or situations where deserialising parties is not required */ + /** + * For testing or situations where deserialising parties is not required + * + * @param fullParties If true then [Party] objects will be serialised as JSON objects, with the owning key serialised + * in addition to the name. For [PartyAndCertificate] objects the cert path will be included. + */ @JvmStatic @JvmOverloads - fun createNonRpcMapper(factory: JsonFactory = JsonFactory()): ObjectMapper = configureMapper(NoPartyObjectMapper(factory)) + fun createNonRpcMapper(factory: JsonFactory = JsonFactory(), fullParties: Boolean = false): ObjectMapper { + return configureMapper(NoPartyObjectMapper(factory, fullParties)) + } /** * Creates a Jackson ObjectMapper that uses an [IdentityService] directly inside the node to deserialise parties from string names. @@ -197,7 +212,14 @@ object JacksonSupport { .filter { Modifier.isStatic(it.modifiers) && it.type == KeyPurposeId::class.java } .associateBy({ (it.get(null) as KeyPurposeId).id }, { it.name }) - val knownExtensions = setOf("2.5.29.15", "2.5.29.37", "2.5.29.19", "2.5.29.17", "2.5.29.18", CordaOID.X509_EXTENSION_CORDA_ROLE) + val knownExtensions = setOf( + "2.5.29.15", + "2.5.29.17", + "2.5.29.18", + "2.5.29.19", + "2.5.29.37", + CordaOID.X509_EXTENSION_CORDA_ROLE + ) override fun serialize(value: X509Certificate, gen: JsonGenerator, serializers: SerializerProvider) { gen.jsonObject { @@ -208,17 +230,20 @@ object JacksonSupport { writeObjectField("issuer", value.issuerX500Principal) writeObjectField("notBefore", value.notBefore) writeObjectField("notAfter", value.notAfter) + writeObjectField("cordaCertRole", CertRole.extract(value)) writeObjectField("issuerUniqueID", value.issuerUniqueID) writeObjectField("subjectUniqueID", value.subjectUniqueID) writeObjectField("keyUsage", value.keyUsage?.asList()?.mapIndexedNotNull { i, flag -> if (flag) keyUsages[i] else null }) writeObjectField("extendedKeyUsage", value.extendedKeyUsage.map { keyPurposeIds.getOrDefault(it, it) }) jsonObject("basicConstraints") { - writeBooleanField("isCA", value.basicConstraints != -1) - writeObjectField("pathLength", value.basicConstraints.let { if (it != Int.MAX_VALUE) it else null }) + val isCa = value.basicConstraints != -1 + writeBooleanField("isCA", isCa) + if (isCa) { + writeObjectField("pathLength", value.basicConstraints.let { if (it != Int.MAX_VALUE) it else null }) + } } writeObjectField("subjectAlternativeNames", value.subjectAlternativeNames) writeObjectField("issuerAlternativeNames", value.issuerAlternativeNames) - writeObjectField("cordaCertRole", CertRole.extract(value)) writeObjectField("otherCriticalExtensions", value.criticalExtensionOIDs - knownExtensions) writeObjectField("otherNonCriticalExtensions", value.nonCriticalExtensionOIDs - knownExtensions) writeBinaryField("encoded", value.encoded) @@ -229,8 +254,12 @@ object JacksonSupport { private class X509CertificateDeserializer : JsonDeserializer() { private val certFactory = CertificateFactory.getInstance("X.509") override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): X509Certificate { - val encoded = parser.readValueAsTree()["encoded"] - return certFactory.generateCertificate(encoded.binaryValue().inputStream()) as X509Certificate + val encoded = if (parser.currentToken == JsonToken.START_OBJECT) { + parser.readValueAsTree()["encoded"].binaryValue() + } else { + parser.binaryValue + } + return certFactory.generateCertificate(encoded.inputStream()) as X509Certificate } } @@ -274,9 +303,13 @@ object JacksonSupport { @Deprecated("This is an internal class, do not use") object PartySerializer : JsonSerializer() { - override fun serialize(value: Party, generator: JsonGenerator, provider: SerializerProvider) { - // TODO Add configurable option to output this as an object which includes the owningKey - generator.writeObject(value.name) + override fun serialize(value: Party, gen: JsonGenerator, provider: SerializerProvider) { + val mapper = gen.codec as PartyObjectMapper + if (mapper.isFullParties) { + gen.writeObject(PartyAnalogue(value.name, value.owningKey)) + } else { + gen.writeObject(value.name) + } } } @@ -284,28 +317,39 @@ object JacksonSupport { object PartyDeserializer : JsonDeserializer() { override fun deserialize(parser: JsonParser, context: DeserializationContext): Party { val mapper = parser.codec as PartyObjectMapper - // The comma character is invalid in Base58, and required as a separator for X.500 names. As Corda - // X.500 names all involve at least three attributes (organisation, locality, country), they must - // include a comma. As such we can use it as a distinguisher between the two types. - return if ("," in parser.text) { - val principal = CordaX500Name.parse(parser.text) - mapper.wellKnownPartyFromX500Name(principal) ?: throw JsonParseException(parser, "Could not find a Party with name $principal") + return if (parser.currentToken == JsonToken.START_OBJECT) { + val analogue = parser.readValueAs() + Party(analogue.name, analogue.owningKey) } else { - val nameMatches = mapper.partiesFromName(parser.text) - when { - nameMatches.isEmpty() -> { - val publicKey = parser.readValueAs() - mapper.partyFromKey(publicKey) - ?: throw JsonParseException(parser, "Could not find a Party with key ${publicKey.toStringShort()}") - } - nameMatches.size == 1 -> nameMatches.first() - else -> throw JsonParseException(parser, "Ambiguous name match '${parser.text}': could be any of " + - nameMatches.map { it.name }.joinToString(" ... or ... ")) + // The comma character is invalid in Base58, and required as a separator for X.500 names. As Corda + // X.500 names all involve at least three attributes (organisation, locality, country), they must + // include a comma. As such we can use it as a distinguisher between the two types. + if ("," in parser.text) { + val principal = CordaX500Name.parse(parser.text) + mapper.wellKnownPartyFromX500Name(principal) ?: throw JsonParseException(parser, "Could not find a Party with name $principal") + } else { + lookupByNameSegment(mapper, parser) } } } + + private fun lookupByNameSegment(mapper: PartyObjectMapper, parser: JsonParser): Party { + val nameMatches = mapper.partiesFromName(parser.text) + return when { + nameMatches.isEmpty() -> { + val publicKey = parser.readValueAs() + mapper.partyFromKey(publicKey) + ?: throw JsonParseException(parser, "Could not find a Party with key ${publicKey.toStringShort()}") + } + nameMatches.size == 1 -> nameMatches.first() + else -> throw JsonParseException(parser, "Ambiguous name match '${parser.text}': could be any of " + + nameMatches.map { it.name }.joinToString(" ... or ... ")) + } + } } + private class PartyAnalogue(val name: CordaX500Name, val owningKey: PublicKey) + @Deprecated("This is an internal class, do not use") object CordaX500NameDeserializer : JsonDeserializer() { override fun deserialize(parser: JsonParser, context: DeserializationContext): CordaX500Name { diff --git a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt index 640dbf3a4a..5dd3fe395a 100644 --- a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt +++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt @@ -29,9 +29,10 @@ import net.corda.core.utilities.NetworkHostAndPort import net.corda.serialization.internal.AllWhitelist import net.corda.serialization.internal.amqp.SerializerFactory import net.corda.serialization.internal.amqp.constructorForDeserialization -import net.corda.serialization.internal.amqp.createSerializerFactoryFactory +import net.corda.serialization.internal.amqp.hasCordaSerializable import net.corda.serialization.internal.amqp.propertiesForSerialization import java.security.PublicKey +import java.security.cert.CertPath class CordaModule : SimpleModule("corda-core") { override fun setupModule(context: SetupContext) { @@ -39,7 +40,7 @@ class CordaModule : SimpleModule("corda-core") { context.addBeanSerializerModifier(CordaSerializableBeanSerializerModifier()) - context.setMixInAnnotations(PartyAndCertificate::class.java, PartyAndCertificateSerializerMixin::class.java) + context.setMixInAnnotations(PartyAndCertificate::class.java, PartyAndCertificateMixin::class.java) context.setMixInAnnotations(NetworkHostAndPort::class.java, NetworkHostAndPortMixin::class.java) context.setMixInAnnotations(CordaX500Name::class.java, CordaX500NameMixin::class.java) context.setMixInAnnotations(Amount::class.java, AmountMixin::class.java) @@ -53,7 +54,7 @@ class CordaModule : SimpleModule("corda-core") { context.setMixInAnnotations(DigitalSignature.WithKey::class.java, ByteSequenceWithPropertiesMixin::class.java) context.setMixInAnnotations(DigitalSignatureWithCert::class.java, ByteSequenceWithPropertiesMixin::class.java) context.setMixInAnnotations(TransactionSignature::class.java, ByteSequenceWithPropertiesMixin::class.java) - context.setMixInAnnotations(SignedTransaction::class.java, SignedTransactionMixin2::class.java) + context.setMixInAnnotations(SignedTransaction::class.java, SignedTransactionMixin::class.java) context.setMixInAnnotations(WireTransaction::class.java, JacksonSupport.WireTransactionMixin::class.java) context.setMixInAnnotations(NodeInfo::class.java, NodeInfoMixin::class.java) } @@ -69,12 +70,15 @@ private class CordaSerializableBeanSerializerModifier : BeanSerializerModifier() override fun changeProperties(config: SerializationConfig, beanDesc: BeanDescription, beanProperties: MutableList): MutableList { - // TODO We're assuming here that Jackson gives us a superset of all the properties. Either confirm this or - // make sure the returned beanProperties are exactly the AMQP properties - if (beanDesc.beanClass.isAnnotationPresent(CordaSerializable::class.java)) { + if (hasCordaSerializable(beanDesc.beanClass)) { val ctor = constructorForDeserialization(beanDesc.beanClass) - val amqpProperties = propertiesForSerialization(ctor, beanDesc.beanClass, serializerFactory).serializationOrder - beanProperties.removeIf { bean -> amqpProperties.none { amqp -> amqp.serializer.name == bean.name } } + val amqpProperties = propertiesForSerialization(ctor, beanDesc.beanClass, serializerFactory) + .serializationOrder + .map { it.serializer.name } + beanProperties.removeIf { it.name !in amqpProperties } + (amqpProperties - beanProperties.map { it.name }).let { + check(it.isEmpty()) { "Jackson didn't provide serialisers for $it" } + } } return beanProperties } @@ -85,26 +89,31 @@ private class CordaSerializableBeanSerializerModifier : BeanSerializerModifier() private interface NetworkHostAndPortMixin private class NetworkHostAndPortDeserializer : JsonDeserializer() { - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext) = NetworkHostAndPort.parse(parser.text) + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NetworkHostAndPort { + return NetworkHostAndPort.parse(parser.text) + } } @JsonSerialize(using = PartyAndCertificateSerializer::class) // TODO Add deserialization which follows the same lookup logic as Party -private interface PartyAndCertificateSerializerMixin +private interface PartyAndCertificateMixin private class PartyAndCertificateSerializer : JsonSerializer() { override fun serialize(value: PartyAndCertificate, gen: JsonGenerator, serializers: SerializerProvider) { - gen.jsonObject { - writeObjectField("name", value.name) - writeObjectField("owningKey", value.owningKey) - // TODO Add configurable option to output the certPath + val mapper = gen.codec as JacksonSupport.PartyObjectMapper + if (mapper.isFullParties) { + gen.writeObject(PartyAndCertificateWrapper(value.name, value.certPath)) + } else { + gen.writeObject(value.party) } } } +private class PartyAndCertificateWrapper(val name: CordaX500Name, val certPath: CertPath) + @JsonSerialize(using = SignedTransactionSerializer::class) @JsonDeserialize(using = SignedTransactionDeserializer::class) -private interface SignedTransactionMixin2 +private interface SignedTransactionMixin private class SignedTransactionSerializer : JsonSerializer() { override fun serialize(value: SignedTransaction, gen: JsonGenerator, serializers: SerializerProvider) { diff --git a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt index 5847e52c10..4283523a33 100644 --- a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt +++ b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt @@ -14,10 +14,7 @@ import net.corda.core.contracts.Amount import net.corda.core.cordapp.CordappProvider import net.corda.core.crypto.* import net.corda.core.crypto.CompositeKey -import net.corda.core.identity.AbstractParty -import net.corda.core.identity.AnonymousParty -import net.corda.core.identity.CordaX500Name -import net.corda.core.identity.Party +import net.corda.core.identity.* import net.corda.core.internal.DigitalSignatureWithCert import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub @@ -247,10 +244,20 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: assertThat(json.textValue()).isEqualTo(MINI_CORP.name.toString()) } + @Test + fun `Party serialization with isFullParty = true`() { + partyObjectMapper.isFullParties = true + val json = mapper.valueToTree(MINI_CORP.party) + val (name, owningKey) = json.assertHasOnlyFields("name", "owningKey") + assertThat(name.valueAs(mapper)).isEqualTo(MINI_CORP.name) + assertThat(owningKey.valueAs(mapper)).isEqualTo(MINI_CORP.publicKey) + } + @Test fun `Party deserialization on full name`() { fun convertToParty() = mapper.convertValue(TextNode(MINI_CORP.name.toString())) + // Check that it fails if it can't find the party assertThatThrownBy { convertToParty() } partyObjectMapper.identities += MINI_CORP.party @@ -261,6 +268,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: fun `Party deserialization on part of name`() { fun convertToParty() = mapper.convertValue(TextNode(MINI_CORP.name.organisation)) + // Check that it fails if it can't find the party assertThatThrownBy { convertToParty() } partyObjectMapper.identities += MINI_CORP.party @@ -271,12 +279,24 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: fun `Party deserialization on public key`() { fun convertToParty() = mapper.convertValue(TextNode(MINI_CORP.publicKey.toBase58String())) + // Check that it fails if it can't find the party assertThatThrownBy { convertToParty() } partyObjectMapper.identities += MINI_CORP.party assertThat(convertToParty()).isEqualTo(MINI_CORP.party) } + @Test + fun `Party deserialization on name and key`() { + val party = mapper.convertValue(mapOf( + "name" to MINI_CORP.name, + "owningKey" to MINI_CORP.publicKey + )) + // Party.equals is only defined on the public key so we must check the name as well + assertThat(party.name).isEqualTo(MINI_CORP.name) + assertThat(party.owningKey).isEqualTo(MINI_CORP.publicKey) + } + @Test fun PublicKey() { val json = mapper.valueToTree(MINI_CORP.publicKey) @@ -316,15 +336,31 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: } @Test - fun `PartyAndCertificate serialisation`() { - val json = mapper.valueToTree(MINI_CORP.identity) - val (name, owningKey) = json.assertHasOnlyFields("name", "owningKey") - assertThat(name.valueAs(mapper)).isEqualTo(MINI_CORP.name) - assertThat(owningKey.valueAs(mapper)).isEqualTo(MINI_CORP.publicKey) + fun `PartyAndCertificate serialization`() { + val json = mapper.valueToTree(MINI_CORP.identity) + assertThat(json.textValue()).isEqualTo(MINI_CORP.name.toString()) } @Test - fun `NodeInfo serialisation`() { + fun `PartyAndCertificate serialization with isFullParty = true`() { + partyObjectMapper.isFullParties = true + val json = mapper.valueToTree(MINI_CORP.identity) + println(mapper.writeValueAsString(json)) + val (name, certPath) = json.assertHasOnlyFields("name", "certPath") + assertThat(name.valueAs(mapper)).isEqualTo(MINI_CORP.name) + assertThat(certPath.valueAs(mapper)).isEqualTo(MINI_CORP.identity.certPath) + } + + @Test + fun `PartyAndCertificate deserialization on cert path`() { + val certPathJson = mapper.valueToTree(MINI_CORP.identity.certPath) + val partyAndCert = mapper.convertValue(mapOf("certPath" to certPathJson)) + // PartyAndCertificate.equals is defined on the Party so we must check the certPath directly + assertThat(partyAndCert.certPath).isEqualTo(MINI_CORP.identity.certPath) + } + + @Test + fun `NodeInfo serialization`() { val (nodeInfo) = createNodeInfoAndSigned(ALICE_NAME) val json = mapper.valueToTree(nodeInfo) val (addresses, legalIdentitiesAndCerts, platformVersion, serial) = json.assertHasOnlyFields( @@ -339,14 +375,14 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: } legalIdentitiesAndCerts.run { assertThat(this).hasSize(1) - assertThat(this[0]["name"].valueAs(mapper)).isEqualTo(ALICE_NAME) + assertThat(this[0].valueAs(mapper)).isEqualTo(ALICE_NAME) } assertThat(platformVersion.intValue()).isEqualTo(nodeInfo.platformVersion) assertThat(serial.longValue()).isEqualTo(nodeInfo.serial) } @Test - fun `NodeInfo deserialisation on name`() { + fun `NodeInfo deserialization on name`() { val (nodeInfo) = createNodeInfoAndSigned(ALICE_NAME) fun convertToNodeInfo() = mapper.convertValue(TextNode(ALICE_NAME.toString())) @@ -359,7 +395,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: } @Test - fun `NodeInfo deserialisation on public key`() { + fun `NodeInfo deserialization on public key`() { val (nodeInfo) = createNodeInfoAndSigned(ALICE_NAME) fun convertToNodeInfo() = mapper.convertValue(TextNode(nodeInfo.legalIdentities[0].owningKey.toBase58String())) @@ -386,7 +422,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: } @Test - fun X509Certificate() { + fun `X509Certificate serialization`() { val cert: X509Certificate = MINI_CORP.identity.certificate val json = mapper.valueToTree(cert) println(mapper.writeValueAsString(json)) @@ -397,7 +433,13 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: assertThat(json["notAfter"].valueAs(mapper)).isEqualTo(cert.notAfter) assertThat(json["notBefore"].valueAs(mapper)).isEqualTo(cert.notBefore) assertThat(json["encoded"].binaryValue()).isEqualTo(cert.encoded) - assertThat(mapper.convertValue(json).encoded).isEqualTo(cert.encoded) + } + + @Test + fun `X509Certificate deserialization`() { + val cert: X509Certificate = MINI_CORP.identity.certificate + assertThat(mapper.convertValue(mapOf("encoded" to cert.encoded))).isEqualTo(cert) + assertThat(mapper.convertValue(BinaryNode(cert.encoded))).isEqualTo(cert) } @Test @@ -448,6 +490,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: } private class TestPartyObjectMapper : JacksonSupport.PartyObjectMapper { + override var isFullParties: Boolean = false val identities = ArrayList() val nodes = ArrayList() override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? { diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 5f4472b884..43338d4706 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -80,6 +80,17 @@ import kotlin.reflect.KClass import kotlin.reflect.full.createInstance val Throwable.rootCause: Throwable get() = cause?.rootCause ?: this +val Throwable.rootMessage: String? get() { + var message = this.message + var throwable = cause + while (throwable != null) { + if (throwable.message != null) { + message = throwable.message + } + throwable = throwable.cause + } + return message +} infix fun Temporal.until(endExclusive: Temporal): Duration = Duration.between(this, endExclusive) diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt index c9b8cc686c..02447e9a2b 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt @@ -209,7 +209,8 @@ object SerializationDefaults { /** * Convenience extension method for deserializing a ByteSequence, utilising the defaults. */ -inline fun ByteSequence.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): T { +inline fun ByteSequence.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, + context: SerializationContext = serializationFactory.defaultContext): T { return serializationFactory.deserialize(this, T::class.java, context) } @@ -218,31 +219,40 @@ inline fun ByteSequence.deserialize(serializationFactory: Seri * It might be helpful to know [SerializationContext] to use the same encoding in the reply. */ inline fun ByteSequence.deserializeWithCompatibleContext(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, - context: SerializationContext = serializationFactory.defaultContext): ObjectWithCompatibleContext { + context: SerializationContext = serializationFactory.defaultContext): ObjectWithCompatibleContext { return serializationFactory.deserializeWithCompatibleContext(this, T::class.java, context) } /** * Convenience extension method for deserializing SerializedBytes with type matching, utilising the defaults. */ -inline fun SerializedBytes.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): T { +inline fun SerializedBytes.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, + context: SerializationContext = serializationFactory.defaultContext): T { return serializationFactory.deserialize(this, T::class.java, context) } /** * Convenience extension method for deserializing a ByteArray, utilising the defaults. */ -inline fun ByteArray.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): T = this.sequence().deserialize(serializationFactory, context) +inline fun ByteArray.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, + context: SerializationContext = serializationFactory.defaultContext): T { + require(isNotEmpty()) { "Empty bytes" } + return this.sequence().deserialize(serializationFactory, context) +} /** * Convenience extension method for deserializing a JDBC Blob, utilising the defaults. */ -inline fun Blob.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): T = this.getBytes(1, this.length().toInt()).deserialize(serializationFactory, context) +inline fun Blob.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, + context: SerializationContext = serializationFactory.defaultContext): T { + return this.getBytes(1, this.length().toInt()).deserialize(serializationFactory, context) +} /** * Convenience extension method for serializing an object of type T, utilising the defaults. */ -fun T.serialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): SerializedBytes { +fun T.serialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, + context: SerializationContext = serializationFactory.defaultContext): SerializedBytes { return serializationFactory.serialize(this, context) } diff --git a/docs/source/blob-inspector.rst b/docs/source/blob-inspector.rst new file mode 100644 index 0000000000..ba301eef0a --- /dev/null +++ b/docs/source/blob-inspector.rst @@ -0,0 +1,63 @@ +Blob Inspector +============== + +There are many benefits to having a custom binary serialisation format (see :doc:`serialization` for details) but one +disadvantage is the inability to view the contents in a human-friendly manner. The blob inspector tool alleviates this issue +by allowing the contents of a binary blob file (or URL end-point) to be output in either YAML or JSON. It uses +``JacksonSupport`` to do this (see :doc:`json`). + +The latest version of the tool can be downloaded from `here `_. + +To run simply pass in the file or URL as the first parameter: + +``java -jar blob-inspector.jar `` + +Use the ``--help`` flag for a full list of command line options. + +``SerializedBytes` +~~~~~~~~~~~~~~~~~~ + +One thing to note is that the binary blob may contain embedded ``SerializedBytes`` objects. Rather than printing these +out as a Base64 string, the blob inspector will first materialise them into Java objects and then output those. You will +see this when dealing with classes such as ``SignedData`` or other structures that attach a signature, such as the +``nodeInfo-*`` files or the ``network-parameters`` file in the node's directory. For example, the output of a node-info +file may look like: + +.. container:: codeset + + .. sourcecode:: yaml + + net.corda.nodeapi.internal.SignedNodeInfo + --- + raw: + class: "net.corda.core.node.NodeInfo" + deserialized: + addresses: + - "localhost:10011" + legalIdentitiesAndCerts: + - "O=BankOfCorda, L=New York, C=US" + platformVersion: 4 + serial: 1527074180971 + signatures: + - !!binary | + dmoAnnzcv0MzRN+3ZSCDcCJIAbXnoYy5mFWB3Nijndzu/dzIoYdIawINXbNSY/5z2XloDK01vZRV + TreFZCbZAg== + + .. sourcecode:: json + + net.corda.nodeapi.internal.SignedNodeInfo + { + "raw" : { + "class" : "net.corda.core.node.NodeInfo", + "deserialized" : { + "addresses" : [ "localhost:10011" ], + "legalIdentitiesAndCerts" : [ "O=BankOfCorda, L=New York, C=US" ], + "platformVersion" : 4, + "serial" : 1527074180971 + } + }, + "signatures" : [ "dmoAnnzcv0MzRN+3ZSCDcCJIAbXnoYy5mFWB3Nijndzu/dzIoYdIawINXbNSY/5z2XloDK01vZRVTreFZCbZAg==" ] + } + +Notice the file is actually a serialised ``SignedNodeInfo`` object, which has a ``raw`` property of type ``SerializedBytes``. +This property is materialised into a ``NodeInfo`` and is output under the ``deserialized`` field. diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index b79be24fa3..4d2931eb76 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -34,12 +34,19 @@ Unreleased * ``Party`` objects can be deserialised by looking up their public key, in addition to their name * ``NodeInfo`` objects are serialised as an object and can be looked up using the same mechanism as ``Party`` * ``NetworkHostAndPort`` serialised according to its ``toString()`` - * ``PartyAndCertificate`` is serialised as an object containing the name and owning key - * ``SerializedBytes`` is serialised by converting the bytes into the object it represents, which is then serialised into - a JSON/YAML object - * ``CertPath`` and ``X509Certificate`` are serialised as objects and can be deserialised back + * ``PartyAndCertificate`` is serialised as the name + * ``SerializedBytes`` is serialised by materialising the bytes into the object it represents, and then serialising that + object into YAML/JSON + * ``X509Certificate`` is serialised as an object with key fields such as ``issuer``, ``publicKey``, ``serialNumber``, etc. + The encoded bytes are also serialised into the ``encoded`` field. This can be used to deserialise an ``X509Certificate`` + back. + * ``CertPath`` objects are serialised as a list of ``X509Certificate`` objects. * ``SignedTransaction`` is serialised into its ``txBits`` and ``signatures`` and can be deserialised back +* ``fullParties`` boolean parameter added to ``JacksonSupport.createDefaultMapper`` and ``createNonRpcMapper``. If ``true`` + then ``Party`` objects are serialised as JSON objects with the ``name`` and ``owningKey`` fields. For ``PartyAndCertificate`` + the ``certPath`` is serialised. + * Several members of ``JacksonSupport`` have been deprecated to highlight that they are internal and not to be used. * The Vault Criteria API has been extended to take a more precise specification of which class contains a field. This diff --git a/docs/source/tools-index.rst b/docs/source/tools-index.rst index 55b398dd35..c9bcafc44b 100644 --- a/docs/source/tools-index.rst +++ b/docs/source/tools-index.rst @@ -4,6 +4,7 @@ Tools .. toctree:: :maxdepth: 1 + blob-inspector network-simulator demobench node-explorer diff --git a/experimental/blobinspector/build.gradle b/experimental/blobinspector/build.gradle deleted file mode 100644 index 2862ff6fae..0000000000 --- a/experimental/blobinspector/build.gradle +++ /dev/null @@ -1,52 +0,0 @@ -apply plugin: 'java' -apply plugin: 'kotlin' -apply plugin: 'application' - -mainClassName = 'net.corda.blobinspector.MainKt' - -dependencies { - compile project(':core') - compile project(':node-api') - - compile "commons-cli:commons-cli:$commons_cli_version" - - testCompile project(':test-utils') - - testCompile "junit:junit:$junit_version" -} - -/** - * To run from within gradle use - * - * ./gradlew -PrunArgs=" " :experimental:blobinspector:run - * - * For example, to parse a file from the command line and print out the deserialized properties - * - * ./gradlew -PrunArgs="-f -d" :experimental:blobinspector:run - * - * at the command line. - */ -run { - if (project.hasProperty('runArgs')) { - args = [ project.findProperty('runArgs').toString().split(" ") ].flatten() - } - - if (System.properties.getProperty('consoleLogLevel') != null) { - logging.captureStandardOutput(LogLevel.valueOf(System.properties.getProperty('consoleLogLevel'))) - logging.captureStandardError(LogLevel.valueOf(System.properties.getProperty('consoleLogLevel'))) - systemProperty "consoleLogLevel", System.properties.getProperty('consoleLogLevel') - } -} - -/** - * Build a executable jar - */ -jar { - baseName 'blobinspector' - manifest { - attributes( - 'Automatic-Module-Name': 'net.corda.experimental.blobinspector', - 'Main-Class': 'net.corda.blobinspector.MainKt' - ) - } -} diff --git a/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt b/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt deleted file mode 100644 index 84de454358..0000000000 --- a/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt +++ /dev/null @@ -1,405 +0,0 @@ -package net.corda.blobinspector - -import net.corda.core.crypto.SecureHash -import net.corda.core.serialization.EncodingWhitelist -import net.corda.core.serialization.SerializationEncoding -import net.corda.core.utilities.ByteSequence -import net.corda.serialization.internal.SerializationFactoryImpl -import net.corda.serialization.internal.amqp.CompositeType -import net.corda.serialization.internal.amqp.DeserializationInput -import net.corda.serialization.internal.amqp.RestrictedType -import net.corda.serialization.internal.amqp.TypeNotation -import net.corda.serialization.internal.amqp.amqpMagic -import org.apache.qpid.proton.amqp.Binary -import org.apache.qpid.proton.amqp.DescribedType -import org.apache.qpid.proton.amqp.Symbol - -/** - * Print a string to the console only if the verbose config option is set. - */ -fun String.debug(config: Config) { - if (config.verbose) { - println(this) - } -} - -/** - * - */ -interface Stringify { - fun stringify(sb: IndentingStringBuilder) -} - -/** - * Makes classnames easier to read by stripping off the package names from the class and separating nested - * classes - * - * For example: - * - * net.corda.blobinspector.Class1 - * Class1 - * - * net.corda.blobinspector.Class1 - * Class1 - * - * net.corda.blobinspector.Class1> - * Class1 > - * - * net.corda.blobinspector.Class1> - * Class1 :: C > - */ -fun String.simplifyClass(): String { - - return if (this.endsWith('>')) { - val templateStart = this.indexOf('<') - val clazz = (this.substring(0, templateStart)) - val params = this.substring(templateStart+1, this.length-1).split(',').joinToString { it.simplifyClass() } - - "${clazz.simplifyClass()} <$params>" - } - else { - substring(this.lastIndexOf('.') + 1).replace("$", " :: ") - } -} - -/** - * Represents the deserialized form of the property of an Object - * - * @param name - * @param type - */ -abstract class Property( - val name: String, - val type: String) : Stringify - -/** - * Derived class of [Property], represents properties of an object that are non compelex, such - * as any POD type or String - */ -class PrimProperty( - name: String, - type: String, - private val value: String) : Property(name, type) { - override fun toString(): String = "$name : $type : $value" - - override fun stringify(sb: IndentingStringBuilder) { - sb.appendln("$name : $type : $value") - } -} - -/** - * Derived class of [Property] that represents a binary blob. Specifically useful because printing - * a stream of bytes onto the screen isn't very use friendly - */ -class BinaryProperty( - name: String, - type: String, - val value: ByteArray) : Property(name, type) { - override fun toString(): String = "$name : $type : <<>>" - - override fun stringify(sb: IndentingStringBuilder) { - sb.appendln("$name : $type : <<>>") - } -} - -/** - * Derived class of [Property] that represent a list property. List could be either PoD types or - * composite types. - */ -class ListProperty( - name: String, - type: String, - private val values: MutableList = mutableListOf()) : Property(name, type) { - override fun stringify(sb: IndentingStringBuilder) { - sb.apply { - when { - values.isEmpty() -> appendln("$name : $type : [ << EMPTY LIST >> ]") - values.first() is Stringify -> { - appendln("$name : $type : [") - values.forEach { - (it as Stringify).stringify(this) - } - appendln("]") - } - else -> { - appendln("$name : $type : [") - values.forEach { - appendln(it.toString()) - } - appendln("]") - } - } - } - } -} - -class MapProperty( - name: String, - type: String, - private val map: MutableMap<*, *> -) : Property(name, type) { - override fun stringify(sb: IndentingStringBuilder) { - if (map.isEmpty()) { - sb.appendln("$name : $type : { << EMPTY MAP >> }") - return - } - - // TODO this will not produce pretty output - sb.apply { - appendln("$name : $type : {") - map.forEach { - try { - (it.key as Stringify).stringify(this) - } catch (e: ClassCastException) { - append (it.key.toString() + " : ") - } - try { - (it.value as Stringify).stringify(this) - } catch (e: ClassCastException) { - appendln("\"${it.value.toString()}\"") - } - } - appendln("}") - } - } -} - -/** - * Derived class of [Property] that represents class properties that are themselves instances of - * some complex type. - */ -class InstanceProperty( - name: String, - type: String, - val value: Instance) : Property(name, type) { - override fun stringify(sb: IndentingStringBuilder) { - sb.append("$name : ") - value.stringify(sb) - } -} - -/** - * Represents an instance of a composite type. - */ -class Instance( - val name: String, - val type: String, - val fields: MutableList = mutableListOf()) : Stringify { - override fun stringify(sb: IndentingStringBuilder) { - sb.apply { - appendln("${name.simplifyClass()} : {") - fields.forEach { - it.stringify(this) - } - appendln("}") - } - } -} - -/** - * - */ -fun inspectComposite( - config: Config, - typeMap: Map, - obj: DescribedType): Instance { - if (obj.described !is List<*>) throw MalformedBlob("") - - val name = (typeMap[obj.descriptor] as CompositeType).name - "composite: $name".debug(config) - - val inst = Instance( - typeMap[obj.descriptor]?.name ?: "", - typeMap[obj.descriptor]?.label ?: "") - - (typeMap[obj.descriptor] as CompositeType).fields.zip(obj.described as List<*>).forEach { - " field: ${it.first.name}".debug(config) - inst.fields.add( - if (it.second is DescribedType) { - " - is described".debug(config) - val d = inspectDescribed(config, typeMap, it.second as DescribedType) - - when (d) { - is Instance -> - InstanceProperty( - it.first.name, - it.first.type, - d) - is List<*> -> { - " - List".debug(config) - ListProperty( - it.first.name, - it.first.type, - d as MutableList) - } - is Map<*, *> -> { - MapProperty( - it.first.name, - it.first.type, - d as MutableMap<*, *>) - } - else -> { - " skip it".debug(config) - return@forEach - } - } - - } else { - " - is prim".debug(config) - when (it.first.type) { - // Note, as in the case of SHA256 we can treat particular binary types - // as different properties with a little coercion - "binary" -> { - if (name == "net.corda.core.crypto.SecureHash\$SHA256") { - PrimProperty( - it.first.name, - it.first.type, - SecureHash.SHA256((it.second as Binary).array).toString()) - } else { - BinaryProperty(it.first.name, it.first.type, (it.second as Binary).array) - } - } - else -> PrimProperty(it.first.name, it.first.type, it.second.toString()) - } - }) - } - - return inst -} - -fun inspectRestricted( - config: Config, - typeMap: Map, - obj: DescribedType): Any { - return when ((typeMap[obj.descriptor] as RestrictedType).source) { - "list" -> inspectRestrictedList(config, typeMap, obj) - "map" -> inspectRestrictedMap(config, typeMap, obj) - else -> throw NotImplementedError() - } -} - - -fun inspectRestrictedList( - config: Config, - typeMap: Map, - obj: DescribedType -) : List { - if (obj.described !is List<*>) throw MalformedBlob("") - - return mutableListOf().apply { - (obj.described as List<*>).forEach { - when (it) { - is DescribedType -> add(inspectDescribed(config, typeMap, it)) - is RestrictedType -> add(inspectRestricted(config, typeMap, it)) - else -> add (it.toString()) - } - } - } -} - -fun inspectRestrictedMap( - config: Config, - typeMap: Map, - obj: DescribedType -) : Map { - if (obj.described !is Map<*,*>) throw MalformedBlob("") - - return mutableMapOf().apply { - (obj.described as Map<*, *>).forEach { - val key = when (it.key) { - is DescribedType -> inspectDescribed(config, typeMap, it.key as DescribedType) - is RestrictedType -> inspectRestricted(config, typeMap, it.key as RestrictedType) - else -> it.key.toString() - } - - val value = when (it.value) { - is DescribedType -> inspectDescribed(config, typeMap, it.value as DescribedType) - is RestrictedType -> inspectRestricted(config, typeMap, it.value as RestrictedType) - else -> it.value.toString() - } - - this[key] = value - } - } -} - - -/** - * Every element of the blob stream will be a ProtonJ [DescribedType]. When inspecting the blob stream - * the two custom Corda types we're interested in are [CompositeType]'s, representing the instance of - * some object (class), and [RestrictedType]'s, representing containers and enumerations. - * - * @param config The configuration object that controls the behaviour of the BlobInspector - * @param typeMap - * @param obj - */ -fun inspectDescribed( - config: Config, - typeMap: Map, - obj: DescribedType): Any { - "${obj.descriptor} in typeMap? = ${obj.descriptor in typeMap}".debug(config) - - return when (typeMap[obj.descriptor]) { - is CompositeType -> { - "* It's composite".debug(config) - inspectComposite(config, typeMap, obj) - } - is RestrictedType -> { - "* It's restricted".debug(config) - inspectRestricted(config, typeMap, obj) - } - else -> { - "${typeMap[obj.descriptor]?.name} is neither Composite or Restricted".debug(config) - } - } - -} - -internal object NullEncodingWhitelist : EncodingWhitelist { - override fun acceptEncoding(encoding: SerializationEncoding) = false -} - -// TODO : Refactor to generically poerate on arbitrary blobs, not a single workflow -fun inspectBlob(config: Config, blob: ByteArray) { - val bytes = ByteSequence.of(blob) - - val headerSize = SerializationFactoryImpl.magicSize - - // TODO written to only understand one version, when we support multiple this will need to change - val headers = listOf(ByteSequence.of(amqpMagic.bytes)) - - val blobHeader = bytes.take(headerSize) - - if (blobHeader !in headers) { - throw MalformedBlob("Blob is not a Corda AMQP serialised object graph") - } - - - val e = DeserializationInput.getEnvelope(bytes, NullEncodingWhitelist) - - if (config.schema) { - println(e.schema) - } - - if (config.transforms) { - println(e.transformsSchema) - } - - val typeMap = e.schema.types.associateBy({ it.descriptor.name }, { it }) - - if (config.data) { - val inspected = inspectDescribed(config, typeMap, e.obj as DescribedType) - - println("\n${IndentingStringBuilder().apply { (inspected as Instance).stringify(this) }}") - - (inspected as Instance).fields.find { - it.type.startsWith("net.corda.core.serialization.SerializedBytes<") - }?.let { - "Found field of SerializedBytes".debug(config) - (it as InstanceProperty).value.fields.find { it.name == "bytes" }?.let { raw -> - inspectBlob(config, (raw as BinaryProperty).value) - } - } - } -} - diff --git a/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobLoader.kt b/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobLoader.kt deleted file mode 100644 index c831665036..0000000000 --- a/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobLoader.kt +++ /dev/null @@ -1,40 +0,0 @@ -package net.corda.blobinspector - -import java.io.File -import java.net.URL - -/** - * - */ -class FileBlobHandler(config_: Config) : BlobHandler(config_) { - private val path = File(URL((config_ as FileConfig).file).toURI()) - - override fun getBytes(): ByteArray { - return path.readBytes() - } -} - -/** - * - */ -class InMemoryBlobHandler(config_: Config) : BlobHandler(config_) { - private val localBytes = (config_ as InMemoryConfig).blob?.bytes ?: kotlin.ByteArray(0) - override fun getBytes(): ByteArray = localBytes -} - -/** - * - */ -abstract class BlobHandler(val config: Config) { - companion object { - fun make(config: Config): BlobHandler { - return when (config.mode) { - Mode.file -> FileBlobHandler(config) - Mode.inMem -> InMemoryBlobHandler(config) - } - } - } - - abstract fun getBytes(): ByteArray -} - diff --git a/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Config.kt b/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Config.kt deleted file mode 100644 index 376331ec2b..0000000000 --- a/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Config.kt +++ /dev/null @@ -1,137 +0,0 @@ -package net.corda.blobinspector - -import org.apache.commons.cli.CommandLine -import net.corda.core.serialization.SerializedBytes -import org.apache.commons.cli.Option -import org.apache.commons.cli.Options - -/** - * Enumeration of the modes in which the blob inspector can be run. - * - * @property make lambda function that takes no parameters and returns a specific instance of the configuration - * object for that mode. - * - * @property options A lambda function that takes no parameters and returns an [Options] instance that define - * the command line flags related to this mode. For example ``file`` mode would have an option to pass in - * the name of the file to read. - * - */ -enum class Mode( - val make : () -> Config, - val options : (Options) -> Unit -) { - file( - { - FileConfig(Mode.file) - }, - { o -> - o.apply{ - addOption( - Option ("f", "file", true, "path to file").apply { - isRequired = true - } - ) - } - } - ), - inMem( - { - InMemoryConfig(Mode.inMem) - }, - { - // The in memory only mode has no specific option assocaited with it as it's intended for - // testing purposes only within the unit test framework and not use on the command line - } - ) -} - -/** - * Configuration data class for the Blob Inspector. - * - * @property mode - */ -abstract class Config (val mode: Mode) { - var schema: Boolean = false - var transforms: Boolean = false - var data: Boolean = false - var verbose: Boolean = false - - abstract fun populateSpecific(cmdLine: CommandLine) - abstract fun withVerbose() : Config - - fun populate(cmdLine: CommandLine) { - schema = cmdLine.hasOption('s') - transforms = cmdLine.hasOption('t') - data = cmdLine.hasOption('d') - verbose = cmdLine.hasOption('v') - - populateSpecific(cmdLine) - } - - fun options() = Options().apply { - // install generic options - addOption(Option("s", "schema", false, "print the blob's schema").apply { - isRequired = false - }) - - addOption(Option("t", "transforms", false, "print the blob's transforms schema").apply { - isRequired = false - }) - - addOption(Option("d", "data", false, "Display the serialised data").apply { - isRequired = false - }) - - addOption(Option("v", "verbose", false, "Enable debug output").apply { - isRequired = false - }) - - // install the mode specific options - mode.options(this) - } -} - - -/** - * Configuration object when running in "File" mode, i.e. the object has been specified at - * the command line - */ -class FileConfig ( - mode: Mode -) : Config(mode) { - - var file: String = "unset" - - override fun populateSpecific(cmdLine : CommandLine) { - file = cmdLine.getParsedOptionValue("f") as String - } - - override fun withVerbose() : FileConfig { - return FileConfig(mode).apply { - this.schema = schema - this.transforms = transforms - this.data = data - this.verbose = true - } - } -} - - -/** - * Placeholder config objet used when running unit tests and the inspected blob is being fed in - * via some mechanism directly. Normally this will be the direct serialisation of an object in a unit - * test and then dumping that blob into the inspector for visual comparison of the output - */ -class InMemoryConfig ( - mode: Mode -) : Config(mode) { - var blob: SerializedBytes<*>? = null - - override fun populateSpecific(cmdLine: CommandLine) { - throw UnsupportedOperationException("In memory config is for testing only and cannot set specific flags") - } - - override fun withVerbose(): Config { - throw UnsupportedOperationException("In memory config is for testing headlessly, cannot be verbose") - } -} diff --git a/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Errors.kt b/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Errors.kt deleted file mode 100644 index 888ef1e302..0000000000 --- a/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Errors.kt +++ /dev/null @@ -1,3 +0,0 @@ -package net.corda.blobinspector - -class MalformedBlob(msg: String) : Exception(msg) diff --git a/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/IndentingStringBuilder.kt b/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/IndentingStringBuilder.kt deleted file mode 100644 index 1ec7fe6557..0000000000 --- a/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/IndentingStringBuilder.kt +++ /dev/null @@ -1,44 +0,0 @@ -package net.corda.blobinspector - -/** - * Wrapper around a [StringBuilder] that automates the indenting of lines as they're appended to facilitate - * pretty printing of deserialized blobs. - * - * @property sb The wrapped [StringBuilder] - * @property indenting Boolean flag that indicates weather we need to pad the start of whatever text - * currently being added to the string. - * @property indent How deeply the next line should be offset from the first column - */ -class IndentingStringBuilder(s: String = "", private val offset: Int = 4) { - private val sb = StringBuilder(s) - private var indenting = true - private var indent = 0 - - private fun wrap(ln: String, appender: (String) -> Unit) { - if ((ln.endsWith("}") || ln.endsWith("]")) && indent > 0 && ln.length == 1) { - indent -= offset - } - - appender(ln) - - if (ln.endsWith("{") || ln.endsWith("[")) { - indent += offset - } - } - - fun appendln(ln: String) { - wrap(ln) { s -> sb.appendln("${"".padStart(if (indenting) indent else 0, ' ')}$s") } - - indenting = true - } - - fun append(ln: String) { - indenting = false - - wrap(ln) { s -> sb.append("${"".padStart(indent, ' ')}$s") } - } - - override fun toString(): String { - return sb.toString() - } -} \ No newline at end of file diff --git a/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt b/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt deleted file mode 100644 index 9abe288afa..0000000000 --- a/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt +++ /dev/null @@ -1,81 +0,0 @@ -package net.corda.blobinspector - -import org.apache.commons.cli.* -import java.lang.IllegalArgumentException - -/** - * Mode isn't a required property as we default it to [Mode.file] - */ -private fun modeOption() = Option("m", "mode", true, "mode, file is the default").apply { - isRequired = false -} - -/** - * - * Parse the command line arguments looking for the main mode into which the application is - * being put. Note, this defaults to [Mode.file] if not set meaning we will look for a file path - * being passed as a parameter and parse that file. - * - * @param args reflects the command line arguments - * - * @return An instantiated but unpopulated [Config] object instance suitable for the mode into - * which we've been placed. This Config object should be populated via [loadModeSpecificOptions] - */ -fun getMode(args: Array): Config { - // For now we only care what mode we're being put in, we can build the rest of the args and parse them - // later - val options = Options().apply { - addOption(modeOption()) - } - - val cmd = try { - DefaultParser().parse(options, args, true) - } catch (e: org.apache.commons.cli.ParseException) { - println(e) - HelpFormatter().printHelp("blobinspector", options) - throw IllegalArgumentException("OH NO!!!") - } - - return try { - Mode.valueOf(cmd.getParsedOptionValue("m") as? String ?: "file") - } catch (e: IllegalArgumentException) { - Mode.file - }.make() -} - -/** - * - * @param config an instance of a [Config] specialisation suitable for the mode into which - * the application has been put. - * @param args The command line arguments - */ -fun loadModeSpecificOptions(config: Config, args: Array) { - config.apply { - // load that modes specific command line switches, needs to include the mode option - val modeSpecificOptions = config.options().apply { - addOption(modeOption()) - } - - populate(try { - DefaultParser().parse(modeSpecificOptions, args, false) - } catch (e: org.apache.commons.cli.ParseException) { - println("Error: ${e.message}") - HelpFormatter().printHelp("blobinspector", modeSpecificOptions) - System.exit(1) - return - }) - } -} - -/** - * Executable entry point - */ -fun main(args: Array) { - println("<<< WARNING: this tool is experimental and under active development >>>") - getMode(args).let { mode -> - loadModeSpecificOptions(mode, args) - BlobHandler.make(mode) - }.apply { - inspectBlob(config, getBytes()) - } -} diff --git a/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/FileParseTests.kt b/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/FileParseTests.kt deleted file mode 100644 index c0a87a667e..0000000000 --- a/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/FileParseTests.kt +++ /dev/null @@ -1,87 +0,0 @@ -package net.corda.blobinspector - -import java.net.URI - -import org.junit.Test -import net.corda.testing.common.internal.ProjectStructure.projectRootDir - -class FileParseTests { - @Suppress("UNUSED") - var localPath: URI = projectRootDir.toUri().resolve( - "tools/blobinspector/src/test/resources/net/corda/blobinspector") - - fun setupArgsWithFile(path: String) = Array(5) { - when (it) { - 0 -> "-m" - 1 -> "file" - 2 -> "-f" - 3 -> path - 4 -> "-d" - else -> "error" - } - } - - private val filesToTest = listOf( - "FileParseTests.1Int", - "FileParseTests.2Int", - "FileParseTests.3Int", - "FileParseTests.1String", - "FileParseTests.1Composite", - "FileParseTests.2Composite", - "FileParseTests.IntList", - "FileParseTests.StringList", - "FileParseTests.MapIntString", - "FileParseTests.MapIntClass" - ) - - fun testFile(file: String) { - val path = FileParseTests::class.java.getResource(file) - val args = setupArgsWithFile(path.toString()) - - val handler = getMode(args).let { mode -> - loadModeSpecificOptions(mode, args) - BlobHandler.make(mode) - } - - inspectBlob(handler.config, handler.getBytes()) - } - - @Test - fun simpleFiles() { - filesToTest.forEach { testFile(it) } - } - - @Test - fun specificTest() { - testFile(filesToTest[4]) - testFile(filesToTest[5]) - testFile(filesToTest[6]) - } - - @Test - fun networkParams() { - val file = "networkParams" - val path = FileParseTests::class.java.getResource(file) - val verbose = false - - val args = verbose.let { - if (it) - Array(4) { - when (it) { 0 -> "-f"; 1 -> path.toString(); 2 -> "-d"; 3 -> "-vs"; else -> "error" - } - } - else - Array(3) { - when (it) { 0 -> "-f"; 1 -> path.toString(); 2 -> "-d"; else -> "error" - } - } - } - - val handler = getMode(args).let { mode -> - loadModeSpecificOptions(mode, args) - BlobHandler.make(mode) - } - - inspectBlob(handler.config, handler.getBytes()) - } -} diff --git a/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/InMemoryTests.kt b/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/InMemoryTests.kt deleted file mode 100644 index 4b94bf2eea..0000000000 --- a/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/InMemoryTests.kt +++ /dev/null @@ -1,91 +0,0 @@ -package net.corda.blobinspector - -import net.corda.core.serialization.SerializedBytes -import net.corda.serialization.internal.AllWhitelist -import net.corda.serialization.internal.amqp.SerializationOutput -import net.corda.serialization.internal.amqp.SerializerFactory -import net.corda.serialization.internal.AMQP_P2P_CONTEXT -import org.junit.Test - - -class InMemoryTests { - private val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) - - private fun inspect (b: SerializedBytes<*>) { - BlobHandler.make( - InMemoryConfig(Mode.inMem).apply { blob = b; data = true} - ).apply { - inspectBlob(config, getBytes()) - } - } - - @Test - fun test1() { - data class C (val a: Int, val b: Long, val c: String) - inspect (SerializationOutput(factory).serialize(C(100, 567L, "this is a test"), AMQP_P2P_CONTEXT)) - } - - @Test - fun test2() { - data class C (val i: Int, val c: C?) - inspect (SerializationOutput(factory).serialize(C(1, C(2, C(3, C(4, null)))), AMQP_P2P_CONTEXT)) - } - - @Test - fun test3() { - data class C (val a: IntArray, val b: Array) - - val a = IntArray(10) { i -> i } - val c = C(a, arrayOf("aaa", "bbb", "ccc")) - - inspect (SerializationOutput(factory).serialize(c, AMQP_P2P_CONTEXT)) - } - - @Test - fun test4() { - data class Elem(val e1: Long, val e2: String) - data class Wrapper (val name: String, val elementes: List) - - inspect (SerializationOutput(factory).serialize( - Wrapper("Outer Class", - listOf( - Elem(1L, "First element"), - Elem(2L, "Second element"), - Elem(3L, "Third element") - )), AMQP_P2P_CONTEXT)) - } - - @Test - fun test4b() { - data class Elem(val e1: Long, val e2: String) - data class Wrapper (val name: String, val elementes: List>) - - inspect (SerializationOutput(factory).serialize( - Wrapper("Outer Class", - listOf ( - listOf( - Elem(1L, "First element"), - Elem(2L, "Second element"), - Elem(3L, "Third element") - ), - listOf( - Elem(4L, "Fourth element"), - Elem(5L, "Fifth element"), - Elem(6L, "Sixth element") - ) - )), AMQP_P2P_CONTEXT)) - } - - @Test - fun test5() { - data class C (val a: Map) - - inspect (SerializationOutput(factory).serialize( - C(mapOf( - "a" to "a a a", - "b" to "b b b", - "c" to "c c c")), - AMQP_P2P_CONTEXT - )) - } -} \ No newline at end of file diff --git a/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/ModeParse.kt b/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/ModeParse.kt deleted file mode 100644 index 80560576a4..0000000000 --- a/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/ModeParse.kt +++ /dev/null @@ -1,83 +0,0 @@ -package net.corda.blobinspector - -import org.junit.Test -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import kotlin.test.assertFalse - -class ModeParse { - @Test - fun fileIsSetToFile() { - val opts1 = Array(2) { - when (it) { - 0 -> "-m" - 1 -> "file" - else -> "error" - } - } - - assertEquals(Mode.file, getMode(opts1).mode) - } - - @Test - fun nothingIsSetToFile() { - val opts1 = Array(0) { "" } - - assertEquals(Mode.file, getMode(opts1).mode) - } - - @Test - fun filePathIsSet() { - val opts1 = Array(4) { - when (it) { - 0 -> "-m" - 1 -> "file" - 2 -> "-f" - 3 -> "path/to/file" - else -> "error" - } - } - - val config = getMode(opts1) - assertTrue(config is FileConfig) - assertEquals(Mode.file, config.mode) - assertEquals("unset", (config as FileConfig).file) - - loadModeSpecificOptions(config, opts1) - - assertEquals("path/to/file", config.file) - } - - @Test - fun schemaIsSet() { - Array(2) { - when (it) { 0 -> "-f"; 1 -> "path/to/file"; else -> "error" - } - }.let { options -> - getMode(options).apply { - loadModeSpecificOptions(this, options) - assertFalse(schema) - } - } - - Array(3) { - when (it) { 0 -> "--schema"; 1 -> "-f"; 2 -> "path/to/file"; else -> "error" - } - }.let { - getMode(it).apply { - loadModeSpecificOptions(this, it) - assertTrue(schema) - } - } - - Array(3) { - when (it) { 0 -> "-f"; 1 -> "path/to/file"; 2 -> "-s"; else -> "error" - } - }.let { - getMode(it).apply { - loadModeSpecificOptions(this, it) - assertTrue(schema) - } - } - } -} \ No newline at end of file diff --git a/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/SimplifyClassTests.kt b/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/SimplifyClassTests.kt deleted file mode 100644 index 3dcafbc88d..0000000000 --- a/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/SimplifyClassTests.kt +++ /dev/null @@ -1,28 +0,0 @@ -package net.corda.blobinspector - -import org.junit.Test - -class SimplifyClassTests { - - @Test - fun test1() { - data class A(val a: Int) - - println(A::class.java.name) - println(A::class.java.name.simplifyClass()) - } - - @Test - fun test2() { - val p = this.javaClass.`package`.name - - println("$p.Class1<$p.Class2>") - println("$p.Class1<$p.Class2>".simplifyClass()) - println("$p.Class1<$p.Class2, $p.Class3>") - println("$p.Class1<$p.Class2, $p.Class3>".simplifyClass()) - println("$p.Class1<$p.Class2<$p.Class3>>") - println("$p.Class1<$p.Class2<$p.Class3>>".simplifyClass()) - println("$p.Class1<$p.Class2<$p.Class3>>") - println("$p.Class1\$C<$p.Class2<$p.Class3>>".simplifyClass()) - } -} \ No newline at end of file diff --git a/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.1Composite b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.1Composite deleted file mode 100644 index 450e6970da613f518ebafb1bdf71cb8c212f4a05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 537 zcmYe!FG@*dWME)uIGO|`85kHZFfcGN0U68;i4ypq(Syu=*6;?$zd#GK5k#FEVXJiWx+ z!UDaJ)Z!9D=bXgiVh2~aLCgo#n27O&!vTLrxF(hZPV5UA6BnA3?f@gtypm#92M0%l wKUoi~WF^}uJYK;S&JG7G5dq0^KmhC&W?;y5b13o`XG^GZ^S@)C3Oic^a+ z6LT`F5=%1k^YjvP3k&o@Qj1FrJ@ZN&T;awrA5dc=#xV{DWD$0=91vh%$e6g08EBd7 QLPi4zM<=*}oCg^h04>={1poj5 diff --git a/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.1String b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.1String deleted file mode 100644 index 9676f0375fb488e6ec107de206caa34f9cc19f96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 262 zcmYe!FG@*dWME)uIGO|`85kH3d}3x;tdy5pqL&Pkv+{H_c1zbUjtomPFg6M^G4%>b zEwga6wLKunxR5U-BePfmh!Pb_Qj1IAhARf#2;`FVPYxrGIKAwUBSgG-7s^U@t$;U+O3P-7y-bq)vQ e5Z1FC5M*D-n7ELw7;K!YgM*_J+(gcUj0^y)B2T9P diff --git a/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.2Composite b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.2Composite deleted file mode 100644 index 0bf3a5c47551253abc26f5aa0b23a67d9549e7b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1004 zcmbtTK~KUk7;VRf@#eu8y?Wpv$y5jeM$87xC=r!_LA{kRurM~Hm4PRJf`4J*Ieb=9$#yG2LQeS5PIi_5EyM*29 z3H6-_+==k{ZAtjx2hp!dyaw0#mE@*V-EUGiin7f^6wQKBh|nw?-f=4>@L^LMu0Q=VIy z_OR6qa!Tx3T3FtkcxFwB!qSuRtc>Fc@pfn~+zFcXYBl$#+F11eQkWxs*3c>}4kvmM w8B2#-Yy-M$E!0QyJ;kQ%1${uqNgoEf7iN|4OrSP3+0)ZD>w`?#Sbl-v3;zg0-v9sr diff --git a/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.2Int b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.2Int deleted file mode 100644 index 118a23f37b86208a302c0d3be55efaf86673c3d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 284 zcmYe!FG@*dWME)uIGO|`85kHZ05L0&!OXB&DKE7|FBzo5D%mMHEj!9ItvIbB(#s{# zsKV6OT;I{w_5cS{NJ=44`3blQOb2!{9@xeR=d&JI$-2-SZj@eLeoAU$L8e}2UP)?E zUSf`3acWU!VoqjNVo7Fxo?c>ZVS!#qYH^8?XI@FMgDcz|<^yU>#5l>}fF%=L6UzYs d_Jxdz3z>noxh`ZhaBy@&lS{%X$9a&E0RTlfQnUa7 diff --git a/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.3Int b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.3Int deleted file mode 100644 index 9f00d5906853f44dcc3d7aecab4b8b5babbfbae4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 317 zcmYe!FG@*dWME)uIGO|`85kHZ0I?d7!OXB&DKE7|FBzo5s@%0S(#A+RS1D6=#eAWX;Sr?ka&C|=vPf1NI$kfZs zD@iTNOU%(LPA$qz%*m`uEXmBz(@V@PEYJ%{EiN(k%quB&aD|)0d_awf7ZWQwYH6~&_<8Z)$5w3~lfHM0+#>9n;S_i}!7Yb)3 zmL=+ymSpDWfy}o7y28%E!O;n>ob5m$8+KK$3t4i2THq?tU4tvgkUYS7kdXlZo6mpj diff --git a/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.MapIntClass b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.MapIntClass deleted file mode 100644 index 175949d9aab8cdbb3b545e805a3b620ef6ab854e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 759 zcmYe!FG@*dWME)uIGO|`85kHZFfcGZ05X^v7AxhYmgpseR9KZ{dbos^Iad?~1O)n; zM&zYr78e$m+u9xoXGBp~&_q_IkiQ{+;0|H}Imij-GE1<_V2))y@PKupE!>rQdHE@+ zi3OQ@nRz9tMR|!idc~Rf#2;`FVPYxrGIKA*sbBzKI2%c_q#{iN(bZu5g=} z52!H_<7bBhS&VQ^EC*uQ7c%E27A$1cI^f5+&^s%!EK#qtBr``3Xp0TdB{~YEJJ1N| zyka{C2S+EkMQjH;*r;o|E7;v|3(&ofD=LvfjP<}u)`jL|dJoAQbgOZt4Tl4kOmO$I f91vh%$e6g085qW{3mFZN0}CRTgjJ67AR_|+;<@xA diff --git a/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.MapIntString b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.MapIntString deleted file mode 100644 index 67ba352ec48a9e7a47412b7580915babc19fa383..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 474 zcmYe!FG@*dWME)uIGO|`85kHZ0P$fUgPCEmQeJ9_UNT69l|^x&n{k1qsc%I@h^uR% zk4I*jS5&#J?STMB6m`BXxrS*bMHQ8v7NOzBu8!tz-X2NmwzdZ~*g`}WvL-43VTi^; zj-(_7FbZK`$XAk)S*!p=i3%mD#U*f;Fo9g619Orxkii6(V?D5eb)g;Hd3t&IDXEDC znR=OdC8$f$KdmT{p}R$^JAUTH~Yjvmk&8=zBk6pFz%**Q2k fI>Gg^9SCQ`tJoE6DqKCf<8TErl7Bc4GBN-FYQBy% diff --git a/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.StringList b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.StringList deleted file mode 100644 index 5758d9fa62f3e815bac4b1ba1e6af87c07f22e02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 448 zcmYe!FG@*dWME)uIGO|`85kHZ0P#v7gPCEmQeJ9_UNT69m8)aEV|Z|QcBn;8R+@HY zZenGHMM=7??Ewcy6m51}u@Al?FG5Vr7}RJ&q{f&iQ4+(8C{bffG=Zr9Y;`-kI)#StGM(=E z=D&Ua_y0#T%nb4gg7C~oX?O~PZ%Gi=Uxt@}u-Hg3lQjqNfa7^fqH= zSKK_1-LnVK(se-ltI#vFYdWv0X5T_vegMyQV5`u*|KKvZ-f?mYK3i9|TwkQQ zbDb0OXl&k^=uh^yCJr4?2ZoxLefwg74#lfujppZKLlZND(PYQ%2-aYjV1+yQDj~hLEE04I>6aW~pHen{!0NJd`S!M4MK)zdtH0Zd)n! z44Ol-g*7;4C3&=v)`>QVOD7*|+1Vn70!=s{*e0C+>yJa%qd%Wl>pC{E_tIZZUYaa) z`zNn$FzPOTcjgNzd;}Q$zWfjtAE+_TNDj*}YB5BaE&3BeT!{NB9pDE}#it#XoR@KU z5i4gR{N#Jb`H6Q_jLOu=V3a2^p$wZ8=`tJ@4Rcl=S^hhUOq9cLW+`gLRKR}N@9r=! zmKDckuS0X?)1bXW;`inVCAmUwR7Y&eFmvNzo*0w|APSZHT9m%9>w~z&>!?Ej!4~)J zv=txUQ!aFY5nUc47VEj0e99EY=#5c%V4D!A+GT8CabR&3(0L>aW3p#?|=QwFl)TUVUJ7{c~p;<1a4MWF>s$85xx`o}XAljdcH?Y1+Dqf>Tv5&e_X_t7Af~5EkuQd9d zsh#X>X&C`nZ8}hibw-gwDlk_ZHEi+m%^qW?s=dnckCf$qbDmd8)oWTR_GP!yw?OGN zBbPI%lL-%5%!Ifhqp(}v4HDmIm{~-L1r!-HK?(E1J_w?6V_mqak#QgNi*9H@JM_zn z-zv-|P#cU<<{8*#iA$PMpv?5JvF-!dfbKb%jx{G^bpPl^;&^4$q!_y>wyk z@6VyDXDWF6Lej}HM?U~lGYd@7^aTXV0 zu+C&Bi!pqpfT@OiD>bj3l3sT*#co{L{XyI5=(JN79!e7m7na(jPK&n`-|b5zu2tMEkn9R#I~VKfKPqv5dQD^chAcWm=Wk|j B^OXPq diff --git a/node/build.gradle b/node/build.gradle index eff17b13c8..2a0aabcd4d 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -98,7 +98,7 @@ dependencies { compile "org.fusesource.jansi:jansi:$jansi_version" // Manifests: for reading stuff from the manifest file - compile "com.jcabi:jcabi-manifests:1.1" + compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version" compile("com.intellij:forms_rt:7.0.3") { exclude group: "asm" diff --git a/node/src/main/kotlin/net/corda/node/serialization/kryo/CordaClassResolver.kt b/node/src/main/kotlin/net/corda/node/serialization/kryo/CordaClassResolver.kt index a826aac113..58e8b61c06 100644 --- a/node/src/main/kotlin/net/corda/node/serialization/kryo/CordaClassResolver.kt +++ b/node/src/main/kotlin/net/corda/node/serialization/kryo/CordaClassResolver.kt @@ -8,13 +8,12 @@ import com.esotericsoftware.kryo.util.DefaultClassResolver import com.esotericsoftware.kryo.util.Util import net.corda.core.internal.writer import net.corda.core.serialization.ClassWhitelist -import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializationContext import net.corda.core.utilities.contextLogger import net.corda.serialization.internal.AttachmentsClassLoader import net.corda.serialization.internal.MutableClassWhitelist import net.corda.serialization.internal.TransientClassWhiteList -import net.corda.serialization.internal.amqp.hasAnnotationInHierarchy +import net.corda.serialization.internal.amqp.hasCordaSerializable import java.io.PrintWriter import java.lang.reflect.Modifier import java.lang.reflect.Modifier.isAbstract @@ -127,7 +126,7 @@ class CordaClassResolver(serializationContext: SerializationContext) : DefaultCl return (type.classLoader !is AttachmentsClassLoader) && !KryoSerializable::class.java.isAssignableFrom(type) && !type.isAnnotationPresent(DefaultSerializer::class.java) - && (type.isAnnotationPresent(CordaSerializable::class.java) || whitelist.hasAnnotationInHierarchy(type)) + && hasCordaSerializable(type) } // Need to clear out class names from attachments. diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt index 9a929e9df3..47b5387413 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt @@ -525,14 +525,17 @@ fun ClassWhitelist.requireWhitelisted(type: Type) { } } -fun ClassWhitelist.isWhitelisted(clazz: Class<*>) = (hasListed(clazz) || hasAnnotationInHierarchy(clazz)) -fun ClassWhitelist.isNotWhitelisted(clazz: Class<*>) = !(this.isWhitelisted(clazz)) +fun ClassWhitelist.isWhitelisted(clazz: Class<*>) = hasListed(clazz) || hasCordaSerializable(clazz) +fun ClassWhitelist.isNotWhitelisted(clazz: Class<*>) = !this.isWhitelisted(clazz) -// Recursively check the class, interfaces and superclasses for our annotation. -fun ClassWhitelist.hasAnnotationInHierarchy(type: Class<*>): Boolean { +/** + * Check the given [Class] has the [CordaSerializable] annotation, either directly or inherited from any of its super + * classes or interfaces. + */ +fun hasCordaSerializable(type: Class<*>): Boolean { return type.isAnnotationPresent(CordaSerializable::class.java) - || type.interfaces.any { hasAnnotationInHierarchy(it) } - || (type.superclass != null && hasAnnotationInHierarchy(type.superclass)) + || type.interfaces.any(::hasCordaSerializable) + || (type.superclass != null && hasCordaSerializable(type.superclass)) } /** @@ -555,27 +558,28 @@ fun ClassWhitelist.hasAnnotationInHierarchy(type: Class<*>): Boolean { * * As such, if objectInstance fails access, revert to Java reflection and try that */ -fun Class<*>.objectInstance() = - try { - this.kotlin.objectInstance - } catch (e: IllegalAccessException) { - // Check it really is an object (i.e. it has no constructor) - if (constructors.isNotEmpty()) null - else { - try { - this.getDeclaredField("INSTANCE")?.let { field -> - // and must be marked as both static and final (>0 means they're set) - if (modifiers and Modifier.STATIC == 0 || modifiers and Modifier.FINAL == 0) null - else { - val accessibility = field.isAccessible - field.isAccessible = true - val obj = field.get(null) - field.isAccessible = accessibility - obj - } +fun Class<*>.objectInstance(): Any? { + return try { + this.kotlin.objectInstance + } catch (e: IllegalAccessException) { + // Check it really is an object (i.e. it has no constructor) + if (constructors.isNotEmpty()) null + else { + try { + this.getDeclaredField("INSTANCE")?.let { field -> + // and must be marked as both static and final (>0 means they're set) + if (modifiers and Modifier.STATIC == 0 || modifiers and Modifier.FINAL == 0) null + else { + val accessibility = field.isAccessible + field.isAccessible = true + val obj = field.get(null) + field.isAccessible = accessibility + obj } - } catch (e: NoSuchFieldException) { - null } + } catch (e: NoSuchFieldException) { + null } } + } +} diff --git a/settings.gradle b/settings.gradle index 313696cd0b..a597018e24 100644 --- a/settings.gradle +++ b/settings.gradle @@ -36,6 +36,7 @@ include 'tools:demobench' include 'tools:loadtest' include 'tools:graphs' include 'tools:bootstrapper' +include 'tools:blobinspector' include 'tools:shell' include 'example-code' project(':example-code').projectDir = file("$settingsDir/docs/source/example-code") diff --git a/tools/blobinspector/build.gradle b/tools/blobinspector/build.gradle new file mode 100644 index 0000000000..5d49461034 --- /dev/null +++ b/tools/blobinspector/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'java' +apply plugin: 'kotlin' + +dependencies { + compile project(':client:jackson') + compile 'info.picocli:picocli:3.0.0' + compile "org.slf4j:slf4j-nop:$slf4j_version" + compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version" + + testCompile project(':test-utils') + testCompile "junit:junit:$junit_version" +} + +jar { + from(configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }) { + exclude "META-INF/*.SF" + exclude "META-INF/*.DSA" + exclude "META-INF/*.RSA" + } + baseName 'blobinspector' + manifest { + attributes( + 'Automatic-Module-Name': 'net.corda.blobinspector', + 'Main-Class': 'net.corda.blobinspector.MainKt' + ) + } +} diff --git a/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt b/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt new file mode 100644 index 0000000000..e2bde0639e --- /dev/null +++ b/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt @@ -0,0 +1,125 @@ +package net.corda.blobinspector + +import com.fasterxml.jackson.core.JsonFactory +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory +import com.jcabi.manifests.Manifests +import net.corda.client.jackson.JacksonSupport +import net.corda.core.internal.rootMessage +import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.internal.SerializationEnvironmentImpl +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.utilities.sequence +import net.corda.serialization.internal.AMQP_P2P_CONTEXT +import net.corda.serialization.internal.CordaSerializationMagic +import net.corda.serialization.internal.SerializationFactoryImpl +import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.amqpMagic +import picocli.CommandLine +import picocli.CommandLine.* +import java.net.MalformedURLException +import java.net.URL +import java.nio.file.Paths +import kotlin.system.exitProcess + +fun main(args: Array) { + val main = Main() + try { + CommandLine.run(main, *args) + } catch (e: ExecutionException) { + val throwable = e.cause ?: e + if (main.verbose) { + throwable.printStackTrace() + } else { + System.err.println("*ERROR*: ${throwable.rootMessage ?: "Use --verbose for more details"}") + } + exitProcess(1) + } +} + +@Command( + name = "Blob Inspector", + versionProvider = VersionProvider::class, + mixinStandardHelpOptions = true, // add --help and --version options, + showDefaultValues = true, + description = ["Inspect AMQP serialised binary blobs"] +) +class Main : Runnable { + @Parameters(index = "0", paramLabel = "SOURCE", description = ["URL or file path to the blob"], converter = [SourceConverter::class]) + private var source: URL? = null + + @Option(names = ["--format"], paramLabel = "type", description = ["Output format. Possible values: [YAML, JSON]"]) + private var formatType: FormatType = FormatType.YAML + + @Option(names = ["--full-parties"], + description = ["Display the owningKey and certPath properties of Party and PartyAndReference objects respectively"]) + private var fullParties: Boolean = false + + @Option(names = ["--schema"], description = ["Print the blob's schema first"]) + private var schema: Boolean = false + + @Option(names = ["--verbose"], description = ["Enable verbose output"]) + var verbose: Boolean = false + + override fun run() { + val bytes = source!!.readBytes().run { + require(size > amqpMagic.size) { "Insufficient bytes for AMQP blob" } + sequence() + } + + require(bytes.take(amqpMagic.size) == amqpMagic) { "Not an AMQP blob" } + + if (schema) { + val envelope = DeserializationInput.getEnvelope(bytes) + println(envelope.schema) + println() + } + + initialiseSerialization() + + val factory = when (formatType) { + FormatType.YAML -> YAMLFactory() + FormatType.JSON -> JsonFactory() + } + val mapper = JacksonSupport.createNonRpcMapper(factory, fullParties) + + val deserialized = bytes.deserialize() + println(deserialized.javaClass.name) + mapper.writeValue(System.out, deserialized) + } + + private fun initialiseSerialization() { + _contextSerializationEnv.set(SerializationEnvironmentImpl( + SerializationFactoryImpl().apply { + registerScheme(AMQPInspectorSerializationScheme) + }, + AMQP_P2P_CONTEXT + )) + } +} + +private object AMQPInspectorSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) { + override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean { + return magic == amqpMagic && target == SerializationContext.UseCase.P2P + } + override fun rpcClientSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException() + override fun rpcServerSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException() +} + +private class SourceConverter : ITypeConverter { + override fun convert(value: String): URL { + return try { + URL(value) + } catch (e: MalformedURLException) { + Paths.get(value).toUri().toURL() + } + } +} + +private class VersionProvider : IVersionProvider { + override fun getVersion(): Array = arrayOf(Manifests.read("Corda-Release-Version")) +} + +private enum class FormatType { YAML, JSON } + From ee0d580448844efe14aa36e80e6a1ab6481c65f5 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 24 May 2018 18:30:45 +0100 Subject: [PATCH 10/13] CORDA-1530 - Generics break default evolver (#3232) * CORDA-1530 - Generics break default evolver When selecting an annotated constructor for evolving a type make sure we treat generics in the same manner we did when serialized. Effectively throw away the template information and treat lists as lists and maps as maps --- .../internal/amqp/EvolutionSerializer.kt | 34 ++++++- .../internal/amqp/EvolvabilityTests.kt | 90 ++++++++++++++++-- ...ndatoryFieldWithAltConstructorForceReorder | Bin 0 -> 332 bytes ...abilityTests.moreComplexNonNullWithReorder | Bin 0 -> 1233 bytes 4 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 serialization/src/test/resources/net/corda/serialization/internal/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructorForceReorder create mode 100644 serialization/src/test/resources/net/corda/serialization/internal/amqp/EvolvabilityTests.moreComplexNonNullWithReorder diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializer.kt index 14913b8882..62914e1518 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializer.kt @@ -3,6 +3,9 @@ package net.corda.serialization.internal.amqp import net.corda.core.internal.isConcreteClass import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.SerializationContext +import net.corda.core.utilities.contextLogger +import net.corda.core.utilities.debug +import net.corda.core.utilities.loggerFor import net.corda.serialization.internal.carpenter.getTypeAsClass import org.apache.qpid.proton.codec.Data import java.io.NotSerializableException @@ -48,9 +51,15 @@ abstract class EvolutionSerializer( new[resultsIndex] = this } } + + override fun toString(): String { + return "resultsIndex = $resultsIndex property = ${property.name}" + } } companion object { + val logger = contextLogger() + /** * Unlike the generic deserialization case where we need to locate the primary constructor * for the object (or our best guess) in the case of an object whose structure has changed @@ -66,22 +75,37 @@ abstract class EvolutionSerializer( if (!clazz.isConcreteClass) return null - val oldArgumentSet = oldArgs.map { Pair(it.key as String?, it.value.property.resolvedType) } - + val oldArgumentSet = oldArgs.map { Pair(it.key as String?, it.value.property.resolvedType.asClass()) } var maxConstructorVersion = Integer.MIN_VALUE var constructor: KFunction? = null + clazz.kotlin.constructors.forEach { val version = it.findAnnotation()?.version ?: Integer.MIN_VALUE - if (oldArgumentSet.containsAll(it.parameters.map { v -> Pair(v.name, v.type.javaType) }) && - version > maxConstructorVersion) { + + if (version > maxConstructorVersion && + oldArgumentSet.containsAll(it.parameters.map { v -> Pair(v.name, v.type.javaType.asClass()) }) + ) { constructor = it maxConstructorVersion = version + + with(logger) { + info("Select annotated constructor version=$version nparams=${it.parameters.size}") + debug{" params=${it.parameters}"} + } + } else if (version != Integer.MIN_VALUE){ + with(logger) { + info("Ignore annotated constructor version=$version nparams=${it.parameters.size}") + debug{" params=${it.parameters}"} + } } } // if we didn't get an exact match revert to existing behaviour, if the new parameters // are not mandatory (i.e. nullable) things are fine - return constructor ?: constructorForDeserialization(type) + return constructor ?: run { + logger.info("Failed to find annotated historic constructor") + constructorForDeserialization(type) + } } private fun makeWithConstructor( diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EvolvabilityTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EvolvabilityTests.kt index 4476987c29..5e4b7e343f 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EvolvabilityTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EvolvabilityTests.kt @@ -8,10 +8,7 @@ import net.corda.core.node.NotaryInfo import net.corda.core.serialization.ConstructorForDeserialization import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.SerializedBytes -import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput -import net.corda.serialization.internal.amqp.testutils.deserialize -import net.corda.serialization.internal.amqp.testutils.serialize -import net.corda.serialization.internal.amqp.testutils.testDefaultFactory +import net.corda.serialization.internal.amqp.testutils.* import net.corda.testing.common.internal.ProjectStructure.projectRootDir import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.TestIdentity @@ -22,6 +19,7 @@ import java.io.NotSerializableException import java.net.URI import java.time.Instant import kotlin.test.assertEquals +import net.corda.serialization.internal.amqp.custom.InstantSerializer // To regenerate any of the binary test files do the following // @@ -202,6 +200,86 @@ class EvolvabilityTests { assertEquals("hello", deserializedCC.b) } + @Test + fun addMandatoryFieldWithAltConstructorForceReorder() { + val sf = testDefaultFactory() + val z = 30 + val y = 20 + val resource = "EvolvabilityTests.addMandatoryFieldWithAltConstructorForceReorder" + + // Original version of the class as it was serialised + // data class CC(val z: Int, val y: Int) + // File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(z, y)).bytes) + + @Suppress("UNUSED") + data class CC(val z: Int, val y: Int, val a: String) { + @DeprecatedConstructorForDeserialization(1) + constructor (z: Int, y: Int) : this(z, y, "10") + } + + val url = EvolvabilityTests::class.java.getResource(resource) + val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes(url.readBytes())) + + assertEquals("10", deserializedCC.a) + assertEquals(y, deserializedCC.y) + assertEquals(z, deserializedCC.z) + } + + @Test + fun moreComplexNonNullWithReorder() { + val resource = "${javaClass.simpleName}.${testName()}" + + data class NetworkParametersExample( + val minimumPlatformVersion: Int, + val notaries: List, + val maxMessageSize: Int, + val maxTransactionSize: Int, + val modifiedTime: Instant, + val epoch: Int, + val whitelistedContractImplementations: Map>, + /* to regenerate test class, comment out this element */ + val eventHorizon: Int + ) { + // when regenerating test class this won't be required + @DeprecatedConstructorForDeserialization(1) + @Suppress("UNUSED") + constructor ( + minimumPlatformVersion: Int, + notaries: List, + maxMessageSize: Int, + maxTransactionSize: Int, + modifiedTime: Instant, + epoch: Int, + whitelistedContractImplementations: Map> + ) : this(minimumPlatformVersion, + notaries, + maxMessageSize, + maxTransactionSize, + modifiedTime, + epoch, + whitelistedContractImplementations, + Int.MAX_VALUE) + } + + val factory = testDefaultFactory().apply { + register(InstantSerializer(this)) + } + + // Uncomment to regenerate test case + // File(URI("$localPath/$resource")).writeBytes(SerializationOutput(factory).serialize( + // NetworkParametersExample( + // 10, + // listOf("Notary1", "Notary2"), + // 100, + // 10, + // Instant.now(), + // 9, + // mapOf("A" to listOf(1, 2, 3), "B" to listOf (4, 5, 6)))).bytes) + + val url = EvolvabilityTests::class.java.getResource(resource) + DeserializationInput(factory).deserialize(SerializedBytes(url.readBytes())) + } + @Test(expected = NotSerializableException::class) @Suppress("UNUSED") fun addMandatoryFieldWithAltConstructorUnAnnotated() { @@ -479,7 +557,7 @@ class EvolvabilityTests { // @Test @Ignore("Test fails after moving NetworkParameters and NotaryInfo into core from node-api") - fun readBrokenNetworkParameters(){ + fun readBrokenNetworkParameters() { val sf = testDefaultFactory() sf.register(net.corda.serialization.internal.amqp.custom.InstantSerializer(sf)) sf.register(net.corda.serialization.internal.amqp.custom.PublicKeySerializer) @@ -515,7 +593,7 @@ class EvolvabilityTests { val resource = "networkParams.." val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party val networkParameters = NetworkParameters( - 3, listOf(NotaryInfo(DUMMY_NOTARY, false)),1000, 1000, Instant.EPOCH, 1, emptyMap()) + 3, listOf(NotaryInfo(DUMMY_NOTARY, false)), 1000, 1000, Instant.EPOCH, 1, emptyMap()) val sf = testDefaultFactory() sf.register(net.corda.serialization.internal.amqp.custom.InstantSerializer(sf)) diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructorForceReorder b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructorForceReorder new file mode 100644 index 0000000000000000000000000000000000000000..8aa1e1f0142fa8d452c61706067d3b8fdb557533 GIT binary patch literal 332 zcmYe!FG@*dWME)uIGO|`85kHZ0I?~M!OXB&DKE7|FBzo5D$~L}JHp>6KQTPLJjB#9 zKRht2!l>NV_5dqWh)9SWTszZ&H;e~fGQ#<+2kx*g%z~SvSDadunV6GVl~|IQpQo3Z zSCU$kmzblMm|Iw&=USGZQ`LN zm{a1MpI2N`RGJJFcgrtIP7O*0Ix@9L#o5`x6>c^20W~IKyx?%ak_oPf<$wVDLdMF4 Y%s{8PE@U)taCAbGtHLVBd61C-08kBQ`Tzg` literal 0 HcmV?d00001 diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EvolvabilityTests.moreComplexNonNullWithReorder b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EvolvabilityTests.moreComplexNonNullWithReorder new file mode 100644 index 0000000000000000000000000000000000000000..3a01cc6b08ceccefa50d1c07cf5610c3d4deaf5b GIT binary patch literal 1233 zcmb7D&rcIU6yEK&yF!dcFeJuklprQl79?tj5zz^{vfnzP+>=?MLmE+S*$D(*hH~1CwA7OlwHhnEulVlU9x6tPemWzq3KNhYKmBd2j77fL zsr$=e&3pcVt?v6p`mE|xtc0DpS*aJtpf4(Q!Mrj9-CfV^`CEQe=Br5!mH~8-0fy8! zE*+2sIbaUDmQh_ZqVx6{R2kWUFQrK0ztK zhnAdDK)@jlp_^3x5AY*P&Jav7>5ysw#UbNlU)839QrI6`@;YH2KE%iaYW3u;Keo^* zj4NNX1Y@q(+v)j2q^a i#5vL+u$7uJ?H0#U)=WA5m&yzOt0(PxN6076bnO??t%`#H literal 0 HcmV?d00001 From a3d88f752d964d3768e153be189f196c600c8d7d Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Fri, 25 May 2018 11:37:20 +0100 Subject: [PATCH 11/13] CORDA-1510 - Allow Doorman and NetworkMap to be configured independently (#3220) * CORDA-1510 - Allow Doorman and NetworkMap to be configured independently Currently only one compatabilityZoneURL can be specified, however the two services can be run on as separate servers. Allow nodes to be configured in this manner * Partial review comments * Review comments * review comments --- docs/source/changelog.rst | 6 +- docs/source/corda-configuration-file.rst | 19 +++++- .../example-node-with-networkservices.conf | 25 ++++++++ docs/source/permissioning.rst | 24 ++++---- .../node/services/network/NetworkMapTest.kt | 52 +++++++++++------ .../registration/NodeRegistrationTest.kt | 3 +- .../kotlin/net/corda/node/NodeArgsParser.kt | 8 +-- .../net/corda/node/internal/AbstractNode.kt | 2 +- .../net/corda/node/internal/NodeStartup.kt | 4 +- .../node/services/config/NodeConfiguration.kt | 47 ++++++++++++++- .../config/NodeConfigurationImplTest.kt | 45 +++++++++++++- .../testing/node/internal/DriverDSLImpl.kt | 58 ++++++++++++++++--- .../node/internal/InternalMockNetwork.kt | 1 + 13 files changed, 245 insertions(+), 49 deletions(-) create mode 100644 docs/source/example-code/src/main/resources/example-node-with-networkservices.conf diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 4d2931eb76..d352f1c7bb 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -7,6 +7,10 @@ release, see :doc:`upgrade-notes`. Unreleased ========== +* Doorman and NetworkMap url's can now be configured individually rather than being assumed to be + the same server. Current ``compatibilityZoneURL`` configurations remain valid. See both :doc:`corda-configuration-file` + and :doc:`permissioning` for details. + * Improved audit trail for ``FinalityFlow`` and related sub-flows. * ``NodeStartup`` will now only print node's configuration if ``devMode`` is ``true``, avoiding the risk of printing passwords in a production setup. @@ -17,7 +21,7 @@ Unreleased public and was already internal for Kotlin code. * RPC Framework moved from Kryo to the Corda AMQP implementation [Corda-847]. This completes the removal - of ``Kryo`` from general use within Corda, remaining only for use in flow checkpointing. + of ``Kryo`` from general use within Corda, remaining only for use in flow checkpointing. * Set co.paralleluniverse.fibers.verifyInstrumentation=true in devMode. diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index 8307e2995d..5e073f2970 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -171,7 +171,16 @@ absolute path to the node's base directory. interfaces, and then by sending an IP discovery request to the network map service. Set to ``false`` to disable. :compatibilityZoneURL: The root address of Corda compatibility zone network management services, it is used by the Corda node to register with the network and - obtain Corda node certificate, (See :doc:`permissioning` for more information.) and also used by the node to obtain network map information. + obtain Corda node certificate, (See :doc:`permissioning` for more information.) and also used by the node to obtain network map information. Cannot be + set at the same time as the ``networkServices`` option. + +:networkServices: If the Corda compatibility zone services, both network map and registration (doorman), are not running on the same endpoint + and thus have different URLs then this option should be used in place of the ``compatibilityZoneURL`` setting. + + :doormanURL: Root address of the network registration service. + :networkMapURL: Root address of the network map service. + +.. note:: Only one of ``compatibilityZoneURL`` or ``networkServices`` should be used. :jvmArgs: An optional list of JVM args, as strings, which replace those inherited from the command line when launching via ``corda.jar`` only. e.g. ``jvmArgs = [ "-Xmx220m", "-Xms220m", "-XX:+UseG1GC" ]`` @@ -236,7 +245,7 @@ Simple notary configuration file: notary : { validating : false } - devMode : true + devMode : false compatibilityZoneURL : "https://cz.corda.net" An example ``web-server.conf`` file is as follow: @@ -255,6 +264,10 @@ An example ``web-server.conf`` file is as follow: webAddress : "localhost:12347", rpcUsers : [{ username=user1, password=letmein, permissions=[ StartFlow.net.corda.protocols.CashProtocol ] }] +Configuring a node where the Corda Comatability Zone's registration and Network Map services exist on different URLs + +.. literalinclude:: example-code/src/main/resources/example-node-with-networkservices.conf + Fields ------ @@ -311,4 +324,4 @@ Example adding/overriding keyStore password when starting Corda node: .. sourcecode:: shell - java -Dcorda.rpcSettings.ssl.keyStorePassword=mypassword -jar node.jar \ No newline at end of file + java -Dcorda.rpcSettings.ssl.keyStorePassword=mypassword -jar node.jar diff --git a/docs/source/example-code/src/main/resources/example-node-with-networkservices.conf b/docs/source/example-code/src/main/resources/example-node-with-networkservices.conf new file mode 100644 index 0000000000..61ddf736d1 --- /dev/null +++ b/docs/source/example-code/src/main/resources/example-node-with-networkservices.conf @@ -0,0 +1,25 @@ +myLegalName : "O=Bank A,L=London,C=GB" +keyStorePassword : "cordacadevpass" +trustStorePassword : "trustpass" +crlCheckSoftFail: true +dataSourceProperties : { + dataSourceClassName : org.h2.jdbcx.JdbcDataSource + dataSource.url : "jdbc:h2:file:"${baseDirectory}"/persistence" + dataSource.user : sa + dataSource.password : "" +} +p2pAddress : "my-corda-node:10002" +rpcSettings = { + useSsl = false + standAloneBroker = false + address : "my-corda-node:10003" + adminAddress : "my-corda-node:10004" +} +rpcUsers : [ + { username=user1, password=letmein, permissions=[ StartFlow.net.corda.protocols.CashProtocol ] } +] +devMode : false +networkServices : { + doormanURL = "https://registration.corda.net" + networkMapURL = "https://cz.corda.net" +} diff --git a/docs/source/permissioning.rst b/docs/source/permissioning.rst index 3e0e8f4965..a77a141f34 100644 --- a/docs/source/permissioning.rst +++ b/docs/source/permissioning.rst @@ -196,21 +196,25 @@ This can be overridden with the additional ``--network-root-truststore`` flag. 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. -: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 :ref:`node_naming` for more information. +* **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 :ref:`node_naming` for more information. -:emailAddress: e.g. "admin@company.com" +* **emailAddress** e.g. "admin@company.com" -:devMode: must be set to false +* **devMode** must be set to false -:compatibilityZoneURL: Corda compatibility zone network management service root URL. +* **networkServices or compatibilityZoneURL** The Corda compatibility zone services must be configured. This must be either: - A new pair of private and public keys generated by the Corda node will be used to create the request. + * **compatibilityZoneURL** The Corda compatibility zone network management service root URL. + * **networkServices** Replaces the ``compatibilityZoneURL`` when the Doorman and Network Map services + are configured to operate on different URL endpoints. The ``doorman`` entry is used for registration. - 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. +A new pair of private and public keys generated by the Corda node will be used to create the request. + +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. .. 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. diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt index 683fc19fce..d4d6f00519 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt @@ -4,12 +4,8 @@ import net.corda.cordform.CordformNode import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.bufferUntilSubscribed +import net.corda.core.internal.* import net.corda.core.internal.concurrent.transpose -import net.corda.core.internal.div -import net.corda.core.internal.exists -import net.corda.core.internal.list -import net.corda.core.internal.readObject import net.corda.core.messaging.ParametersUpdateInfo import net.corda.core.node.NodeInfo import net.corda.core.serialization.serialize @@ -21,18 +17,11 @@ import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME import net.corda.nodeapi.internal.network.SignedNetworkParameters import net.corda.testing.common.internal.testNetworkParameters -import net.corda.testing.core.ALICE_NAME -import net.corda.testing.core.BOB_NAME -import net.corda.testing.core.SerializationEnvironmentRule -import net.corda.testing.core.expect -import net.corda.testing.core.expectEvents -import net.corda.testing.core.sequence +import net.corda.testing.core.* import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.internal.NodeHandleInternal import net.corda.testing.driver.internal.RandomFree -import net.corda.testing.node.internal.CompatibilityZoneParams -import net.corda.testing.node.internal.DriverDSLImpl -import net.corda.testing.node.internal.internalDriver +import net.corda.testing.node.internal.* import net.corda.testing.node.internal.network.NetworkMapServer import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy @@ -41,10 +30,13 @@ import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized import java.net.URL import java.time.Instant -class NetworkMapTest { +@RunWith(Parameterized::class) +class NetworkMapTest(var initFunc: (URL, NetworkMapServer) -> CompatibilityZoneParams) { @Rule @JvmField val testSerialization = SerializationEnvironmentRule(true) @@ -55,13 +47,37 @@ class NetworkMapTest { private lateinit var networkMapServer: NetworkMapServer private lateinit var compatibilityZone: CompatibilityZoneParams + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun runParams() = listOf( + { addr: URL, nms: NetworkMapServer -> + SharedCompatibilityZoneParams( + addr, + publishNotaries = { + nms.networkParameters = testNetworkParameters(it, modifiedTime = Instant.ofEpochMilli(random63BitValue()), epoch = 2) + } + ) + }, + { addr: URL, nms: NetworkMapServer -> + SplitCompatibilityZoneParams( + doormanURL = URL("http://I/Don't/Exist"), + networkMapURL = addr, + publishNotaries = { + nms.networkParameters = testNetworkParameters(it, modifiedTime = Instant.ofEpochMilli(random63BitValue()), epoch = 2) + } + ) + } + + ) + } + + @Before fun start() { networkMapServer = NetworkMapServer(cacheTimeout, portAllocation.nextHostAndPort()) val address = networkMapServer.start() - compatibilityZone = CompatibilityZoneParams(URL("http://$address"), publishNotaries = { - networkMapServer.networkParameters = testNetworkParameters(it, modifiedTime = Instant.ofEpochMilli(random63BitValue()), epoch = 2) - }) + compatibilityZone = initFunc(URL("http://$address"), networkMapServer) } @After diff --git a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt index ed2bbf50cf..1e751a1658 100644 --- a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt @@ -21,6 +21,7 @@ import net.corda.testing.driver.PortAllocation import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.node.NotarySpec import net.corda.testing.node.internal.CompatibilityZoneParams +import net.corda.testing.node.internal.SharedCompatibilityZoneParams import net.corda.testing.node.internal.internalDriver import net.corda.testing.node.internal.network.NetworkMapServer import org.assertj.core.api.Assertions.assertThat @@ -79,7 +80,7 @@ class NodeRegistrationTest { @Test fun `node registration correct root cert`() { - val compatibilityZone = CompatibilityZoneParams( + val compatibilityZone = SharedCompatibilityZoneParams( URL("http://$serverHostAndPort"), publishNotaries = { server.networkParameters = testNetworkParameters(it) }, rootCert = DEV_ROOT_CA.certificate) diff --git a/node/src/main/kotlin/net/corda/node/NodeArgsParser.kt b/node/src/main/kotlin/net/corda/node/NodeArgsParser.kt index 7dd26c4a0a..2568774e57 100644 --- a/node/src/main/kotlin/net/corda/node/NodeArgsParser.kt +++ b/node/src/main/kotlin/net/corda/node/NodeArgsParser.kt @@ -120,11 +120,11 @@ data class CmdLineOptions(val baseDirectory: Path, if (devMode) mapOf("devMode" to this.devMode) else emptyMap()) ) return rawConfig to Try.on { - rawConfig.parseAsNodeConfiguration(unknownConfigKeysPolicy::handle).also { + rawConfig.parseAsNodeConfiguration(unknownConfigKeysPolicy::handle).also { config -> if (nodeRegistrationOption != null) { - require(!it.devMode) { "registration cannot occur in devMode" } - requireNotNull(it.compatibilityZoneURL) { - "compatibilityZoneURL must be present in node configuration file in registration mode." + require(!config.devMode) { "registration cannot occur in devMode" } + require(config.compatibilityZoneURL != null || config.networkServices != null) { + "compatibilityZoneURL or networkServices must be present in the node configuration file in registration mode." } } } 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 f0fbb99bdf..d3a356cbd8 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -281,7 +281,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null) val identityService = makeIdentityService(identity.certificate) - networkMapClient = configuration.compatibilityZoneURL?.let { NetworkMapClient(it, identityService.trustRoot) } + networkMapClient = configuration.networkServices?.let { NetworkMapClient(it.networkMapURL, identityService.trustRoot) } val networkParameters = NetworkParametersReader(identityService.trustRoot, networkMapClient, configuration.baseDirectory).networkParameters check(networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) { 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 c0c78439d8..797647bee4 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -208,7 +208,9 @@ open class NodeStartup(val args: Array) { } protected open fun registerWithNetwork(conf: NodeConfiguration, nodeRegistrationConfig: NodeRegistrationOption) { - val compatibilityZoneURL = conf.compatibilityZoneURL!! + val compatibilityZoneURL = conf.networkServices?.doormanURL ?: throw RuntimeException( + "compatibilityZoneURL or networkServices must be configured!") + println() println("******************************************************************") println("* *") 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 465c0b7e7d..b2cdafa210 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 @@ -36,6 +36,7 @@ interface NodeConfiguration : NodeSSLConfiguration { val devMode: Boolean val devModeOptions: DevModeOptions? val compatibilityZoneURL: URL? + val networkServices: NetworkServicesConfig? val certificateChainCheckPolicies: List val verifierType: VerifierType val p2pMessagingRetry: P2PMessagingRetryConfiguration @@ -116,6 +117,25 @@ data class BFTSMaRtConfiguration( } } +/** + * Used as an alternative to the older compatibilityZoneURL to allow the doorman and network map + * services for a node to be configured as different URLs. Cannot be set at the same time as the + * compatibilityZoneURL, and will be defaulted (if not set) to both point at the configured + * compatibilityZoneURL. + * + * @property doormanURL The URL of the tls certificate signing service. + * @property networkMapURL The URL of the Network Map service. + * @property inferred Non user setting that indicates weather the Network Services configuration was + * set explicitly ([inferred] == false) or weather they have been inferred via the compatibilityZoneURL parameter + * ([inferred] == true) where both the network map and doorman are running on the same endpoint. Only one, + * compatibilityZoneURL or networkServices, can be set at any one time. + */ +data class NetworkServicesConfig( + val doormanURL: URL, + val networkMapURL: URL, + val inferred : Boolean = false +) + /** * Currently only used for notarisation requests. * @@ -141,6 +161,7 @@ data class NodeConfigurationImpl( override val crlCheckSoftFail: Boolean, override val dataSourceProperties: Properties, override val compatibilityZoneURL: URL? = null, + override var networkServices: NetworkServicesConfig? = null, override val tlsCertCrlDistPoint: URL? = null, override val tlsCertCrlIssuer: String? = null, override val rpcUsers: List, @@ -185,6 +206,7 @@ data class NodeConfigurationImpl( explicitAddress != null -> { require(settings.address == null) { "Can't provide top-level rpcAddress and rpcSettings.address (they control the same property)." } logger.warn("Top-level declaration of property 'rpcAddress' is deprecated. Please use 'rpcSettings.address' instead.") + settings.copy(address = explicitAddress) } else -> { @@ -217,6 +239,7 @@ data class NodeConfigurationImpl( errors += validateDevModeOptions() errors += validateRpcOptions(rpcOptions) errors += validateTlsCertCrlConfig() + errors += validateNetworkServices() return errors } @@ -231,12 +254,28 @@ data class NodeConfigurationImpl( } private fun validateDevModeOptions(): List { - val errors = mutableListOf() if (devMode) { compatibilityZoneURL?.let { - errors += "'compatibilityZoneURL': present. Property cannot be set when 'devMode' is true." + return listOf("'compatibilityZoneURL': present. Property cannot be set when 'devMode' is true.") + } + + // if compatibiliZoneURL is set then it will be copied into the networkServices field and thus skipping + // this check by returning above is fine. + networkServices?.let { + return listOf("'networkServices': present. Property cannot be set when 'devMode' is true.") } } + + return emptyList() + } + + private fun validateNetworkServices(): List { + val errors = mutableListOf() + + if (compatibilityZoneURL != null && networkServices != null && !(networkServices!!.inferred)) { + errors += "Cannot configure both compatibilityZoneUrl and networkServices simultaneously" + } + return errors } @@ -258,6 +297,10 @@ data class NodeConfigurationImpl( |Please contact the R3 team on the public slack to discuss your use case. """.trimMargin()) } + + if (compatibilityZoneURL != null && networkServices == null) { + networkServices = NetworkServicesConfig(compatibilityZoneURL, compatibilityZoneURL, true) + } } } 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 6bf6553cbb..bf99cb3a4a 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,8 @@ import net.corda.tools.shell.SSHDConfiguration import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatCode import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertEquals import org.junit.Test import java.net.URI import java.net.URL @@ -127,7 +129,9 @@ class NodeConfigurationImplTest { @Test fun `validation has error when compatibilityZoneURL is present and devMode is true`() { - val configuration = testConfiguration.copy(devMode = true, compatibilityZoneURL = URI.create("https://r3.com").toURL()) + val configuration = testConfiguration.copy( + devMode = true, + compatibilityZoneURL = URL("https://r3.com")) val errors = configuration.validate() @@ -146,6 +150,33 @@ class NodeConfigurationImplTest { } } + @Test + fun `validation has error when compatibilityZone is present and devMode is true`() { + val configuration = testConfiguration.copy(devMode = true, networkServices = NetworkServicesConfig( + URL("https://r3.com.doorman"), + URL("https://r3.com/nm"))) + + val errors = configuration.validate() + + assertThat(errors).hasOnlyOneElementSatisfying { error -> error.contains("networkServices") && error.contains("devMode") } + } + + @Test + fun `validation has error when both compatibilityZoneURL and networkServices are configured`() { + val configuration = testConfiguration.copy( + devMode = false, + compatibilityZoneURL = URL("https://r3.com"), + networkServices = NetworkServicesConfig( + URL("https://r3.com.doorman"), + URL("https://r3.com/nm"))) + + val errors = configuration.validate() + + assertThat(errors).hasOnlyOneElementSatisfying { + error -> error.contains("Cannot configure both compatibilityZoneUrl and networkServices simultaneously") + } + } + @Test fun `rpcAddress and rpcSettings_address are equivalent`() { var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false)) @@ -155,6 +186,18 @@ class NodeConfigurationImplTest { assertThatCode { rawConfig.parseAsNodeConfiguration() }.doesNotThrowAnyException() } + @Test + fun `compatiilityZoneURL populates NetworkServices`() { + val compatibilityZoneURL = URI.create("https://r3.com").toURL() + val configuration = testConfiguration.copy( + devMode = false, + compatibilityZoneURL = compatibilityZoneURL) + + assertNotNull(configuration.networkServices) + assertEquals(compatibilityZoneURL, configuration.networkServices!!.doormanURL) + assertEquals(compatibilityZoneURL, configuration.networkServices!!.networkMapURL) + } + private fun configDebugOptions(devMode: Boolean, devModeOptions: DevModeOptions?): NodeConfiguration { return testConfiguration.copy(devMode = devMode, devModeOptions = devModeOptions) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index 53b1b7aea5..fb803440cc 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -23,6 +23,7 @@ import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.millis import net.corda.node.NodeRegistrationOption +import net.corda.node.internal.ConfigurationException import net.corda.node.internal.Node import net.corda.node.internal.StartedNode import net.corda.node.services.Permissions @@ -184,7 +185,7 @@ class DriverDSLImpl( val registrationFuture = if (compatibilityZone?.rootCert != null) { // We don't need the network map to be available to be able to register the node - startNodeRegistration(name, compatibilityZone.rootCert, compatibilityZone.url) + startNodeRegistration(name, compatibilityZone.rootCert, compatibilityZone.doormanURL()) } else { doneFuture(Unit) } @@ -209,7 +210,15 @@ class DriverDSLImpl( val rpcAdminAddress = portAllocation.nextHostAndPort() val webAddress = portAllocation.nextHostAndPort() val users = rpcUsers.map { it.copy(permissions = it.permissions + DRIVER_REQUIRED_PERMISSIONS) } - val czUrlConfig = if (compatibilityZone != null) mapOf("compatibilityZoneURL" to compatibilityZone.url.toString()) else emptyMap() + val czUrlConfig = when (compatibilityZone) { + null -> emptyMap() + is SharedCompatibilityZoneParams -> + mapOf("compatibilityZoneURL" to compatibilityZone.doormanURL().toString()) + is SplitCompatibilityZoneParams -> + mapOf("networkServices.doormanURL" to compatibilityZone.doormanURL().toString(), + "networkServices.networkMapURL" to compatibilityZone.networkMapURL().toString()) + } + val overrides = configOf( "myLegalName" to name.toString(), "p2pAddress" to p2pAddress.toString(), @@ -412,7 +421,7 @@ class DriverDSLImpl( startNotaryIdentityGeneration() } else { // With a root cert specified we delegate generation of the notary identities to the CZ. - startAllNotaryRegistrations(compatibilityZone.rootCert, compatibilityZone.url) + startAllNotaryRegistrations(compatibilityZone.rootCert, compatibilityZone.doormanURL()) } notaryInfosFuture.map { notaryInfos -> compatibilityZone.publishNotaries(notaryInfos) @@ -502,7 +511,7 @@ class DriverDSLImpl( private fun startNotaries(localNetworkMap: LocalNetworkMap?, customOverrides: Map): List>> { return notarySpecs.map { when (it.cluster) { - null -> startSingleNotary(it, localNetworkMap, customOverrides ) + null -> startSingleNotary(it, localNetworkMap, customOverrides) is ClusterSpec.Raft, // DummyCluster is used for testing the notary communication path, and it does not matter // which underlying consensus algorithm is used, so we just stick to Raft @@ -870,7 +879,8 @@ class DriverDSLImpl( val index = stackTrace.indexOfLast { it.className == "net.corda.testing.driver.Driver" } // In this case we're dealing with the the RPCDriver or one of it's cousins which are internal and we don't care about them if (index == -1) return emptyList() - val callerPackage = Class.forName(stackTrace[index + 1].className).`package` ?: throw IllegalStateException("Function instantiating driver must be defined in a package.") + val callerPackage = Class.forName(stackTrace[index + 1].className).`package` + ?: throw IllegalStateException("Function instantiating driver must be defined in a package.") return listOf(callerPackage.name) } @@ -1057,15 +1067,49 @@ fun genericDriver( /** * Internal API to enable testing of the network map service and node registration process using the internal driver. - * @property url The base CZ URL for registration and network map updates + * * @property publishNotaries Hook for a network map server to capture the generated [NotaryInfo] objects needed for * creating the network parameters. This is needed as the network map server is expected to distribute it. The callback * will occur on a different thread to the driver-calling thread. * @property rootCert If specified then the nodes will register themselves with the doorman service using [url] and expect * the registration response to be rooted at this cert. If not specified then no registration is performed and the dev * root cert is used as normal. + * + * @see SharedCompatibilityZoneParams + * @see SplitCompatibilityZoneParams */ -data class CompatibilityZoneParams(val url: URL, val publishNotaries: (List) -> Unit, val rootCert: X509Certificate? = null) +sealed class CompatibilityZoneParams( + val publishNotaries: (List) -> Unit, + val rootCert: X509Certificate? = null +) { + abstract fun networkMapURL(): URL + abstract fun doormanURL(): URL +} + +/** + * Represent network management services, network map and doorman, running on the same URL + */ +class SharedCompatibilityZoneParams( + private val url: URL, + publishNotaries: (List) -> Unit, + rootCert: X509Certificate? = null +) : CompatibilityZoneParams(publishNotaries, rootCert) { + override fun doormanURL() = url + override fun networkMapURL() = url +} + +/** + * Represent network management services, network map and doorman, running on different URLs + */ +class SplitCompatibilityZoneParams( + private val doormanURL: URL, + private val networkMapURL: URL, + publishNotaries: (List) -> Unit, + rootCert: X509Certificate? = null +) : CompatibilityZoneParams(publishNotaries, rootCert) { + override fun doormanURL() = doormanURL + override fun networkMapURL() = networkMapURL +} fun internalDriver( isDebug: Boolean = DriverParameters().isDebug, diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt index 5cc09a5e64..b850782818 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt @@ -467,6 +467,7 @@ private fun mockNodeConfiguration(): NodeConfiguration { doReturn(null).whenever(it).jmxMonitoringHttpPort doReturn(true).whenever(it).devMode doReturn(null).whenever(it).compatibilityZoneURL + doReturn(null).whenever(it).networkServices doReturn(VerifierType.InMemory).whenever(it).verifierType doReturn(P2PMessagingRetryConfiguration(5.seconds, 3, backoffBase = 1.0)).whenever(it).p2pMessagingRetry doReturn(5.seconds.toMillis()).whenever(it).additionalNodeInfoPollingFrequencyMsec From 7cbc316b9d1d825177a8de69b3ffa4601573d68d Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Fri, 25 May 2018 13:00:04 +0100 Subject: [PATCH 12/13] CORDA-1521 - Fix rpc attachment smoke test / better AMQP logging (#3213) * CORDA-1521 - Fix rpc attachment smoke test / better AMQP logging * Remove poor debug message * Review comments * reduce debug spam --- .../kotlin/rpc/StandaloneCordaRPClientTest.kt | 17 ++++++ .../internal/amqp/DeserializationInput.kt | 6 ++- .../internal/amqp/EvolutionSerializer.kt | 6 ++- .../serialization/internal/amqp/Schema.kt | 8 ++- .../internal/amqp/SerializerFactory.kt | 44 ++++++++++++--- .../amqp/custom/InputStreamSerializer.kt | 10 +++- .../internal/amqp/StreamTests.kt | 53 +++++++++++++++++++ .../internal/amqp/testutils/AMQPTestUtils.kt | 9 ++++ 8 files changed, 142 insertions(+), 11 deletions(-) create mode 100644 serialization/src/test/kotlin/net/corda/serialization/internal/amqp/StreamTests.kt diff --git a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt index e898c7b610..f8b28eb37b 100644 --- a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt +++ b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt @@ -27,6 +27,7 @@ import net.corda.smoketesting.NodeProcess import org.apache.commons.io.output.NullOutputStream import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Test import java.io.FilterInputStream import java.io.InputStream @@ -94,8 +95,24 @@ class StandaloneCordaRPClientTest { financeJar.copyToDirectory(cordappsDir) } + @Test fun `test attachments`() { + val attachment = InputStreamAndHash.createInMemoryTestZip(attachmentSize, 1) + assertFalse(rpcProxy.attachmentExists(attachment.sha256)) + val id = attachment.inputStream.use { rpcProxy.uploadAttachment(it) } + assertEquals(attachment.sha256, id, "Attachment has incorrect SHA256 hash") + + val hash = HashingInputStream(Hashing.sha256(), rpcProxy.openAttachment(id)).use { it -> + it.copyTo(NullOutputStream()) + SecureHash.SHA256(it.hash().asBytes()) + } + assertEquals(attachment.sha256, hash) + } + + @Ignore("CORDA-1520 - After switching from Kryo to AMQP this test won't work") + @Test + fun `test wrapped attachments`() { val attachment = InputStreamAndHash.createInMemoryTestZip(attachmentSize, 1) assertFalse(rpcProxy.attachmentExists(attachment.sha256)) val id = WrapperStream(attachment.inputStream).use { rpcProxy.uploadAttachment(it) } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt index ac1fa6eb15..822bb3d855 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt @@ -5,6 +5,7 @@ import net.corda.core.serialization.EncodingWhitelist import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializedBytes import net.corda.core.utilities.ByteSequence +import net.corda.core.utilities.loggerFor import net.corda.serialization.internal.* import org.apache.qpid.proton.amqp.Binary import org.apache.qpid.proton.amqp.DescribedType @@ -29,6 +30,7 @@ data class ObjectAndEnvelope(val obj: T, val envelope: Envelope) class DeserializationInput @JvmOverloads constructor(private val serializerFactory: SerializerFactory, private val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist) { private val objectHistory: MutableList = mutableListOf() + private val logger = loggerFor() companion object { @VisibleForTesting @@ -73,7 +75,6 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto inline fun deserialize(bytes: SerializedBytes, context: SerializationContext): T = deserialize(bytes, T::class.java, context) - @Throws(NotSerializableException::class) private fun des(generator: () -> R): R { try { @@ -96,6 +97,9 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto fun deserialize(bytes: ByteSequence, clazz: Class, context: SerializationContext): T = des { val envelope = getEnvelope(bytes, encodingWhitelist) + + logger.trace("deserialize blob scheme=\"${envelope.schema.toString()}\"") + clazz.cast(readObjectOrNull(envelope.obj, SerializationSchemas(envelope.schema, envelope.transformsSchema), clazz, context)) } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializer.kt index 62914e1518..7a99b142ff 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializer.kt @@ -275,9 +275,13 @@ class EvolutionSerializerGetter : EvolutionSerializerGetterBase() { // both the new and old fingerprint if (newSerializer is CollectionSerializer || newSerializer is MapSerializer) { newSerializer - } else { + } else if (newSerializer is EnumSerializer){ EnumEvolutionSerializer.make(typeNotation, newSerializer, factory, schemas) } + else { + loggerFor().error("typeNotation=${typeNotation.name} Need to evolve unsupported type") + throw NotSerializableException ("${typeNotation.name} cannot be evolved") + } } } } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/Schema.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/Schema.kt index 009268c5e3..1c6156f818 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/Schema.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/Schema.kt @@ -153,7 +153,13 @@ sealed class TypeNotation : DescribedType { abstract val descriptor: Descriptor } -data class CompositeType(override val name: String, override val label: String?, override val provides: List, override val descriptor: Descriptor, val fields: List) : TypeNotation() { +data class CompositeType( + override val name: String, + override val label: String?, + override val provides: List, + override val descriptor: Descriptor, + val fields: List +) : TypeNotation() { companion object : DescribedTypeConstructor { val DESCRIPTOR = AMQPDescriptorRegistry.COMPOSITE_TYPE.amqpDescriptor diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt index 7162f27afb..b63e0f12e9 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt @@ -5,6 +5,7 @@ import com.google.common.reflect.TypeResolver import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.ClassWhitelist import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.trace import net.corda.serialization.internal.carpenter.* import org.apache.qpid.proton.amqp.* import java.io.NotSerializableException @@ -54,6 +55,7 @@ open class SerializerFactory( serializersByDescriptor = ConcurrentHashMap(), customSerializers = CopyOnWriteArrayList(), transformsCache = ConcurrentHashMap()) + constructor(whitelist: ClassWhitelist, classLoader: ClassLoader, evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(), @@ -74,6 +76,8 @@ open class SerializerFactory( private fun getEvolutionSerializer(typeNotation: TypeNotation, newSerializer: AMQPSerializer, schemas: SerializationSchemas) = evolutionSerializerGetter.getEvolutionSerializer(this, typeNotation, newSerializer, schemas) + private val logger = loggerFor() + /** * Look up, and manufacture if necessary, a serializer for the given type. * @@ -82,6 +86,9 @@ open class SerializerFactory( */ @Throws(NotSerializableException::class) fun get(actualClass: Class<*>?, declaredType: Type): AMQPSerializer { + // can be useful to enable but will be *extremely* chatty if you do + logger.trace { "Get Serializer for $actualClass ${declaredType.typeName}" } + val declaredClass = declaredType.asClass() ?: throw NotSerializableException( "Declared types of $declaredType are not supported.") @@ -107,10 +114,15 @@ open class SerializerFactory( makeMapSerializer(declaredTypeAmended) } } - Enum::class.java.isAssignableFrom(actualClass - ?: declaredClass) -> serializersByType.computeIfAbsent(actualClass ?: declaredClass) { - whitelist.requireWhitelisted(actualType) - EnumSerializer(actualType, actualClass ?: declaredClass, this) + Enum::class.java.isAssignableFrom(actualClass ?: declaredClass) -> { + logger.debug("class=[${actualClass?.simpleName} | $declaredClass] is an enumeration " + + "declaredType=${declaredType.typeName} " + + "isEnum=${declaredType::class.java.isEnum}") + + serializersByType.computeIfAbsent(actualClass ?: declaredClass) { + whitelist.requireWhitelisted(actualType) + EnumSerializer(actualType, actualClass ?: declaredClass, this) + } } else -> { makeClassSerializer(actualClass ?: declaredClass, actualType, declaredType) @@ -198,6 +210,7 @@ open class SerializerFactory( @Throws(NotSerializableException::class) fun get(typeDescriptor: Any, schema: SerializationSchemas): AMQPSerializer { return serializersByDescriptor[typeDescriptor] ?: { + logger.trace("get Serializer descriptor=${typeDescriptor}") processSchema(FactorySchemaAndDescriptor(schema, typeDescriptor)) serializersByDescriptor[typeDescriptor] ?: throw NotSerializableException( "Could not find type matching descriptor $typeDescriptor.") @@ -232,16 +245,24 @@ open class SerializerFactory( private fun processSchema(schemaAndDescriptor: FactorySchemaAndDescriptor, sentinel: Boolean = false) { val metaSchema = CarpenterMetaSchema.newInstance() for (typeNotation in schemaAndDescriptor.schemas.schema.types) { + logger.trace("descriptor=${schemaAndDescriptor.typeDescriptor}, typeNotation=${typeNotation.name}") try { val serialiser = processSchemaEntry(typeNotation) // if we just successfully built a serializer for the type but the type fingerprint // doesn't match that of the serialised object then we are dealing with different // instance of the class, as such we need to build an EvolutionSerializer if (serialiser.typeDescriptor != typeNotation.descriptor.name) { + logger.info("typeNotation=${typeNotation.name} action=\"requires Evolution\"") getEvolutionSerializer(typeNotation, serialiser, schemaAndDescriptor.schemas) } } catch (e: ClassNotFoundException) { - if (sentinel) throw e + if (sentinel) { + logger.error("typeNotation=${typeNotation.name} error=\"after Carpentry attempt failed to load\"") + throw e + } + else { + logger.info("typeNotation=\"${typeNotation.name}\" action=\"carpentry required\"") + } metaSchema.buildFor(typeNotation, classloader) } } @@ -270,8 +291,16 @@ open class SerializerFactory( } private fun processSchemaEntry(typeNotation: TypeNotation) = when (typeNotation) { - is CompositeType -> processCompositeType(typeNotation) // java.lang.Class (whether a class or interface) - is RestrictedType -> processRestrictedType(typeNotation) // Collection / Map, possibly with generics + // java.lang.Class (whether a class or interface) + is CompositeType -> { + logger.trace("typeNotation=${typeNotation.name} amqpType=CompositeType") + processCompositeType(typeNotation) + } + // Collection / Map, possibly with generics + is RestrictedType -> { + logger.trace("typeNotation=${typeNotation.name} amqpType=RestrictedType") + processRestrictedType(typeNotation) + } } // TODO: class loader logic, and compare the schema. @@ -285,6 +314,7 @@ open class SerializerFactory( } private fun makeClassSerializer(clazz: Class<*>, type: Type, declaredType: Type): AMQPSerializer = serializersByType.computeIfAbsent(type) { + logger.debug("class=${clazz.simpleName}, type=$type is a composite type") if (clazz.isSynthetic) { // Explicitly ban synthetic classes, we have no way of recreating them when deserializing. This also // captures Lambda expressions and other anonymous functions diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/InputStreamSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/InputStreamSerializer.kt index cc100d551e..1a7f5fce89 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/InputStreamSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/InputStreamSerializer.kt @@ -14,7 +14,15 @@ import java.lang.reflect.Type object InputStreamSerializer : CustomSerializer.Implements(InputStream::class.java) { override val revealSubclassesInSchema: Boolean = true - override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, descriptor, emptyList()))) + override val schemaForDocumentation = Schema( + listOf( + RestrictedType( + type.toString(), + "", + listOf(type.toString()), + SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, + descriptor, + emptyList()))) override fun writeDescribedObject(obj: InputStream, data: Data, type: Type, output: SerializationOutput, context: SerializationContext diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/StreamTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/StreamTests.kt new file mode 100644 index 0000000000..4ac37e433b --- /dev/null +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/StreamTests.kt @@ -0,0 +1,53 @@ +package net.corda.serialization.internal.amqp + +import net.corda.core.internal.InputStreamAndHash +import net.corda.serialization.internal.amqp.custom.InputStreamSerializer +import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput +import net.corda.serialization.internal.amqp.testutils.deserialize +import net.corda.serialization.internal.amqp.testutils.testDefaultFactory +import org.junit.Test +import java.io.FilterInputStream +import java.io.InputStream + +class StreamTests { + + private class WrapperStream(input: InputStream) : FilterInputStream(input) + + @Test + fun inputStream() { + val attachment = InputStreamAndHash.createInMemoryTestZip(2116, 1) + val id : InputStream = WrapperStream(attachment.inputStream) + + val serializerFactory = testDefaultFactory().apply { + register(InputStreamSerializer) + } + + val bytes = TestSerializationOutput(true, serializerFactory).serialize(id) + + val deserializerFactory = testDefaultFactory().apply { + register(InputStreamSerializer) + } + + DeserializationInput(serializerFactory).deserialize(bytes) + DeserializationInput(deserializerFactory).deserialize(bytes) + } + + @Test + fun listInputStream() { + val attachment = InputStreamAndHash.createInMemoryTestZip(2116, 1) + val id /* : List */= listOf(WrapperStream(attachment.inputStream)) + + val serializerFactory = testDefaultFactory().apply { + register(InputStreamSerializer) + } + + val bytes = TestSerializationOutput(true, serializerFactory).serialize(id) + + val deserializerFactory = testDefaultFactory().apply { + register(InputStreamSerializer) + } + + DeserializationInput(serializerFactory).deserialize(bytes) + DeserializationInput(deserializerFactory).deserialize(bytes) + } +} \ No newline at end of file diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/testutils/AMQPTestUtils.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/testutils/AMQPTestUtils.kt index 68e4227172..8d78ed61c6 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/testutils/AMQPTestUtils.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/testutils/AMQPTestUtils.kt @@ -30,6 +30,15 @@ class TestSerializationOutput( } super.writeTransformSchema(transformsSchema, data) } + + @Throws(NotSerializableException::class) + fun serialize(obj: T): SerializedBytes { + try { + return _serialize(obj, testSerializationContext) + } finally { + andFinally() + } + } } fun testName(): String = Thread.currentThread().stackTrace[2].methodName From 59fdb3df67ebdfcca48fa86677c77031aefd72fd Mon Sep 17 00:00:00 2001 From: Rick Parker Date: Fri, 25 May 2018 13:26:00 +0100 Subject: [PATCH 13/13] CORDA-1475 CORDA-1465 Allow flows to retry from last checkpoint (#3204) --- .../corda/core/internal/FlowStateMachine.kt | 1 + .../internal/persistence/CordaPersistence.kt | 17 +- .../persistence/DatabaseTransaction.kt | 16 +- .../net/corda/node/flows/FlowRetryTest.kt | 158 ++++++ .../events/ScheduledFlowIntegrationTests.kt | 6 +- .../net/corda/node/internal/AbstractNode.kt | 33 +- .../node/services/api/CheckpointStorage.kt | 6 + .../node/services/api/ServiceHubInternal.kt | 14 +- .../services/events/NodeSchedulerService.kt | 71 ++- .../node/services/messaging/Messaging.kt | 28 +- .../messaging/P2PMessageDeduplicator.kt | 3 - .../services/messaging/P2PMessagingClient.kt | 25 +- .../persistence/DBCheckpointStorage.kt | 7 +- .../persistence/DBTransactionStorage.kt | 7 +- .../node/services/statemachine/Action.kt | 12 +- .../statemachine/ActionExecutorImpl.kt | 9 +- .../services/statemachine/DeduplicationId.kt | 6 + .../corda/node/services/statemachine/Event.kt | 19 +- .../services/statemachine/FlowHospital.kt | 7 +- .../services/statemachine/FlowMessaging.kt | 4 +- .../statemachine/FlowStateMachineImpl.kt | 15 +- .../statemachine/PropagatingFlowHospital.kt | 9 +- .../SingleThreadedStateMachineManager.kt | 105 +++- .../statemachine/StaffedFlowHospital.kt | 127 +++++ .../statemachine/StateMachineManager.kt | 57 ++- .../statemachine/StateMachineState.kt | 4 +- .../DumpHistoryOnErrorInterceptor.kt | 3 +- .../interceptors/HospitalisingInterceptor.kt | 23 +- .../DeliverSessionMessageTransition.kt | 20 +- .../transitions/ErrorFlowTransition.kt | 2 +- .../transitions/StartedFlowTransition.kt | 6 +- .../transitions/TopLevelTransition.kt | 37 +- .../transitions/UnstartedFlowTransition.kt | 2 +- .../node/utilities/AppendOnlyPersistentMap.kt | 287 +++++++++-- .../events/NodeSchedulerServiceTest.kt | 33 +- .../AppendOnlyPersistentMapTest.kt | 290 +++++++++++ .../persistence/TransactionCallbackTest.kt | 49 ++ .../statemachine/RetryFlowMockTest.kt | 166 ++++++ .../node/services/vault/VaultQueryTests.kt | 483 +++++++++--------- .../corda/node/utilities/ObservablesTests.kt | 69 ++- .../testing/node/InMemoryMessagingNetwork.kt | 76 ++- 41 files changed, 1843 insertions(+), 469 deletions(-) create mode 100644 node/src/integration-test/kotlin/net/corda/node/flows/FlowRetryTest.kt create mode 100644 node/src/main/kotlin/net/corda/node/services/statemachine/StaffedFlowHospital.kt create mode 100644 node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt create mode 100644 node/src/test/kotlin/net/corda/node/services/persistence/TransactionCallbackTest.kt create mode 100644 node/src/test/kotlin/net/corda/node/services/statemachine/RetryFlowMockTest.kt diff --git a/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt b/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt index 36002556c8..fc6a5a529e 100644 --- a/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt +++ b/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt @@ -41,4 +41,5 @@ interface FlowStateMachine { val resultFuture: CordaFuture val context: InvocationContext val ourIdentity: Party + val ourSenderUUID: String? } 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 31ca0cf166..dbec43d561 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 @@ -5,7 +5,6 @@ import net.corda.core.schemas.MappedSchema import net.corda.core.utilities.contextLogger import rx.Observable import rx.Subscriber -import rx.subjects.PublishSubject import rx.subjects.UnicastSubject import java.io.Closeable import java.sql.Connection @@ -67,9 +66,7 @@ class CordaPersistence( } val entityManagerFactory get() = hibernateConfig.sessionFactoryForRegisteredSchemas - data class Boundary(val txId: UUID) - - internal val transactionBoundaries = PublishSubject.create().toSerialized() + data class Boundary(val txId: UUID, val success: Boolean) init { // Found a unit test that was forgetting to close the database transactions. When you close() on the top level @@ -186,15 +183,19 @@ class CordaPersistence( * * For examples, see the call hierarchy of this function. */ -fun rx.Observer.bufferUntilDatabaseCommit(): rx.Observer { - val currentTxId = contextTransaction.id - val databaseTxBoundary: Observable = contextDatabase.transactionBoundaries.first { it.txId == currentTxId } +fun rx.Observer.bufferUntilDatabaseCommit(propagateRollbackAsError: Boolean = false): rx.Observer { + val currentTx = contextTransaction val subject = UnicastSubject.create() + val databaseTxBoundary: Observable = currentTx.boundary.filter { it.success } + if (propagateRollbackAsError) { + currentTx.boundary.filter { !it.success }.subscribe { this.onError(DatabaseTransactionRolledBackException(it.txId)) } + } subject.delaySubscription(databaseTxBoundary).subscribe(this) - databaseTxBoundary.doOnCompleted { subject.onCompleted() } return subject } +class DatabaseTransactionRolledBackException(txId: UUID) : Exception("Database transaction $txId was rolled back") + // A subscriber that delegates to multiple others, wrapping a database transaction around the combination. private class DatabaseTransactionWrappingSubscriber(private val db: CordaPersistence?) : Subscriber() { // Some unsubscribes happen inside onNext() so need something that supports concurrent modification. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/DatabaseTransaction.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/DatabaseTransaction.kt index ecc48300e3..578479d39a 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/DatabaseTransaction.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/DatabaseTransaction.kt @@ -3,6 +3,7 @@ package net.corda.nodeapi.internal.persistence import co.paralleluniverse.strands.Strand import org.hibernate.Session import org.hibernate.Transaction +import rx.subjects.PublishSubject import java.sql.Connection import java.util.* @@ -35,11 +36,16 @@ class DatabaseTransaction( val session: Session by sessionDelegate private lateinit var hibernateTransaction: Transaction + + internal val boundary = PublishSubject.create() + private var committed = false + fun commit() { if (sessionDelegate.isInitialized()) { hibernateTransaction.commit() } connection.commit() + committed = true } fun rollback() { @@ -58,7 +64,15 @@ class DatabaseTransaction( connection.close() contextTransactionOrNull = outerTransaction if (outerTransaction == null) { - database.transactionBoundaries.onNext(CordaPersistence.Boundary(id)) + boundary.onNext(CordaPersistence.Boundary(id, committed)) } } + + fun onCommit(callback: () -> Unit) { + boundary.filter { it.success }.subscribe { callback() } + } + + fun onRollback(callback: () -> Unit) { + boundary.filter { !it.success }.subscribe { callback() } + } } diff --git a/node/src/integration-test/kotlin/net/corda/node/flows/FlowRetryTest.kt b/node/src/integration-test/kotlin/net/corda/node/flows/FlowRetryTest.kt new file mode 100644 index 0000000000..40bdb29444 --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/flows/FlowRetryTest.kt @@ -0,0 +1,158 @@ +package net.corda.node.flows + +import co.paralleluniverse.fibers.Suspendable +import net.corda.client.rpc.CordaRPCClient +import net.corda.core.flows.* +import net.corda.core.identity.Party +import net.corda.core.messaging.startFlow +import net.corda.core.serialization.CordaSerializable +import net.corda.core.utilities.ProgressTracker +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.unwrap +import net.corda.node.services.Permissions +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.Before +import org.junit.Test +import java.lang.management.ManagementFactory +import java.sql.SQLException +import java.util.* +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + + +class FlowRetryTest { + @Before + fun resetCounters() { + InitiatorFlow.seen.clear() + InitiatedFlow.seen.clear() + } + + @Test + fun `flows continue despite errors`() { + val numSessions = 2 + val numIterations = 10 + val user = User("mark", "dadada", setOf(Permissions.startFlow())) + val result: Any? = driver(DriverParameters(isDebug = true, startNodesInProcess = isQuasarAgentSpecified(), + portAllocation = RandomFree)) { + + val nodeAHandle = startNode(rpcUsers = listOf(user)).getOrThrow() + val nodeBHandle = startNode(rpcUsers = listOf(user)).getOrThrow() + + val result = CordaRPCClient(nodeAHandle.rpcAddress).start(user.username, user.password).use { + it.proxy.startFlow(::InitiatorFlow, numSessions, numIterations, nodeBHandle.nodeInfo.singleIdentity()).returnValue.getOrThrow() + } + result + } + assertNotNull(result) + assertEquals("$numSessions:$numIterations", result) + } +} + +fun isQuasarAgentSpecified(): Boolean { + val jvmArgs = ManagementFactory.getRuntimeMXBean().inputArguments + return jvmArgs.any { it.startsWith("-javaagent:") && it.contains("quasar") } +} + +class ExceptionToCauseRetry : SQLException("deadlock") + +@StartableByRPC +@InitiatingFlow +class InitiatorFlow(private val sessionsCount: Int, private val iterationsCount: Int, private val other: Party) : FlowLogic() { + companion object { + object FIRST_STEP : ProgressTracker.Step("Step one") + + fun tracker() = ProgressTracker(FIRST_STEP) + + val seen = Collections.synchronizedSet(HashSet()) + + fun visit(sessionNum: Int, iterationNum: Int, step: Step) { + val visited = Visited(sessionNum, iterationNum, step) + if (visited !in seen) { + seen += visited + throw ExceptionToCauseRetry() + } + } + } + + override val progressTracker = tracker() + + @Suspendable + override fun call(): Any { + progressTracker.currentStep = FIRST_STEP + var received: Any? = null + visit(-1, -1, Step.First) + for (sessionNum in 1..sessionsCount) { + visit(sessionNum, -1, Step.BeforeInitiate) + val session = initiateFlow(other) + visit(sessionNum, -1, Step.AfterInitiate) + session.send(SessionInfo(sessionNum, iterationsCount)) + visit(sessionNum, -1, Step.AfterInitiateSendReceive) + for (iteration in 1..iterationsCount) { + visit(sessionNum, iteration, Step.BeforeSend) + logger.info("A Sending $sessionNum:$iteration") + session.send("$sessionNum:$iteration") + visit(sessionNum, iteration, Step.AfterSend) + received = session.receive().unwrap { it } + visit(sessionNum, iteration, Step.AfterReceive) + logger.info("A Got $sessionNum:$iteration") + } + doSleep() + } + return received!! + } + + // This non-flow-friendly sleep triggered a bug with session end messages and non-retryable checkpoints. + private fun doSleep() { + Thread.sleep(2000) + } +} + +@InitiatedBy(InitiatorFlow::class) +class InitiatedFlow(val session: FlowSession) : FlowLogic() { + companion object { + object FIRST_STEP : ProgressTracker.Step("Step one") + + fun tracker() = ProgressTracker(FIRST_STEP) + + val seen = Collections.synchronizedSet(HashSet()) + + fun visit(sessionNum: Int, iterationNum: Int, step: Step) { + val visited = Visited(sessionNum, iterationNum, step) + if (visited !in seen) { + seen += visited + throw ExceptionToCauseRetry() + } + } + } + + override val progressTracker = tracker() + + @Suspendable + override fun call() { + progressTracker.currentStep = FIRST_STEP + visit(-1, -1, Step.AfterInitiate) + val sessionInfo = session.receive().unwrap { it } + visit(sessionInfo.sessionNum, -1, Step.AfterInitiateSendReceive) + for (iteration in 1..sessionInfo.iterationsCount) { + visit(sessionInfo.sessionNum, iteration, Step.BeforeReceive) + val got = session.receive().unwrap { it } + visit(sessionInfo.sessionNum, iteration, Step.AfterReceive) + logger.info("B Got $got") + logger.info("B Sending $got") + visit(sessionInfo.sessionNum, iteration, Step.BeforeSend) + session.send(got) + visit(sessionInfo.sessionNum, iteration, Step.AfterSend) + } + } +} + +@CordaSerializable +data class SessionInfo(val sessionNum: Int, val iterationsCount: Int) + +enum class Step { First, BeforeInitiate, AfterInitiate, AfterInitiateSendReceive, BeforeSend, AfterSend, BeforeReceive, AfterReceive } + +data class Visited(val sessionNum: Int, val iterationNum: Int, val step: Step) \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/node/services/events/ScheduledFlowIntegrationTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/events/ScheduledFlowIntegrationTests.kt index d6970efa58..110bf1ebb9 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/events/ScheduledFlowIntegrationTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/events/ScheduledFlowIntegrationTests.kt @@ -25,6 +25,7 @@ import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.seconds import net.corda.testMessage.ScheduledState import net.corda.testMessage.SpentState import net.corda.testing.contracts.DummyContract @@ -96,7 +97,7 @@ class ScheduledFlowIntegrationTests { val aliceClient = CordaRPCClient(alice.rpcAddress).start(rpcUser.username, rpcUser.password) val bobClient = CordaRPCClient(bob.rpcAddress).start(rpcUser.username, rpcUser.password) - val scheduledFor = Instant.now().plusSeconds(20) + val scheduledFor = Instant.now().plusSeconds(10) val initialiseFutures = mutableListOf>() for (i in 0 until N) { initialiseFutures.add(aliceClient.proxy.startFlow(::InsertInitialStateFlow, bob.nodeInfo.legalIdentities.first(), defaultNotaryIdentity, i, scheduledFor).returnValue) @@ -111,6 +112,9 @@ class ScheduledFlowIntegrationTests { } spendAttemptFutures.getOrThrowAll() + // TODO: the queries below are not atomic so we need to allow enough time for the scheduler to finish. Would be better to query scheduler. + Thread.sleep(20.seconds.toMillis()) + val aliceStates = aliceClient.proxy.vaultQuery(ScheduledState::class.java).states.filter { it.state.data.processed } val aliceSpentStates = aliceClient.proxy.vaultQuery(SpentState::class.java).states 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 d3a356cbd8..8a0b1c3203 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -985,8 +985,37 @@ internal fun logVendorString(database: CordaPersistence, log: Logger) { } internal class FlowStarterImpl(private val smm: StateMachineManager, private val flowLogicRefFactory: FlowLogicRefFactory) : FlowStarter { - override fun startFlow(logic: FlowLogic, context: InvocationContext, deduplicationHandler: DeduplicationHandler?): CordaFuture> { - return smm.startFlow(logic, context, ourIdentity = null, deduplicationHandler = deduplicationHandler) + override fun startFlow(event: ExternalEvent.ExternalStartFlowEvent): CordaFuture> { + smm.deliverExternalEvent(event) + return event.future + } + + override fun startFlow(logic: FlowLogic, context: InvocationContext): CordaFuture> { + val startFlowEvent = object : ExternalEvent.ExternalStartFlowEvent, DeduplicationHandler { + override fun insideDatabaseTransaction() {} + + override fun afterDatabaseTransaction() {} + + override val externalCause: ExternalEvent + get() = this + override val deduplicationHandler: DeduplicationHandler + get() = this + + override val flowLogic: FlowLogic + get() = logic + override val context: InvocationContext + get() = context + + override fun wireUpFuture(flowFuture: CordaFuture>) { + _future.captureLater(flowFuture) + } + + private val _future = openFuture>() + override val future: CordaFuture> + get() = _future + + } + return startFlow(startFlowEvent) } override fun invokeFlowAsync( diff --git a/node/src/main/kotlin/net/corda/node/services/api/CheckpointStorage.kt b/node/src/main/kotlin/net/corda/node/services/api/CheckpointStorage.kt index 5d0cf99dde..7901ea7f1e 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/CheckpointStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/CheckpointStorage.kt @@ -20,6 +20,12 @@ interface CheckpointStorage { */ fun removeCheckpoint(id: StateMachineRunId): Boolean + /** + * Load an existing checkpoint from the store. + * @return the checkpoint, still in serialized form, or null if not found. + */ + fun getCheckpoint(id: StateMachineRunId): SerializedBytes? + /** * Stream all checkpoints from the store. If this is backed by a database the stream will be valid until the * underlying database connection is closed, so any processing should happen before it is closed. diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index df04be4140..a32f3c771a 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -19,9 +19,9 @@ import net.corda.core.utilities.contextLogger import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.internal.cordapp.CordappProviderInternal import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.messaging.DeduplicationHandler import net.corda.node.services.messaging.MessagingService import net.corda.node.services.network.NetworkMapUpdater +import net.corda.node.services.statemachine.ExternalEvent import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.nodeapi.internal.persistence.CordaPersistence @@ -134,11 +134,17 @@ interface ServiceHubInternal : ServiceHub { interface FlowStarter { /** - * Starts an already constructed flow. Note that you must be on the server thread to call this method. + * Starts an already constructed flow. Note that you must be on the server thread to call this method. This method + * just synthesizes an [ExternalEvent.ExternalStartFlowEvent] and calls the method below. * @param context indicates who started the flow, see: [InvocationContext]. - * @param deduplicationHandler allows exactly-once start of the flow, see [DeduplicationHandler] */ - fun startFlow(logic: FlowLogic, context: InvocationContext, deduplicationHandler: DeduplicationHandler? = null): CordaFuture> + fun startFlow(logic: FlowLogic, context: InvocationContext): CordaFuture> + + /** + * Starts a flow as described by an [ExternalEvent.ExternalStartFlowEvent]. If a transient error + * occurs during invocation, it will re-attempt to start the flow. + */ + fun startFlow(event: ExternalEvent.ExternalStartFlowEvent): CordaFuture> /** * Will check [logicType] and [args] against a whitelist and if acceptable then construct and initiate the flow. diff --git a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt index 22de33c70e..a57b2b0f05 100644 --- a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt +++ b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt @@ -2,6 +2,7 @@ package net.corda.node.services.events import co.paralleluniverse.fibers.Suspendable import com.google.common.util.concurrent.ListenableFuture +import net.corda.core.concurrent.CordaFuture import net.corda.core.context.InvocationContext import net.corda.core.context.InvocationOrigin import net.corda.core.contracts.SchedulableState @@ -10,11 +11,9 @@ import net.corda.core.contracts.ScheduledStateRef import net.corda.core.contracts.StateRef import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogicRefFactory -import net.corda.core.internal.ThreadBox -import net.corda.core.internal.VisibleForTesting +import net.corda.core.internal.* import net.corda.core.internal.concurrent.flatMap -import net.corda.core.internal.join -import net.corda.core.internal.until +import net.corda.core.internal.concurrent.openFuture import net.corda.core.node.ServicesForResolution import net.corda.core.schemas.PersistentStateRef import net.corda.core.serialization.SingletonSerializeAsToken @@ -26,8 +25,10 @@ import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.NodePropertiesStore import net.corda.node.services.api.SchedulerService import net.corda.node.services.messaging.DeduplicationHandler +import net.corda.node.services.statemachine.ExternalEvent import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX +import net.corda.nodeapi.internal.persistence.contextTransaction import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.mina.util.ConcurrentHashSet import org.slf4j.Logger @@ -156,29 +157,31 @@ class NodeSchedulerService(private val clock: CordaClock, override fun scheduleStateActivity(action: ScheduledStateRef) { log.trace { "Schedule $action" } - if (!schedulerRepo.merge(action)) { - // Only increase the number of unfinished schedules if the state didn't already exist on the queue - unfinishedSchedules.countUp() - } - mutex.locked { - if (action.scheduledAt < nextScheduledAction?.scheduledAt ?: Instant.MAX) { - // We are earliest - rescheduleWakeUp() - } else if (action.ref == nextScheduledAction?.ref && action.scheduledAt != nextScheduledAction?.scheduledAt) { - // We were earliest but might not be any more - rescheduleWakeUp() + // Only increase the number of unfinished schedules if the state didn't already exist on the queue + val countUp = !schedulerRepo.merge(action) + contextTransaction.onCommit { + if (countUp) unfinishedSchedules.countUp() + mutex.locked { + if (action.scheduledAt < nextScheduledAction?.scheduledAt ?: Instant.MAX) { + // We are earliest + rescheduleWakeUp() + } else if (action.ref == nextScheduledAction?.ref && action.scheduledAt != nextScheduledAction?.scheduledAt) { + // We were earliest but might not be any more + rescheduleWakeUp() + } } } } override fun unscheduleStateActivity(ref: StateRef) { log.trace { "Unschedule $ref" } - if (startingStateRefs.all { it.ref != ref } && schedulerRepo.delete(ref)) { - unfinishedSchedules.countDown() - } - mutex.locked { - if (nextScheduledAction?.ref == ref) { - rescheduleWakeUp() + val countDown = startingStateRefs.all { it.ref != ref } && schedulerRepo.delete(ref) + contextTransaction.onCommit { + if (countDown) unfinishedSchedules.countDown() + mutex.locked { + if (nextScheduledAction?.ref == ref) { + rescheduleWakeUp() + } } } } @@ -227,7 +230,12 @@ class NodeSchedulerService(private val clock: CordaClock, schedulerTimerExecutor.join() } - private inner class FlowStartDeduplicationHandler(val scheduledState: ScheduledStateRef) : DeduplicationHandler { + private inner class FlowStartDeduplicationHandler(val scheduledState: ScheduledStateRef, override val flowLogic: FlowLogic, override val context: InvocationContext) : DeduplicationHandler, ExternalEvent.ExternalStartFlowEvent { + override val externalCause: ExternalEvent + get() = this + override val deduplicationHandler: FlowStartDeduplicationHandler + get() = this + override fun insideDatabaseTransaction() { schedulerRepo.delete(scheduledState.ref) } @@ -239,6 +247,18 @@ class NodeSchedulerService(private val clock: CordaClock, override fun toString(): String { return "${javaClass.simpleName}($scheduledState)" } + + override fun wireUpFuture(flowFuture: CordaFuture>) { + _future.captureLater(flowFuture) + val future = _future.flatMap { it.resultFuture } + future.then { + unfinishedSchedules.countDown() + } + } + + private val _future = openFuture>() + override val future: CordaFuture> + get() = _future } private fun onTimeReached(scheduledState: ScheduledStateRef) { @@ -250,11 +270,8 @@ class NodeSchedulerService(private val clock: CordaClock, flowName = scheduledFlow.javaClass.name // TODO refactor the scheduler to store and propagate the original invocation context val context = InvocationContext.newInstance(InvocationOrigin.Scheduled(scheduledState)) - val deduplicationHandler = FlowStartDeduplicationHandler(scheduledState) - val future = flowStarter.startFlow(scheduledFlow, context, deduplicationHandler).flatMap { it.resultFuture } - future.then { - unfinishedSchedules.countDown() - } + val startFlowEvent = FlowStartDeduplicationHandler(scheduledState, scheduledFlow, context) + flowStarter.startFlow(startFlowEvent) } } } catch (e: Exception) { diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt index 4d13f0df12..9dcb8658dd 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt @@ -10,6 +10,8 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.serialize import net.corda.core.utilities.ByteSequence import net.corda.node.services.statemachine.DeduplicationId +import net.corda.node.services.statemachine.ExternalEvent +import net.corda.node.services.statemachine.SenderDeduplicationId import java.time.Instant import javax.annotation.concurrent.ThreadSafe @@ -25,6 +27,12 @@ import javax.annotation.concurrent.ThreadSafe */ @ThreadSafe interface MessagingService { + /** + * A unique identifier for this sender that changes whenever a node restarts. This is used in conjunction with a sequence + * number for message de-duplication at the recipient. + */ + val ourSenderUUID: String + /** * The provided function will be invoked for each received message whose topic and session matches. The callback * will run on the main server thread provided when the messaging service is constructed, and a database @@ -93,11 +101,12 @@ interface MessagingService { /** * Returns an initialised [Message] with the current time, etc, already filled in. * - * @param topicSession identifier for the topic and session the message is sent to. - * @param additionalProperties optional additional message headers. * @param topic identifier for the topic the message is sent to. + * @param data the payload for the message. + * @param deduplicationId optional message deduplication ID including sender identifier. + * @param additionalHeaders optional additional message headers. */ - fun createMessage(topic: String, data: ByteArray, deduplicationId: DeduplicationId = DeduplicationId.createRandom(newSecureRandom()), additionalHeaders: Map = emptyMap()): Message + fun createMessage(topic: String, data: ByteArray, deduplicationId: SenderDeduplicationId = SenderDeduplicationId(DeduplicationId.createRandom(newSecureRandom()), ourSenderUUID), additionalHeaders: Map = emptyMap()): Message /** Given information about either a specific node or a service returns its corresponding address */ fun getAddressOfParty(partyInfo: PartyInfo): MessageRecipients @@ -106,7 +115,7 @@ interface MessagingService { val myAddress: SingleMessageRecipient } -fun MessagingService.send(topicSession: String, payload: Any, to: MessageRecipients, deduplicationId: DeduplicationId = DeduplicationId.createRandom(newSecureRandom()), retryId: Long? = null, additionalHeaders: Map = emptyMap()) = send(createMessage(topicSession, payload.serialize().bytes, deduplicationId, additionalHeaders), to, retryId) +fun MessagingService.send(topicSession: String, payload: Any, to: MessageRecipients, deduplicationId: SenderDeduplicationId = SenderDeduplicationId(DeduplicationId.createRandom(newSecureRandom()), ourSenderUUID), retryId: Long? = null, additionalHeaders: Map = emptyMap()) = send(createMessage(topicSession, payload.serialize().bytes, deduplicationId, additionalHeaders), to, retryId) interface MessageHandlerRegistration @@ -152,15 +161,17 @@ object TopicStringValidator { } /** - * This handler is used to implement exactly-once delivery of an event on top of a possibly duplicated one. This is done + * This handler is used to implement exactly-once delivery of an external event on top of an at-least-once delivery. This is done * using two hooks that are called from the event processor, one called from the database transaction committing the - * side-effect caused by the event, and another one called after the transaction has committed successfully. + * side-effect caused by the external event, and another one called after the transaction has committed successfully. * * For example for messaging we can use [insideDatabaseTransaction] to store the message's unique ID for later * deduplication, and [afterDatabaseTransaction] to acknowledge the message and stop retries. * * We also use this for exactly-once start of a scheduled flow, [insideDatabaseTransaction] is used to remove the * to-be-scheduled state of the flow, [afterDatabaseTransaction] is used for cleanup of in-memory bookkeeping. + * + * It holds a reference back to the causing external event. */ interface DeduplicationHandler { /** @@ -174,6 +185,11 @@ interface DeduplicationHandler { * cleanup/acknowledgement/stopping of retries. */ fun afterDatabaseTransaction() + + /** + * The external event for which we are trying to reduce from at-least-once delivery to exactly-once. + */ + val externalCause: ExternalEvent } typealias MessageHandler = (ReceivedMessage, MessageHandlerRegistration, DeduplicationHandler) -> Unit diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessageDeduplicator.kt b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessageDeduplicator.kt index 76075477c3..dd666f51a3 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessageDeduplicator.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessageDeduplicator.kt @@ -8,7 +8,6 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import java.io.Serializable import java.time.Instant -import java.util.* import java.util.concurrent.ConcurrentHashMap import javax.persistence.Column import javax.persistence.Entity @@ -18,8 +17,6 @@ import javax.persistence.Id * Encapsulate the de-duplication logic. */ class P2PMessageDeduplicator(private val database: CordaPersistence) { - val ourSenderUUID = UUID.randomUUID().toString() - // A temporary in-memory set of deduplication IDs and associated high water mark details. // When we receive a message we don't persist the ID immediately, // so we store the ID here in the meantime (until the persisting db tx has committed). This is because Artemis may 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 c8e65c7c21..84b9f19490 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 @@ -23,6 +23,8 @@ import net.corda.node.internal.artemis.ReactiveArtemisConsumer.Companion.multipl import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.statemachine.DeduplicationId +import net.corda.node.services.statemachine.ExternalEvent +import net.corda.node.services.statemachine.SenderDeduplicationId import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.PersistentMap import net.corda.nodeapi.ArtemisTcpTransport.Companion.p2pConnectorTcpTransport @@ -124,7 +126,7 @@ class P2PMessagingClient(val config: NodeConfiguration, ) } - private class NodeClientMessage(override val topic: String, override val data: ByteSequence, override val uniqueMessageId: DeduplicationId, override val senderUUID: String?, override val additionalHeaders: Map) : Message { + class NodeClientMessage(override val topic: String, override val data: ByteSequence, override val uniqueMessageId: DeduplicationId, override val senderUUID: String?, override val additionalHeaders: Map) : Message { override val debugTimestamp: Instant = Instant.now() override fun toString() = "$topic#${String(data.bytes)}" } @@ -158,6 +160,8 @@ class P2PMessagingClient(val config: NodeConfiguration, data class HandlerRegistration(val topic: String, val callback: Any) : MessageHandlerRegistration override val myAddress: SingleMessageRecipient = NodeAddress(myIdentity, advertisedAddress) + override val ourSenderUUID = UUID.randomUUID().toString() + private val messageRedeliveryDelaySeconds = config.p2pMessagingRetry.messageRedeliveryDelay.seconds private val state = ThreadBox(InnerState()) private val knownQueues = Collections.newSetFromMap(ConcurrentHashMap()) @@ -227,7 +231,7 @@ class P2PMessagingClient(val config: NodeConfiguration, producer!!, versionInfo, this@P2PMessagingClient, - ourSenderUUID = deduplicator.ourSenderUUID + ourSenderUUID = ourSenderUUID ) registerBridgeControl(bridgeSession!!, inboxes.toList()) @@ -435,18 +439,23 @@ class P2PMessagingClient(val config: NodeConfiguration, } } - inner class MessageDeduplicationHandler(val artemisMessage: ClientMessage, val cordaMessage: ReceivedMessage) : DeduplicationHandler { + private inner class MessageDeduplicationHandler(val artemisMessage: ClientMessage, override val receivedMessage: ReceivedMessage) : DeduplicationHandler, ExternalEvent.ExternalMessageEvent { + override val externalCause: ExternalEvent + get() = this + override val deduplicationHandler: MessageDeduplicationHandler + get() = this + override fun insideDatabaseTransaction() { - deduplicator.persistDeduplicationId(cordaMessage.uniqueMessageId) + deduplicator.persistDeduplicationId(receivedMessage.uniqueMessageId) } override fun afterDatabaseTransaction() { - deduplicator.signalMessageProcessFinish(cordaMessage.uniqueMessageId) + deduplicator.signalMessageProcessFinish(receivedMessage.uniqueMessageId) messagingExecutor!!.acknowledge(artemisMessage) } override fun toString(): String { - return "${javaClass.simpleName}(${cordaMessage.uniqueMessageId})" + return "${javaClass.simpleName}(${receivedMessage.uniqueMessageId})" } } @@ -610,8 +619,8 @@ class P2PMessagingClient(val config: NodeConfiguration, handlers.remove(registration.topic) } - override fun createMessage(topic: String, data: ByteArray, deduplicationId: DeduplicationId, additionalHeaders: Map): Message { - return NodeClientMessage(topic, OpaqueBytes(data), deduplicationId, deduplicator.ourSenderUUID, additionalHeaders) + override fun createMessage(topic: String, data: ByteArray, deduplicationId: SenderDeduplicationId, additionalHeaders: Map): Message { + return NodeClientMessage(topic, OpaqueBytes(data), deduplicationId.deduplicationId, deduplicationId.senderUUID, additionalHeaders) } override fun getAddressOfParty(partyInfo: PartyInfo): MessageRecipients { diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt index be1e01b1a5..73105d598e 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt @@ -10,9 +10,9 @@ import net.corda.nodeapi.internal.persistence.currentDBSession import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.io.Serializable import java.util.* import java.util.stream.Stream -import java.io.Serializable import javax.persistence.Column import javax.persistence.Entity import javax.persistence.Id @@ -53,6 +53,11 @@ class DBCheckpointStorage : CheckpointStorage { return session.createQuery(delete).executeUpdate() > 0 } + override fun getCheckpoint(id: StateMachineRunId): SerializedBytes? { + val bytes = currentDBSession().get(DBCheckpoint::class.java, id.uuid.toString())?.checkpoint ?: return null + return SerializedBytes(bytes) + } + override fun getAllCheckpoints(): Stream>> { val session = currentDBSession() val criteriaQuery = session.criteriaBuilder.createQuery(DBCheckpoint::class.java) diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt index 3271208e95..05b99bc25b 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt @@ -21,7 +21,6 @@ import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import rx.Observable import rx.subjects.PublishSubject import java.io.Serializable -import java.util.* import javax.persistence.* // cache value type to just store the immutable bits of a signed transaction plus conversion helpers @@ -71,11 +70,11 @@ class DBTransactionStorage(cacheSizeBytes: Long) : WritableTransactionStorage, S // to the memory pressure at all here. private const val transactionSignatureOverheadEstimate = 1024 - private fun weighTx(tx: Optional): Int { - if (!tx.isPresent) { + private fun weighTx(tx: AppendOnlyPersistentMapBase.Transactional): Int { + val actTx = tx.valueWithoutIsolation + if (actTx == null) { return 0 } - val actTx = tx.get() return actTx.second.sumBy { it.size + transactionSignatureOverheadEstimate } + actTx.first.size } } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/Action.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/Action.kt index 07d4ceb866..a48d03be7c 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/Action.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/Action.kt @@ -24,7 +24,7 @@ sealed class Action { data class SendInitial( val party: Party, val initialise: InitialSessionMessage, - val deduplicationId: DeduplicationId + val deduplicationId: SenderDeduplicationId ) : Action() /** @@ -33,7 +33,7 @@ sealed class Action { data class SendExisting( val peerParty: Party, val message: ExistingSessionMessage, - val deduplicationId: DeduplicationId + val deduplicationId: SenderDeduplicationId ) : Action() /** @@ -62,7 +62,8 @@ sealed class Action { */ data class PropagateErrors( val errorMessages: List, - val sessions: List + val sessions: List, + val senderUUID: String? ) : Action() /** @@ -129,6 +130,11 @@ sealed class Action { * Release soft locks associated with given ID (currently the flow ID). */ data class ReleaseSoftLocks(val uuid: UUID?) : Action() + + /** + * Retry a flow from the last checkpoint, or if there is no checkpoint, restart the flow with the same invocation details. + */ + data class RetryFlowFromSafePoint(val currentState: StateMachineState) : Action() } /** diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/ActionExecutorImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/ActionExecutorImpl.kt index 683cd720e3..8aa57d69ef 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/ActionExecutorImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/ActionExecutorImpl.kt @@ -73,6 +73,7 @@ class ActionExecutorImpl( is Action.CommitTransaction -> executeCommitTransaction() is Action.ExecuteAsyncOperation -> executeAsyncOperation(fiber, action) is Action.ReleaseSoftLocks -> executeReleaseSoftLocks(action) + is Action.RetryFlowFromSafePoint -> executeRetryFlowFromSafePoint(action) } } @@ -125,7 +126,7 @@ class ActionExecutorImpl( @Suspendable private fun executePropagateErrors(action: Action.PropagateErrors) { action.errorMessages.forEach { (exception) -> - log.debug("Propagating error", exception) + log.warn("Propagating error", exception) } for (sessionState in action.sessions) { // We cannot propagate if the session isn't live. @@ -137,7 +138,7 @@ class ActionExecutorImpl( val sinkSessionId = sessionState.initiatedState.peerSinkSessionId val existingMessage = ExistingSessionMessage(sinkSessionId, errorMessage) val deduplicationId = DeduplicationId.createForError(errorMessage.errorId, sinkSessionId) - flowMessaging.sendSessionMessage(sessionState.peerParty, existingMessage, deduplicationId) + flowMessaging.sendSessionMessage(sessionState.peerParty, existingMessage, SenderDeduplicationId(deduplicationId, action.senderUUID)) } } } @@ -226,6 +227,10 @@ class ActionExecutorImpl( ) } + private fun executeRetryFlowFromSafePoint(action: Action.RetryFlowFromSafePoint) { + stateMachineManager.retryFlowFromSafePoint(action.currentState) + } + private fun serializeCheckpoint(checkpoint: Checkpoint): SerializedBytes { return checkpoint.serialize(context = checkpointSerializationContext) } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/DeduplicationId.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/DeduplicationId.kt index 7e853dc5a8..b6d2bbb89b 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/DeduplicationId.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/DeduplicationId.kt @@ -45,3 +45,9 @@ data class DeduplicationId(val toString: String) { } } } + +/** + * Represents the deduplication ID of a flow message, and the sender identifier for the flow doing the sending. The identifier might be + * null if the flow is trying to replay messages and doesn't want an optimisation to ignore the deduplication ID. + */ +data class SenderDeduplicationId(val deduplicationId: DeduplicationId, val senderUUID: String?) \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/Event.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/Event.kt index 921795038c..c750a05d1e 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/Event.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/Event.kt @@ -31,9 +31,9 @@ sealed class Event { */ data class DeliverSessionMessage( val sessionMessage: ExistingSessionMessage, - val deduplicationHandler: DeduplicationHandler, + override val deduplicationHandler: DeduplicationHandler, val sender: Party - ) : Event() + ) : Event(), GeneratedByExternalEvent /** * Signal that an error has happened. This may be due to an uncaught exception in the flow or some external error. @@ -133,4 +133,19 @@ sealed class Event { * @param returnValue the result of the operation. */ data class AsyncOperationCompletion(val returnValue: Any?) : Event() + + /** + * Retry a flow from the last checkpoint, or if there is no checkpoint, restart the flow with the same invocation details. + */ + object RetryFlowFromSafePoint : Event() { + override fun toString() = "RetryFlowFromSafePoint" + } + + /** + * Indicates that an event was generated by an external event and that external event needs to be replayed if we retry the flow, + * even if it has not yet been processed and placed on the pending de-duplication handlers list. + */ + interface GeneratedByExternalEvent { + val deduplicationHandler: DeduplicationHandler + } } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowHospital.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowHospital.kt index bcd60557df..68ea53724e 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowHospital.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowHospital.kt @@ -9,10 +9,15 @@ interface FlowHospital { /** * The flow running in [flowFiber] has errored. */ - fun flowErrored(flowFiber: FlowFiber) + fun flowErrored(flowFiber: FlowFiber, currentState: StateMachineState, errors: List) /** * The flow running in [flowFiber] has cleaned, possibly as a result of a flow hospital resume. */ fun flowCleaned(flowFiber: FlowFiber) + + /** + * The flow has been removed from the state machine. + */ + fun flowRemoved(flowFiber: FlowFiber) } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowMessaging.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowMessaging.kt index 0f47417eb8..1ca7490e7d 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowMessaging.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowMessaging.kt @@ -24,7 +24,7 @@ interface FlowMessaging { * listen on the send acknowledgement. */ @Suspendable - fun sendSessionMessage(party: Party, message: SessionMessage, deduplicationId: DeduplicationId) + fun sendSessionMessage(party: Party, message: SessionMessage, deduplicationId: SenderDeduplicationId) /** * Start the messaging using the [onMessage] message handler. @@ -49,7 +49,7 @@ class FlowMessagingImpl(val serviceHub: ServiceHubInternal): FlowMessaging { } @Suspendable - override fun sendSessionMessage(party: Party, message: SessionMessage, deduplicationId: DeduplicationId) { + override fun sendSessionMessage(party: Party, message: SessionMessage, deduplicationId: SenderDeduplicationId) { log.trace { "Sending message $deduplicationId $message to party $party" } val networkMessage = serviceHub.networkService.createMessage(sessionTopic, serializeSessionMessage(message).bytes, deduplicationId, message.additionalHeaders(party)) val partyInfo = serviceHub.networkMapCache.getPartyInfo(party) ?: throw IllegalArgumentException("Don't know about $party") diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt index fe60503096..3d2b460152 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt @@ -47,6 +47,8 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, fun currentStateMachine(): FlowStateMachineImpl<*>? = Strand.currentStrand() as? FlowStateMachineImpl<*> private val log: Logger = LoggerFactory.getLogger("net.corda.flow") + + private val SERIALIZER_BLOCKER = Fiber::class.java.getDeclaredField("SERIALIZER_BLOCKER").apply { isAccessible = true }.get(null) } override val serviceHub get() = getTransientField(TransientValues::serviceHub) @@ -65,6 +67,14 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, internal var transientValues: TransientReference? = null internal var transientState: TransientReference? = null + /** + * What sender identifier to put on messages sent by this flow. This will either be the identifier for the current + * state machine manager / messaging client, or null to indicate this flow is restored from a checkpoint and + * the de-duplication of messages it sends should not be optimised since this could be unreliable. + */ + override val ourSenderUUID: String? + get() = transientState?.value?.senderUUID + private fun getTransientField(field: KProperty1): A { val suppliedValues = transientValues ?: throw IllegalStateException("${field.name} wasn't supplied!") return field.get(suppliedValues.value) @@ -168,6 +178,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, fun setLoggingContext() { context.pushToLoggingContext() MDC.put("flow-id", id.uuid.toString()) + MDC.put("fiber-id", this.getId().toString()) } @Suspendable @@ -185,7 +196,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, suspend(FlowIORequest.WaitForSessionConfirmations, maySkipCheckpoint = true) Try.Success(result) } catch (throwable: Throwable) { - logger.warn("Flow threw exception", throwable) + logger.info("Flow threw exception... sending to flow hospital", throwable) Try.Failure(throwable) } val softLocksId = if (hasSoftLockedStates) logic.runId.uuid else null @@ -325,7 +336,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, isDbTransactionOpenOnExit = false ) require(continuation == FlowContinuation.ProcessEvents) - Fiber.unparkDeserialized(this, scheduler) + unpark(SERIALIZER_BLOCKER) } setLoggingContext() return uncheckedCast(processEventsUntilFlowIsResumed( diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/PropagatingFlowHospital.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/PropagatingFlowHospital.kt index a31656db5d..49f5fdb167 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/PropagatingFlowHospital.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/PropagatingFlowHospital.kt @@ -9,12 +9,17 @@ import net.corda.core.utilities.loggerFor object PropagatingFlowHospital : FlowHospital { private val log = loggerFor() - override fun flowErrored(flowFiber: FlowFiber) { - log.debug { "Flow ${flowFiber.id} dirtied ${flowFiber.snapshot().checkpoint.errorState}" } + override fun flowErrored(flowFiber: FlowFiber, currentState: StateMachineState, errors: List) { + log.debug { "Flow ${flowFiber.id} in state $currentState encountered error" } flowFiber.scheduleEvent(Event.StartErrorPropagation) + for ((index, error) in errors.withIndex()) { + log.warn("Flow ${flowFiber.id} is propagating error [$index] ", error) + } } override fun flowCleaned(flowFiber: FlowFiber) { throw IllegalStateException("Flow ${flowFiber.id} cleaned after error propagation triggered") } + + override fun flowRemoved(flowFiber: FlowFiber) {} } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt index 1fca4d4501..a0d492f50e 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt @@ -46,13 +46,15 @@ import java.security.SecureRandom import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ExecutorService +import java.util.concurrent.locks.ReentrantLock import javax.annotation.concurrent.ThreadSafe import kotlin.collections.ArrayList +import kotlin.concurrent.withLock import kotlin.streams.toList /** * The StateMachineManagerImpl will always invoke the flow fibers on the given [AffinityExecutor], regardless of which - * thread actually starts them via [startFlow]. + * thread actually starts them via [deliverExternalEvent]. */ @ThreadSafe class SingleThreadedStateMachineManager( @@ -90,6 +92,7 @@ class SingleThreadedStateMachineManager( private val flowMessaging: FlowMessaging = FlowMessagingImpl(serviceHub) private val fiberDeserializationChecker = if (serviceHub.configuration.shouldCheckCheckpoints()) FiberDeserializationChecker() else null private val transitionExecutor = makeTransitionExecutor() + private val ourSenderUUID = serviceHub.networkService.ourSenderUUID private var checkpointSerializationContext: SerializationContext? = null private var tokenizableServices: List? = null @@ -128,7 +131,7 @@ class SingleThreadedStateMachineManager( resumeRestoredFlows(fibers) flowMessaging.start { receivedMessage, deduplicationHandler -> executor.execute { - onSessionMessage(receivedMessage, deduplicationHandler) + deliverExternalEvent(deduplicationHandler.externalCause) } } } @@ -176,7 +179,7 @@ class SingleThreadedStateMachineManager( } } - override fun startFlow( + private fun startFlow( flowLogic: FlowLogic, context: InvocationContext, ourIdentity: Party?, @@ -310,7 +313,73 @@ class SingleThreadedStateMachineManager( } } - private fun onSessionMessage(message: ReceivedMessage, deduplicationHandler: DeduplicationHandler) { + override fun retryFlowFromSafePoint(currentState: StateMachineState) { + // Get set of external events + val flowId = currentState.flowLogic.runId + val oldFlowLeftOver = mutex.locked { flows[flowId] }?.fiber?.transientValues?.value?.eventQueue + if (oldFlowLeftOver == null) { + logger.error("Unable to find flow for flow $flowId. Something is very wrong. The flow will not retry.") + return + } + val flow = if (currentState.isAnyCheckpointPersisted) { + val serializedCheckpoint = checkpointStorage.getCheckpoint(flowId) + if (serializedCheckpoint == null) { + logger.error("Unable to find database checkpoint for flow $flowId. Something is very wrong. The flow will not retry.") + return + } + val checkpoint = deserializeCheckpoint(serializedCheckpoint) + if (checkpoint == null) { + logger.error("Unable to deserialize database checkpoint for flow $flowId. Something is very wrong. The flow will not retry.") + return + } + // Resurrect flow + createFlowFromCheckpoint( + id = flowId, + checkpoint = checkpoint, + initialDeduplicationHandler = null, + isAnyCheckpointPersisted = true, + isStartIdempotent = false, + senderUUID = null + ) + } else { + // Just flow initiation message + null + } + externalEventMutex.withLock { + if (flow != null) addAndStartFlow(flowId, flow) + // Deliver all the external events from the old flow instance. + val unprocessedExternalEvents = mutableListOf() + do { + val event = oldFlowLeftOver.tryReceive() + if (event is Event.GeneratedByExternalEvent) { + unprocessedExternalEvents += event.deduplicationHandler.externalCause + } + } while (event != null) + val externalEvents = currentState.pendingDeduplicationHandlers.map { it.externalCause } + unprocessedExternalEvents + for (externalEvent in externalEvents) { + deliverExternalEvent(externalEvent) + } + } + } + + private val externalEventMutex = ReentrantLock() + override fun deliverExternalEvent(event: ExternalEvent) { + externalEventMutex.withLock { + when (event) { + is ExternalEvent.ExternalMessageEvent -> onSessionMessage(event) + is ExternalEvent.ExternalStartFlowEvent<*> -> onExternalStartFlow(event) + } + } + } + + private fun onExternalStartFlow(event: ExternalEvent.ExternalStartFlowEvent) { + val future = startFlow(event.flowLogic, event.context, ourIdentity = null, deduplicationHandler = event.deduplicationHandler) + event.wireUpFuture(future) + } + + private fun onSessionMessage(event: ExternalEvent.ExternalMessageEvent) { + val message: ReceivedMessage = event.receivedMessage + val deduplicationHandler: DeduplicationHandler = event.deduplicationHandler val peer = message.peer val sessionMessage = try { message.data.deserialize() @@ -384,7 +453,7 @@ class SingleThreadedStateMachineManager( } if (replyError != null) { - flowMessaging.sendSessionMessage(sender, replyError, DeduplicationId.createRandom(secureRandom)) + flowMessaging.sendSessionMessage(sender, replyError, SenderDeduplicationId(DeduplicationId.createRandom(secureRandom), ourSenderUUID)) deduplicationHandler.afterDatabaseTransaction() } } @@ -458,7 +527,8 @@ class SingleThreadedStateMachineManager( isAnyCheckpointPersisted = false, isStartIdempotent = isStartIdempotent, isRemoved = false, - flowLogic = flowLogic + flowLogic = flowLogic, + senderUUID = ourSenderUUID ) flowStateMachineImpl.transientState = TransientReference(initialState) mutex.locked { @@ -493,7 +563,7 @@ class SingleThreadedStateMachineManager( private fun createTransientValues(id: StateMachineRunId, resultFuture: CordaFuture): FlowStateMachineImpl.TransientValues { return FlowStateMachineImpl.TransientValues( - eventQueue = Channels.newChannel(stateMachineConfiguration.eventQueueSize, Channels.OverflowPolicy.BLOCK), + eventQueue = Channels.newChannel(-1, Channels.OverflowPolicy.BLOCK), resultFuture = resultFuture, database = database, transitionExecutor = transitionExecutor, @@ -509,7 +579,8 @@ class SingleThreadedStateMachineManager( checkpoint: Checkpoint, isAnyCheckpointPersisted: Boolean, isStartIdempotent: Boolean, - initialDeduplicationHandler: DeduplicationHandler? + initialDeduplicationHandler: DeduplicationHandler?, + senderUUID: String? = ourSenderUUID ): Flow { val flowState = checkpoint.flowState val resultFuture = openFuture() @@ -524,7 +595,8 @@ class SingleThreadedStateMachineManager( isAnyCheckpointPersisted = isAnyCheckpointPersisted, isStartIdempotent = isStartIdempotent, isRemoved = false, - flowLogic = logic + flowLogic = logic, + senderUUID = senderUUID ) val fiber = FlowStateMachineImpl(id, logic, scheduler) fiber.transientValues = TransientReference(createTransientValues(id, resultFuture)) @@ -542,7 +614,8 @@ class SingleThreadedStateMachineManager( isAnyCheckpointPersisted = isAnyCheckpointPersisted, isStartIdempotent = isStartIdempotent, isRemoved = false, - flowLogic = fiber.logic + flowLogic = fiber.logic, + senderUUID = senderUUID ) fiber.transientValues = TransientReference(createTransientValues(id, resultFuture)) fiber.transientState = TransientReference(state) @@ -566,9 +639,13 @@ class SingleThreadedStateMachineManager( startedFutures[id]?.setException(IllegalStateException("Will not start flow as SMM is stopping")) logger.trace("Not resuming as SMM is stopping.") } else { - incrementLiveFibers() - unfinishedFibers.countUp() - flows[id] = flow + val oldFlow = flows.put(id, flow) + if (oldFlow == null) { + incrementLiveFibers() + unfinishedFibers.countUp() + } else { + oldFlow.resultFuture.captureLater(flow.resultFuture) + } flow.fiber.scheduleEvent(Event.DoRemainingWork) when (checkpoint.flowState) { is FlowState.Unstarted -> { @@ -604,7 +681,7 @@ class SingleThreadedStateMachineManager( private fun makeTransitionExecutor(): TransitionExecutor { val interceptors = ArrayList() - interceptors.add { HospitalisingInterceptor(PropagatingFlowHospital, it) } + interceptors.add { HospitalisingInterceptor(StaffedFlowHospital, it) } if (serviceHub.configuration.devMode) { interceptors.add { DumpHistoryOnErrorInterceptor(it) } } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StaffedFlowHospital.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StaffedFlowHospital.kt new file mode 100644 index 0000000000..b0fb7943f0 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StaffedFlowHospital.kt @@ -0,0 +1,127 @@ +package net.corda.node.services.statemachine + +import net.corda.core.flows.StateMachineRunId +import net.corda.core.utilities.loggerFor +import java.sql.SQLException +import java.time.Instant +import java.util.concurrent.ConcurrentHashMap + +/** + * This hospital consults "staff" to see if they can automatically diagnose and treat flows. + */ +object StaffedFlowHospital : FlowHospital { + private val log = loggerFor() + + private val staff = listOf(DeadlockNurse, DuplicateInsertSpecialist) + + private val patients = ConcurrentHashMap() + + val numberOfPatients = patients.size + + class MedicalHistory { + val records: MutableList = mutableListOf() + + sealed class Record(val suspendCount: Int) { + class Admitted(val at: Instant, suspendCount: Int) : Record(suspendCount) { + override fun toString() = "Admitted(at=$at, suspendCount=$suspendCount)" + } + + class Discharged(val at: Instant, suspendCount: Int, val by: Staff, val error: Throwable) : Record(suspendCount) { + override fun toString() = "Discharged(at=$at, suspendCount=$suspendCount, by=$by)" + } + } + + fun notDischargedForTheSameThingMoreThan(max: Int, by: Staff): Boolean { + val lastAdmittanceSuspendCount = (records.last() as MedicalHistory.Record.Admitted).suspendCount + return records.filterIsInstance(MedicalHistory.Record.Discharged::class.java).filter { it.by == by && it.suspendCount == lastAdmittanceSuspendCount }.count() <= max + } + + override fun toString(): String = "${this.javaClass.simpleName}(records = $records)" + } + + override fun flowErrored(flowFiber: FlowFiber, currentState: StateMachineState, errors: List) { + log.info("Flow ${flowFiber.id} admitted to hospital in state $currentState") + val medicalHistory = patients.computeIfAbsent(flowFiber.id) { MedicalHistory() } + medicalHistory.records += MedicalHistory.Record.Admitted(Instant.now(), currentState.checkpoint.numberOfSuspends) + for ((index, error) in errors.withIndex()) { + log.info("Flow ${flowFiber.id} has error [$index]", error) + if (!errorIsDischarged(flowFiber, currentState, error, medicalHistory)) { + // If any error isn't discharged, then we propagate. + log.warn("Flow ${flowFiber.id} error was not discharged, propagating.") + flowFiber.scheduleEvent(Event.StartErrorPropagation) + return + } + } + // If all are discharged, retry. + flowFiber.scheduleEvent(Event.RetryFlowFromSafePoint) + } + + private fun errorIsDischarged(flowFiber: FlowFiber, currentState: StateMachineState, error: Throwable, medicalHistory: MedicalHistory): Boolean { + for (staffMember in staff) { + val diagnosis = staffMember.consult(flowFiber, currentState, error, medicalHistory) + if (diagnosis == Diagnosis.DISCHARGE) { + medicalHistory.records += MedicalHistory.Record.Discharged(Instant.now(), currentState.checkpoint.numberOfSuspends, staffMember, error) + log.info("Flow ${flowFiber.id} error discharged from hospital by $staffMember") + return true + } + } + return false + } + + // It's okay for flows to be cleaned... we fix them now! + override fun flowCleaned(flowFiber: FlowFiber) {} + + override fun flowRemoved(flowFiber: FlowFiber) { + patients.remove(flowFiber.id) + } + + enum class Diagnosis { + /** + * Retry from last safe point. + */ + DISCHARGE, + /** + * Please try another member of staff. + */ + NOT_MY_SPECIALTY + } + + interface Staff { + fun consult(flowFiber: FlowFiber, currentState: StateMachineState, newError: Throwable, history: MedicalHistory): Diagnosis + } + + /** + * SQL Deadlock detection. + */ + object DeadlockNurse : Staff { + override fun consult(flowFiber: FlowFiber, currentState: StateMachineState, newError: Throwable, history: MedicalHistory): Diagnosis { + return if (mentionsDeadlock(newError)) { + Diagnosis.DISCHARGE + } else { + Diagnosis.NOT_MY_SPECIALTY + } + } + + private fun mentionsDeadlock(exception: Throwable?): Boolean { + return exception != null && (exception is SQLException && ((exception.message?.toLowerCase()?.contains("deadlock") + ?: false)) || mentionsDeadlock(exception.cause)) + } + } + + /** + * Primary key violation detection for duplicate inserts. Will detect other constraint violations too. + */ + object DuplicateInsertSpecialist : Staff { + override fun consult(flowFiber: FlowFiber, currentState: StateMachineState, newError: Throwable, history: MedicalHistory): Diagnosis { + return if (mentionsConstraintViolation(newError) && history.notDischargedForTheSameThingMoreThan(3, this)) { + Diagnosis.DISCHARGE + } else { + Diagnosis.NOT_MY_SPECIALTY + } + } + + private fun mentionsConstraintViolation(exception: Throwable?): Boolean { + return exception != null && (exception is org.hibernate.exception.ConstraintViolationException || mentionsConstraintViolation(exception.cause)) + } + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt index 087408c265..084452aa25 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt @@ -4,11 +4,11 @@ import net.corda.core.concurrent.CordaFuture import net.corda.core.context.InvocationContext 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.messaging.DataFeed import net.corda.core.utilities.Try import net.corda.node.services.messaging.DeduplicationHandler +import net.corda.node.services.messaging.ReceivedMessage import rx.Observable /** @@ -40,21 +40,6 @@ interface StateMachineManager { */ fun stop(allowedUnsuspendedFiberCount: Int) - /** - * Starts a new flow. - * - * @param flowLogic The flow's code. - * @param context The context of the flow. - * @param ourIdentity The identity to use for the flow. - * @param deduplicationHandler Allows exactly-once start of the flow, see [DeduplicationHandler]. - */ - fun startFlow( - flowLogic: FlowLogic, - context: InvocationContext, - ourIdentity: Party?, - deduplicationHandler: DeduplicationHandler? - ): CordaFuture> - /** * Represents an addition/removal of a state machine. */ @@ -91,6 +76,12 @@ interface StateMachineManager { * @return whether the flow existed and was killed. */ fun killFlow(id: StateMachineRunId): Boolean + + /** + * Deliver an external event to the state machine. Such an event might be a new P2P message, or a request to start a flow. + * The event may be replayed if a flow fails and attempts to retry. + */ + fun deliverExternalEvent(event: ExternalEvent) } // These must be idempotent! A later failure in the state transition may error the flow state, and a replay may call @@ -100,4 +91,38 @@ interface StateMachineManagerInternal { fun addSessionBinding(flowId: StateMachineRunId, sessionId: SessionId) fun removeSessionBindings(sessionIds: Set) fun removeFlow(flowId: StateMachineRunId, removalReason: FlowRemovalReason, lastState: StateMachineState) + fun retryFlowFromSafePoint(currentState: StateMachineState) +} + +/** + * Represents an external event that can be injected into the state machine and that might need to be replayed if + * a flow retries. They always have de-duplication handlers to assist with the at-most once logic where required. + */ +interface ExternalEvent { + val deduplicationHandler: DeduplicationHandler + + /** + * An external P2P message event. + */ + interface ExternalMessageEvent : ExternalEvent { + val receivedMessage: ReceivedMessage + } + + /** + * An external request to start a flow, from the scheduler for example. + */ + interface ExternalStartFlowEvent : ExternalEvent { + val flowLogic: FlowLogic + val context: InvocationContext + + /** + * A callback for the state machine to pass back the [Future] associated with the flow start to the submitter. + */ + fun wireUpFuture(flowFuture: CordaFuture>) + + /** + * The future representing the flow start, passed back from the state machine to the submitter of this event. + */ + val future: CordaFuture> + } } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineState.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineState.kt index b2d572b475..9ff1edd3ca 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineState.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineState.kt @@ -26,6 +26,7 @@ import net.corda.node.services.messaging.DeduplicationHandler * possible. * @param isRemoved true if the flow has been removed from the state machine manager. This is used to avoid any further * work. + * @param senderUUID the identifier of the sending state machine or null if this flow is resumed from a checkpoint so that it does not participate in de-duplication high-water-marking. */ // TODO perhaps add a read-only environment to the state machine for things that don't change over time? // TODO evaluate persistent datastructure libraries to replace the inefficient copying we currently do. @@ -37,7 +38,8 @@ data class StateMachineState( val isTransactionTracked: Boolean, val isAnyCheckpointPersisted: Boolean, val isStartIdempotent: Boolean, - val isRemoved: Boolean + val isRemoved: Boolean, + val senderUUID: String? ) /** diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/interceptors/DumpHistoryOnErrorInterceptor.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/interceptors/DumpHistoryOnErrorInterceptor.kt index 23ee4a2d9f..2e57e0bb14 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/interceptors/DumpHistoryOnErrorInterceptor.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/interceptors/DumpHistoryOnErrorInterceptor.kt @@ -34,7 +34,8 @@ class DumpHistoryOnErrorInterceptor(val delegate: TransitionExecutor) : Transiti (record ?: ArrayList()).apply { add(transitionRecord) } } - if (nextState.checkpoint.errorState is ErrorState.Errored) { + // Just if we decide to propagate, and not if just on the way to the hospital. + if (nextState.checkpoint.errorState is ErrorState.Errored && nextState.checkpoint.errorState.propagating) { log.warn("Flow ${fiber.id} errored, dumping all transitions:\n${record!!.joinToString("\n")}") for (error in nextState.checkpoint.errorState.errors) { log.warn("Flow ${fiber.id} error", error.exception) diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/interceptors/HospitalisingInterceptor.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/interceptors/HospitalisingInterceptor.kt index 8ed5f67b95..f463017d62 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/interceptors/HospitalisingInterceptor.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/interceptors/HospitalisingInterceptor.kt @@ -26,20 +26,23 @@ class HospitalisingInterceptor( actionExecutor: ActionExecutor ): Pair { val (continuation, nextState) = delegate.executeTransition(fiber, previousState, event, transition, actionExecutor) - when (nextState.checkpoint.errorState) { - ErrorState.Clean -> { - if (hospitalisedFlows.remove(fiber.id) != null) { - flowHospital.flowCleaned(fiber) + + when (nextState.checkpoint.errorState) { + is ErrorState.Clean -> { + if (hospitalisedFlows.remove(fiber.id) != null) { + flowHospital.flowCleaned(fiber) + } + } + is ErrorState.Errored -> { + val exceptionsToHandle = nextState.checkpoint.errorState.errors.map { it.exception } + if (hospitalisedFlows.putIfAbsent(fiber.id, fiber) == null) { + flowHospital.flowErrored(fiber, previousState, exceptionsToHandle) + } } } - is ErrorState.Errored -> { - if (hospitalisedFlows.putIfAbsent(fiber.id, fiber) == null) { - flowHospital.flowErrored(fiber) - } - } - } if (nextState.isRemoved) { hospitalisedFlows.remove(fiber.id) + flowHospital.flowRemoved(fiber) } return Pair(continuation, nextState) } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/DeliverSessionMessageTransition.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/DeliverSessionMessageTransition.kt index 87fcb49ca6..11d9d771cd 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/DeliverSessionMessageTransition.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/DeliverSessionMessageTransition.kt @@ -46,9 +46,6 @@ class DeliverSessionMessageTransition( is EndSessionMessage -> endMessageTransition() } } - if (!isErrored()) { - persistCheckpoint() - } // Schedule a DoRemainingWork to check whether the flow needs to be woken up. actions.add(Action.ScheduleEvent(Event.DoRemainingWork)) FlowContinuation.ProcessEvents @@ -73,7 +70,7 @@ class DeliverSessionMessageTransition( // Send messages that were buffered pending confirmation of session. val sendActions = sessionState.bufferedMessages.map { (deduplicationId, bufferedMessage) -> val existingMessage = ExistingSessionMessage(message.initiatedSessionId, bufferedMessage) - Action.SendExisting(initiatedSession.peerParty, existingMessage, deduplicationId) + Action.SendExisting(initiatedSession.peerParty, existingMessage, SenderDeduplicationId(deduplicationId, startingState.senderUUID)) } actions.addAll(sendActions) currentState = currentState.copy(checkpoint = newCheckpoint) @@ -146,21 +143,6 @@ class DeliverSessionMessageTransition( } } - private fun TransitionBuilder.persistCheckpoint() { - // We persist the message as soon as it arrives. - actions.addAll(arrayOf( - Action.CreateTransaction, - Action.PersistCheckpoint(context.id, currentState.checkpoint), - Action.PersistDeduplicationFacts(currentState.pendingDeduplicationHandlers), - Action.CommitTransaction, - Action.AcknowledgeMessages(currentState.pendingDeduplicationHandlers) - )) - currentState = currentState.copy( - pendingDeduplicationHandlers = emptyList(), - isAnyCheckpointPersisted = true - ) - } - private fun TransitionBuilder.endMessageTransition() { val sessionId = event.sessionMessage.recipientSessionId val sessions = currentState.checkpoint.sessions diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/ErrorFlowTransition.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/ErrorFlowTransition.kt index 97cb3be926..89b1b00a29 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/ErrorFlowTransition.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/ErrorFlowTransition.kt @@ -46,7 +46,7 @@ class ErrorFlowTransition( sessions = newSessions ) currentState = currentState.copy(checkpoint = newCheckpoint) - actions.add(Action.PropagateErrors(errorMessages, initiatedSessions)) + actions.add(Action.PropagateErrors(errorMessages, initiatedSessions, startingState.senderUUID)) } // If we're errored but not propagating keep processing events. diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/StartedFlowTransition.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/StartedFlowTransition.kt index 28d1d04486..f2a09939b4 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/StartedFlowTransition.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/StartedFlowTransition.kt @@ -216,7 +216,7 @@ class StartedFlowTransition( } val deduplicationId = DeduplicationId.createForNormal(checkpoint, index++) val initialMessage = createInitialSessionMessage(sessionState.initiatingSubFlow, sourceSessionId, null) - actions.add(Action.SendInitial(sessionState.party, initialMessage, deduplicationId)) + actions.add(Action.SendInitial(sessionState.party, initialMessage, SenderDeduplicationId(deduplicationId, startingState.senderUUID))) newSessions[sourceSessionId] = SessionState.Initiating( bufferedMessages = emptyList(), rejectionError = null @@ -253,7 +253,7 @@ class StartedFlowTransition( when (existingSessionState) { is SessionState.Uninitiated -> { val initialMessage = createInitialSessionMessage(existingSessionState.initiatingSubFlow, sourceSessionId, message) - actions.add(Action.SendInitial(existingSessionState.party, initialMessage, deduplicationId)) + actions.add(Action.SendInitial(existingSessionState.party, initialMessage, SenderDeduplicationId(deduplicationId, startingState.senderUUID))) newSessions[sourceSessionId] = SessionState.Initiating( bufferedMessages = emptyList(), rejectionError = null @@ -270,7 +270,7 @@ class StartedFlowTransition( is InitiatedSessionState.Live -> { val sinkSessionId = existingSessionState.initiatedState.peerSinkSessionId val existingMessage = ExistingSessionMessage(sinkSessionId, sessionMessage) - actions.add(Action.SendExisting(existingSessionState.peerParty, existingMessage, deduplicationId)) + actions.add(Action.SendExisting(existingSessionState.peerParty, existingMessage, SenderDeduplicationId(deduplicationId, startingState.senderUUID))) Unit } InitiatedSessionState.Ended -> { diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/TopLevelTransition.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/TopLevelTransition.kt index a0cdc389f2..5fc4133e98 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/TopLevelTransition.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/TopLevelTransition.kt @@ -18,18 +18,19 @@ class TopLevelTransition( ) : Transition { override fun transition(): TransitionResult { return when (event) { - is Event.DoRemainingWork -> DoRemainingWorkTransition(context, startingState).transition() - is Event.DeliverSessionMessage -> DeliverSessionMessageTransition(context, startingState, event).transition() - is Event.Error -> errorTransition(event) - is Event.TransactionCommitted -> transactionCommittedTransition(event) - is Event.SoftShutdown -> softShutdownTransition() - is Event.StartErrorPropagation -> startErrorPropagationTransition() - is Event.EnterSubFlow -> enterSubFlowTransition(event) - is Event.LeaveSubFlow -> leaveSubFlowTransition() - is Event.Suspend -> suspendTransition(event) - is Event.FlowFinish -> flowFinishTransition(event) - is Event.InitiateFlow -> initiateFlowTransition(event) - is Event.AsyncOperationCompletion -> asyncOperationCompletionTransition(event) + is Event.DoRemainingWork -> DoRemainingWorkTransition(context, startingState).transition() + is Event.DeliverSessionMessage -> DeliverSessionMessageTransition(context, startingState, event).transition() + is Event.Error -> errorTransition(event) + is Event.TransactionCommitted -> transactionCommittedTransition(event) + is Event.SoftShutdown -> softShutdownTransition() + is Event.StartErrorPropagation -> startErrorPropagationTransition() + is Event.EnterSubFlow -> enterSubFlowTransition(event) + is Event.LeaveSubFlow -> leaveSubFlowTransition() + is Event.Suspend -> suspendTransition(event) + is Event.FlowFinish -> flowFinishTransition(event) + is Event.InitiateFlow -> initiateFlowTransition(event) + is Event.AsyncOperationCompletion -> asyncOperationCompletionTransition(event) + is Event.RetryFlowFromSafePoint -> retryFlowFromSafePointTransition(startingState) } } @@ -191,7 +192,7 @@ class TopLevelTransition( if (state is SessionState.Initiated && state.initiatedState is InitiatedSessionState.Live) { val message = ExistingSessionMessage(state.initiatedState.peerSinkSessionId, EndSessionMessage) val deduplicationId = DeduplicationId.createForNormal(currentState.checkpoint, index) - Action.SendExisting(state.peerParty, message, deduplicationId) + Action.SendExisting(state.peerParty, message, SenderDeduplicationId(deduplicationId, currentState.senderUUID)) } else { null } @@ -230,4 +231,14 @@ class TopLevelTransition( resumeFlowLogic(event.returnValue) } } + + private fun retryFlowFromSafePointTransition(startingState: StateMachineState): TransitionResult { + return builder { + // Need to create a flow from the prior checkpoint or flow initiation. + actions.add(Action.CreateTransaction) + actions.add(Action.RetryFlowFromSafePoint(startingState)) + actions.add(Action.CommitTransaction) + FlowContinuation.Abort + } + } } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/UnstartedFlowTransition.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/UnstartedFlowTransition.kt index bd9f30c65c..7737127e16 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/UnstartedFlowTransition.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/UnstartedFlowTransition.kt @@ -58,7 +58,7 @@ class UnstartedFlowTransition( Action.SendExisting( flowStart.peerSession.counterparty, sessionMessage, - DeduplicationId.createForNormal(currentState.checkpoint, 0) + SenderDeduplicationId(DeduplicationId.createForNormal(currentState.checkpoint, 0), currentState.senderUUID) ) ) } diff --git a/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt b/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt index f7065c9807..639a02ff85 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt @@ -3,14 +3,21 @@ package net.corda.node.utilities import com.github.benmanes.caffeine.cache.LoadingCache import com.github.benmanes.caffeine.cache.Weigher import net.corda.core.utilities.contextLogger +import net.corda.nodeapi.internal.persistence.DatabaseTransaction +import net.corda.nodeapi.internal.persistence.contextTransaction import net.corda.nodeapi.internal.persistence.currentDBSession +import java.lang.ref.WeakReference import java.util.* +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicReference /** - * Implements a caching layer on top of an *append-only* table accessed via Hibernate mapping. Note that if the same key is [set] twice the - * behaviour is unpredictable! There is a best-effort check for double inserts, but this should *not* be relied on, so - * ONLY USE THIS IF YOUR TABLE IS APPEND-ONLY + * Implements a caching layer on top of an *append-only* table accessed via Hibernate mapping. Note that if the same key is [set] twice, + * typically this will result in a duplicate insert if this is racing with another transaction. The flow framework will then retry. + * + * This class relies heavily on the fact that compute operations in the cache are atomic for a particular key. */ abstract class AppendOnlyPersistentMapBase( val toPersistentEntityKey: (K) -> EK, @@ -23,7 +30,8 @@ abstract class AppendOnlyPersistentMapBase( private val log = contextLogger() } - protected abstract val cache: LoadingCache> + protected abstract val cache: LoadingCache> + protected val pendingKeys = ConcurrentHashMap>() /** * Returns the value associated with the key, first loading that value from the storage if necessary. @@ -47,32 +55,31 @@ abstract class AppendOnlyPersistentMapBase( return result.map { x -> fromPersistentEntity(x) }.asSequence() } - private tailrec fun set(key: K, value: V, logWarning: Boolean, store: (K, V) -> V?): Boolean { - var insertionAttempt = false - var isUnique = true - val existingInCache = cache.get(key) { - // Thread safe, if multiple threads may wait until the first one has loaded. - insertionAttempt = true - // Key wasn't in the cache and might be in the underlying storage. - // Depending on 'store' method, this may insert without checking key duplication or it may avoid inserting a duplicated key. - val existingInDb = store(key, value) - if (existingInDb != null) { // Always reuse an existing value from the storage of a duplicated key. - isUnique = false - Optional.of(existingInDb) - } else { - Optional.of(value) - } - }!! - if (!insertionAttempt) { - if (existingInCache.isPresent) { - // Key already exists in cache, do nothing. - isUnique = false - } else { - // This happens when the key was queried before with no value associated. We invalidate the cached null - // value and recursively call set again. This is to avoid race conditions where another thread queries after - // the invalidate but before the set. - cache.invalidate(key!!) - return set(key, value, logWarning, store) + private fun set(key: K, value: V, logWarning: Boolean, store: (K, V) -> V?): Boolean { + // Will be set to true if store says it isn't in the database. + var isUnique = false + cache.asMap().compute(key) { _, oldValue -> + // Always write to the database, unless we can see it's already committed. + when (oldValue) { + is Transactional.InFlight<*, V> -> { + // Someone else is writing, so store away! + // TODO: we can do collision detection here and prevent it happening in the database. But we also have to do deadlock detection, so a bit of work. + isUnique = (store(key, value) == null) + oldValue.apply { alsoWrite(value) } + } + is Transactional.Committed -> oldValue // The value is already globally visible and cached. So do nothing since the values are always the same. + else -> { + // Null or Missing. Store away! + isUnique = (store(key, value) == null) + if (!isUnique && !weAreWriting(key)) { + // If we found a value already in the database, and we were not already writing, then it's already committed but got evicted. + Transactional.Committed(value) + } else { + // Some database transactions, including us, writing, with readers seeing whatever is in the database and writers seeing the (in memory) value. + Transactional.InFlight(this, key, { loadValue(key) }).apply { alsoWrite(value) } + } + } + } } if (logWarning && !isUnique) { @@ -93,7 +100,8 @@ abstract class AppendOnlyPersistentMapBase( /** * Associates the specified value with the specified key in this map and persists it. - * If the map previously contained a mapping for the key, the old value is not replaced. + * If the map previously contained a committed mapping for the key, the old value is not replaced. It may throw an error from the + * underlying storage if this races with another database transaction to store a value for the same key. * @return true if added key was unique, otherwise false */ fun addWithDuplicatesAllowed(key: K, value: V, logWarning: Boolean = true): Boolean = @@ -116,7 +124,7 @@ abstract class AppendOnlyPersistentMapBase( protected fun loadValue(key: K): V? { val result = currentDBSession().find(persistentEntityClass, toPersistentEntityKey(key)) - return result?.let(fromPersistentEntity)?.second + return result?.apply { currentDBSession().detach(result) }?.let(fromPersistentEntity)?.second } operator fun contains(key: K) = get(key) != null @@ -132,9 +140,161 @@ abstract class AppendOnlyPersistentMapBase( session.createQuery(deleteQuery).executeUpdate() cache.invalidateAll() } + + // Helpers to know if transaction(s) are currently writing the given key. + protected fun weAreWriting(key: K): Boolean = pendingKeys.get(key)?.contains(contextTransaction) ?: false + protected fun anyoneWriting(key: K): Boolean = pendingKeys.get(key)?.isNotEmpty() ?: false + + // Indicate this database transaction is a writer of this key. + private fun addPendingKey(key: K, databaseTransaction: DatabaseTransaction): Boolean { + var added = true + pendingKeys.compute(key) { k, oldSet -> + if (oldSet == null) { + val newSet = HashSet(0) + newSet += databaseTransaction + newSet + } else { + added = oldSet.add(databaseTransaction) + oldSet + } + } + return added + } + + // Remove this database transaction as a writer of this key, because the transaction committed or rolled back. + private fun removePendingKey(key: K, databaseTransaction: DatabaseTransaction) { + pendingKeys.compute(key) { k, oldSet -> + if (oldSet == null) { + oldSet + } else { + oldSet -= databaseTransaction + if (oldSet.size == 0) null else oldSet + } + } + } + + /** + * Represents a value in the cache, with transaction isolation semantics. + * + * There are 3 states. Globally missing, globally visible, and being written in a transaction somewhere now or in + * the past (and it rolled back). + */ + sealed class Transactional { + abstract val value: T + abstract val isPresent: Boolean + abstract val valueWithoutIsolation: T? + + fun orElse(alt: T?) = if (isPresent) value else alt + + // Everyone can see it, and database transaction committed. + class Committed(override val value: T) : Transactional() { + override val isPresent: Boolean + get() = true + override val valueWithoutIsolation: T? + get() = value + } + + // No one can see it. + class Missing() : Transactional() { + override val value: T + get() = throw NoSuchElementException("Not present") + override val isPresent: Boolean + get() = false + override val valueWithoutIsolation: T? + get() = null + } + + // Written in a transaction (uncommitted) somewhere, but there's a small window when this might be seen after commit, + // hence the committed flag. + class InFlight(private val map: AppendOnlyPersistentMapBase, + private val key: K, + private val _readerValueLoader: () -> T?, + private val _writerValueLoader: () -> T = { throw IllegalAccessException("No value loader provided") }) : Transactional() { + + // A flag to indicate this has now been committed, but hasn't yet been replaced with Committed. This also + // de-duplicates writes of the Committed value to the cache. + private val committed = AtomicBoolean(false) + + // What to do if a non-writer needs to see the value and it hasn't yet been committed to the database. + // Can be updated into a no-op once evaluated. + private val readerValueLoader = AtomicReference<() -> T?>(_readerValueLoader) + // What to do if a writer needs to see the value and it hasn't yet been committed to the database. + // Can be updated into a no-op once evaluated. + private val writerValueLoader = AtomicReference<() -> T>(_writerValueLoader) + + fun alsoWrite(_value: T) { + // Make the lazy loader the writers see actually just return the value that has been set. + writerValueLoader.set({ _value }) + // We make all these vals so that the lambdas do not need a reference to this, and so the onCommit only has a weak ref to the value. + // We want this so that the cache could evict the value (due to memory constraints etc) without the onCommit callback + // retaining what could be a large memory footprint object. + val tx = contextTransaction + val strongKey = key + val weakValue = WeakReference(_value) + val strongComitted = committed + val strongMap = map + if (map.addPendingKey(key, tx)) { + // If the transaction commits, update cache to make globally visible if we're first for this key, + // and then stop saying the transaction is writing the key. + tx.onCommit { + if (strongComitted.compareAndSet(false, true)) { + val dereferencedKey = strongKey + val dereferencedValue = weakValue.get() + if (dereferencedValue != null) { + strongMap.cache.put(dereferencedKey, Committed(dereferencedValue)) + } + } + strongMap.removePendingKey(strongKey, tx) + } + // If the transaction rolls back, stop saying this transaction is writing the key. + tx.onRollback { + strongMap.removePendingKey(strongKey, tx) + } + } + } + + // Lazy load the value a "writer" would see. If the original loader hasn't been replaced, replace it + // with one that just returns the value once evaluated. + private fun loadAsWriter(): T { + val _value = writerValueLoader.get()() + if (writerValueLoader.get() == _writerValueLoader) { + writerValueLoader.set({ _value }) + } + return _value + } + + // Lazy load the value a "reader" would see. If the original loader hasn't been replaced, replace it + // with one that just returns the value once evaluated. + private fun loadAsReader(): T? { + val _value = readerValueLoader.get()() + if (readerValueLoader.get() == _readerValueLoader) { + readerValueLoader.set({ _value }) + } + return _value + } + + // Whether someone reading (only) can see the entry. + private val isPresentAsReader: Boolean get() = (loadAsReader() != null) + // Whether the entry is already written and committed, or we are writing it (and thus it can be seen). + private val isPresentAsWriter: Boolean get() = committed.get() || map.weAreWriting(key) + + override val isPresent: Boolean + get() = isPresentAsWriter || isPresentAsReader + + // If it is committed or we are writing, reveal the value, potentially lazy loading from the database. + // If none of the above, see what was already in the database, potentially lazily. + override val value: T + get() = if (isPresentAsWriter) loadAsWriter() else if (isPresentAsReader) loadAsReader()!! else throw NoSuchElementException("Not present") + + // The value from the perspective of the eviction algorithm of the cache. i.e. we want to reveal memory footprint to it etc. + override val valueWithoutIsolation: T? + get() = if (writerValueLoader.get() != _writerValueLoader) writerValueLoader.get()() else if (readerValueLoader.get() != _writerValueLoader) readerValueLoader.get()() else null + } + } } -class AppendOnlyPersistentMap( +// Open for tests to override +open class AppendOnlyPersistentMap( toPersistentEntityKey: (K) -> EK, fromPersistentEntity: (E) -> Pair, toPersistentEntity: (key: K, value: V) -> E, @@ -146,26 +306,71 @@ class AppendOnlyPersistentMap( toPersistentEntity, persistentEntityClass) { //TODO determine cacheBound based on entity class later or with node config allowing tuning, or using some heuristic based on heap size - override val cache = NonInvalidatingCache>( + override val cache = NonInvalidatingCache>( bound = cacheBound, - loadFunction = { key -> Optional.ofNullable(loadValue(key)) }) + loadFunction = { key: K -> + // This gets called if a value is read and the cache has no Transactional for this key yet. + val value: V? = loadValue(key) + if (value == null) { + // No visible value + if (anyoneWriting(key)) { + // If someone is writing (but not us) + // For those not writing, the value cannot be seen. + // For those writing, they need to re-load the value from the database (which their database transaction CAN see). + Transactional.InFlight(this, key, { null }, { loadValue(key)!! }) + } else { + // If no one is writing, then the value does not exist. + Transactional.Missing() + } + } else { + // A value was found + if (weAreWriting(key)) { + // If we are writing, it might not be globally visible, and was evicted from the cache. + // For those not writing, they need to check the database again. + // For those writing, they can see the value found. + Transactional.InFlight(this, key, { loadValue(key) }, { value }) + } else { + // If no one is writing, then make it globally visible. + Transactional.Committed(value) + } + } + }) } +// Same as above, but with weighted values (e.g. memory footprint sensitive). class WeightBasedAppendOnlyPersistentMap( toPersistentEntityKey: (K) -> EK, fromPersistentEntity: (E) -> Pair, toPersistentEntity: (key: K, value: V) -> E, persistentEntityClass: Class, maxWeight: Long, - weighingFunc: (K, Optional) -> Int + weighingFunc: (K, Transactional) -> Int ) : AppendOnlyPersistentMapBase( toPersistentEntityKey, fromPersistentEntity, toPersistentEntity, persistentEntityClass) { - override val cache = NonInvalidatingWeightBasedCache( + override val cache = NonInvalidatingWeightBasedCache>( maxWeight = maxWeight, - weigher = Weigher> { key, value -> weighingFunc(key, value) }, - loadFunction = { key -> Optional.ofNullable(loadValue(key)) } - ) -} \ No newline at end of file + weigher = object : Weigher> { + override fun weigh(key: K, value: Transactional): Int { + return weighingFunc(key, value) + } + }, + loadFunction = { key: K -> + val value: V? = loadValue(key) + if (value == null) { + if (anyoneWriting(key)) { + Transactional.InFlight(this, key, { null }, { loadValue(key)!! }) + } else { + Transactional.Missing() + } + } else { + if (weAreWriting(key)) { + Transactional.InFlight(this, key, { loadValue(key) }, { value }) + } else { + Transactional.Committed(value) + } + } + }) +} diff --git a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index ec6eb06604..3255e92b7a 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt @@ -14,14 +14,15 @@ import net.corda.node.internal.configureDatabase import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.NodePropertiesStore import net.corda.node.services.messaging.DeduplicationHandler +import net.corda.node.services.statemachine.ExternalEvent import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig -import net.corda.nodeapi.internal.persistence.DatabaseTransaction import net.corda.testing.internal.doLookup import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.spectator import net.corda.testing.node.MockServices import net.corda.testing.node.TestClock +import org.junit.After import org.junit.Ignore import org.junit.Rule import org.junit.Test @@ -50,7 +51,7 @@ open class NodeSchedulerServiceTestBase { dedupe.insideDatabaseTransaction() dedupe.afterDatabaseTransaction() openFuture>() - }.whenever(it).startFlow(any>(), any(), any()) + }.whenever(it).startFlow(any>()) } private val flowsDraingMode = rigorousMock().also { doReturn(false).whenever(it).isEnabled() @@ -80,7 +81,7 @@ open class NodeSchedulerServiceTestBase { protected fun assertStarted(flowLogic: FlowLogic<*>) { // Like in assertWaitingFor, use timeout to make verify wait as we often race the call to startFlow: - verify(flowStarter, timeout(5000)).startFlow(same(flowLogic), any(), any()) + verify(flowStarter, timeout(5000)).startFlow(argForWhich> { this.flowLogic == flowLogic }) } protected fun assertStarted(event: Event) = assertStarted(event.flowLogic) @@ -112,11 +113,11 @@ class MockScheduledFlowRepository : ScheduledFlowRepository { } class NodeSchedulerServiceTest : NodeSchedulerServiceTestBase() { - private val database = rigorousMock().also { - doAnswer { - val block: DatabaseTransaction.() -> Any? = it.getArgument(0) - rigorousMock().block() - }.whenever(it).transaction(any()) + private val database = configureDatabase(MockServices.makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) + + @After + fun closeDatabase() { + database.close() } private val scheduler = NodeSchedulerService( @@ -148,7 +149,9 @@ class NodeSchedulerServiceTest : NodeSchedulerServiceTestBase() { }).whenever(it).data } flows[logicRef] = flowLogic - scheduler.scheduleStateActivity(ssr) + database.transaction { + scheduler.scheduleStateActivity(ssr) + } } @Test @@ -207,7 +210,9 @@ class NodeSchedulerServiceTest : NodeSchedulerServiceTestBase() { fun `test activity due in the future and schedule another for same time then unschedule second`() { val eventA = schedule(mark + 1.days) val eventB = schedule(mark + 1.days) - scheduler.unscheduleStateActivity(eventB.stateRef) + database.transaction { + scheduler.unscheduleStateActivity(eventB.stateRef) + } assertWaitingFor(eventA) testClock.advanceBy(1.days) assertStarted(eventA) @@ -217,7 +222,9 @@ class NodeSchedulerServiceTest : NodeSchedulerServiceTestBase() { fun `test activity due in the future and schedule another for same time then unschedule original`() { val eventA = schedule(mark + 1.days) val eventB = schedule(mark + 1.days) - scheduler.unscheduleStateActivity(eventA.stateRef) + database.transaction { + scheduler.unscheduleStateActivity(eventA.stateRef) + } assertWaitingFor(eventB) testClock.advanceBy(1.days) assertStarted(eventB) @@ -225,7 +232,9 @@ class NodeSchedulerServiceTest : NodeSchedulerServiceTestBase() { @Test fun `test activity due in the future then unschedule`() { - scheduler.unscheduleStateActivity(schedule(mark + 1.days).stateRef) + database.transaction { + scheduler.unscheduleStateActivity(schedule(mark + 1.days).stateRef) + } testClock.advanceBy(1.days) } } diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt new file mode 100644 index 0000000000..b10042dcc6 --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt @@ -0,0 +1,290 @@ +package net.corda.node.services.persistence + +import net.corda.core.schemas.MappedSchema +import net.corda.core.utilities.loggerFor +import net.corda.node.internal.configureDatabase +import net.corda.node.services.schema.NodeSchemaService +import net.corda.node.utilities.AppendOnlyPersistentMap +import net.corda.nodeapi.internal.persistence.DatabaseConfig +import net.corda.testing.internal.rigorousMock +import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties +import org.junit.After +import org.junit.Assert.* +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import java.io.Serializable +import java.util.concurrent.CountDownLatch +import javax.persistence.Column +import javax.persistence.Entity +import javax.persistence.Id +import javax.persistence.PersistenceException + +@RunWith(Parameterized::class) +class AppendOnlyPersistentMapTest(var scenario: Scenario) { + companion object { + + private val scenarios = arrayOf( + Scenario(false, ReadOrWrite.Read, ReadOrWrite.Read, Outcome.Fail, Outcome.Fail), + Scenario(false, ReadOrWrite.Write, ReadOrWrite.Read, Outcome.Success, Outcome.Fail, Outcome.Success), + Scenario(false, ReadOrWrite.Read, ReadOrWrite.Write, Outcome.Fail, Outcome.Success), + Scenario(false, ReadOrWrite.Write, ReadOrWrite.Write, Outcome.Success, Outcome.SuccessButErrorOnCommit), + Scenario(false, ReadOrWrite.WriteDuplicateAllowed, ReadOrWrite.Read, Outcome.Success, Outcome.Fail, Outcome.Success), + Scenario(false, ReadOrWrite.Read, ReadOrWrite.WriteDuplicateAllowed, Outcome.Fail, Outcome.Success), + Scenario(false, ReadOrWrite.WriteDuplicateAllowed, ReadOrWrite.WriteDuplicateAllowed, Outcome.Success, Outcome.SuccessButErrorOnCommit, Outcome.Fail), + Scenario(true, ReadOrWrite.Read, ReadOrWrite.Read, Outcome.Success, Outcome.Success), + Scenario(true, ReadOrWrite.Write, ReadOrWrite.Read, Outcome.SuccessButErrorOnCommit, Outcome.Success), + Scenario(true, ReadOrWrite.Read, ReadOrWrite.Write, Outcome.Success, Outcome.Fail), + Scenario(true, ReadOrWrite.Write, ReadOrWrite.Write, Outcome.SuccessButErrorOnCommit, Outcome.SuccessButErrorOnCommit), + Scenario(true, ReadOrWrite.WriteDuplicateAllowed, ReadOrWrite.Read, Outcome.Fail, Outcome.Success), + Scenario(true, ReadOrWrite.Read, ReadOrWrite.WriteDuplicateAllowed, Outcome.Success, Outcome.Fail), + Scenario(true, ReadOrWrite.WriteDuplicateAllowed, ReadOrWrite.WriteDuplicateAllowed, Outcome.Fail, Outcome.Fail) + ) + + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun data(): Array> = scenarios.map { arrayOf(it) }.toTypedArray() + } + + enum class ReadOrWrite { Read, Write, WriteDuplicateAllowed } + enum class Outcome { Success, Fail, SuccessButErrorOnCommit } + + data class Scenario(val prePopulated: Boolean, + val a: ReadOrWrite, + val b: ReadOrWrite, + val aExpected: Outcome, + val bExpected: Outcome, + val bExpectedIfSingleThreaded: Outcome = bExpected) + + private val database = configureDatabase(makeTestDataSourceProperties(), + DatabaseConfig(), + rigorousMock(), + NodeSchemaService(setOf(MappedSchema(AppendOnlyPersistentMapTest::class.java, 1, listOf(PersistentMapEntry::class.java))))) + + @After + fun closeDatabase() { + database.close() + } + + @Test + fun `concurrent test no purge between A and B`() { + prepopulateIfRequired() + val map = createMap() + val a = TestThread("A", map).apply { start() } + val b = TestThread("B", map).apply { start() } + + // Begin A + a.phase1.countDown() + a.await(a::phase2) + + // Begin B + b.phase1.countDown() + b.await(b::phase2) + + // Commit A + a.phase3.countDown() + a.await(a::phase4) + + // Commit B + b.phase3.countDown() + b.await(b::phase4) + + // End + a.join() + b.join() + assertTrue(map.pendingKeysIsEmpty()) + } + + @Test + fun `test no purge with only a single transaction`() { + prepopulateIfRequired() + val map = createMap() + val a = TestThread("A", map, true).apply { + phase1.countDown() + phase3.countDown() + } + val b = TestThread("B", map, true).apply { + phase1.countDown() + phase3.countDown() + } + try { + database.transaction { + a.run() + b.run() + } + } catch (t: PersistenceException) { + // This only helps if thrown on commit, otherwise other latches not counted down. + assertEquals(t.message, Outcome.SuccessButErrorOnCommit, a.outcome) + } + a.await(a::phase4) + b.await(b::phase4) + assertTrue(map.pendingKeysIsEmpty()) + } + + + @Test + fun `concurrent test purge between A and B`() { + // Writes intentionally do not check the database first, so purging between read and write changes behaviour + val remapped = mapOf(Scenario(true, ReadOrWrite.Read, ReadOrWrite.Write, Outcome.Success, Outcome.Fail) to Scenario(true, ReadOrWrite.Read, ReadOrWrite.Write, Outcome.Success, Outcome.SuccessButErrorOnCommit)) + scenario = remapped[scenario] ?: scenario + prepopulateIfRequired() + val map = createMap() + val a = TestThread("A", map).apply { start() } + val b = TestThread("B", map).apply { start() } + + // Begin A + a.phase1.countDown() + a.await(a::phase2) + + map.invalidate() + + // Begin B + b.phase1.countDown() + b.await(b::phase2) + + // Commit A + a.phase3.countDown() + a.await(a::phase4) + + // Commit B + b.phase3.countDown() + b.await(b::phase4) + + // End + a.join() + b.join() + assertTrue(map.pendingKeysIsEmpty()) + } + + @Test + fun `test purge mid-way in a single transaction`() { + // Writes intentionally do not check the database first, so purging between read and write changes behaviour + val remapped = mapOf(Scenario(true, ReadOrWrite.Read, ReadOrWrite.Write, Outcome.Success, Outcome.Fail) to Scenario(true, ReadOrWrite.Read, ReadOrWrite.Write, Outcome.SuccessButErrorOnCommit, Outcome.SuccessButErrorOnCommit)) + scenario = remapped[scenario] ?: scenario + prepopulateIfRequired() + val map = createMap() + val a = TestThread("A", map, true).apply { + phase1.countDown() + phase3.countDown() + } + val b = TestThread("B", map, true).apply { + phase1.countDown() + phase3.countDown() + } + try { + database.transaction { + a.run() + map.invalidate() + b.run() + } + } catch (t: PersistenceException) { + // This only helps if thrown on commit, otherwise other latches not counted down. + assertEquals(t.message, Outcome.SuccessButErrorOnCommit, a.outcome) + } + a.await(a::phase4) + b.await(b::phase4) + assertTrue(map.pendingKeysIsEmpty()) + } + + inner class TestThread(name: String, val map: AppendOnlyPersistentMap, singleThreaded: Boolean = false) : Thread(name) { + private val log = loggerFor() + + val readOrWrite = if (name == "A") scenario.a else scenario.b + val outcome = if (name == "A") scenario.aExpected else if (singleThreaded) scenario.bExpectedIfSingleThreaded else scenario.bExpected + + val phase1 = latch() + val phase2 = latch() + val phase3 = latch() + val phase4 = latch() + + override fun run() { + try { + database.transaction { + await(::phase1) + doActivity() + phase2.countDown() + await(::phase3) + } + } catch (t: PersistenceException) { + // This only helps if thrown on commit, otherwise other latches not counted down. + assertEquals(t.message, Outcome.SuccessButErrorOnCommit, outcome) + } + phase4.countDown() + } + + private fun doActivity() { + if (readOrWrite == ReadOrWrite.Read) { + log.info("Reading") + val value = map.get(1) + log.info("Read $value") + if (outcome == Outcome.Success || outcome == Outcome.SuccessButErrorOnCommit) { + assertEquals("X", value) + } else { + assertNull(value) + } + } else if (readOrWrite == ReadOrWrite.Write) { + log.info("Writing") + val wasSet = map.set(1, "X") + log.info("Write $wasSet") + if (outcome == Outcome.Success || outcome == Outcome.SuccessButErrorOnCommit) { + assertEquals(true, wasSet) + } else { + assertEquals(false, wasSet) + } + } else if (readOrWrite == ReadOrWrite.WriteDuplicateAllowed) { + log.info("Writing with duplicates allowed") + val wasSet = map.addWithDuplicatesAllowed(1, "X") + log.info("Write with duplicates allowed $wasSet") + if (outcome == Outcome.Success || outcome == Outcome.SuccessButErrorOnCommit) { + assertEquals(true, wasSet) + } else { + assertEquals(false, wasSet) + } + } + } + + private fun latch() = CountDownLatch(1) + fun await(latch: () -> CountDownLatch) { + log.info("Awaiting $latch") + latch().await() + } + } + + private fun prepopulateIfRequired() { + if (scenario.prePopulated) { + database.transaction { + val map = createMap() + map.set(1, "X") + } + } + } + + @Entity + @javax.persistence.Table(name = "persist_map_test") + class PersistentMapEntry( + @Id + @Column(name = "key") + var key: Long = -1, + + @Column(name = "value", length = 16) + var value: String = "" + ) : Serializable + + class TestMap : AppendOnlyPersistentMap( + toPersistentEntityKey = { it }, + fromPersistentEntity = { Pair(it.key, it.value) }, + toPersistentEntity = { key: Long, value: String -> + PersistentMapEntry().apply { + this.key = key + this.value = value + } + }, + persistentEntityClass = PersistentMapEntry::class.java + ) { + fun pendingKeysIsEmpty() = pendingKeys.isEmpty() + + fun invalidate() = cache.invalidateAll() + } + + fun createMap() = TestMap() +} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/TransactionCallbackTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/TransactionCallbackTest.kt new file mode 100644 index 0000000000..cd46899392 --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/services/persistence/TransactionCallbackTest.kt @@ -0,0 +1,49 @@ +package net.corda.node.services.persistence + +import net.corda.node.internal.configureDatabase +import net.corda.nodeapi.internal.persistence.DatabaseConfig +import net.corda.testing.internal.rigorousMock +import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties +import org.junit.After +import org.junit.Test +import kotlin.test.assertEquals + + +class TransactionCallbackTest { + private val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) + + @After + fun closeDatabase() { + database.close() + } + + @Test + fun `onCommit called and onRollback not called on commit`() { + var onCommitCount = 0 + var onRollbackCount = 0 + database.transaction { + onCommit { onCommitCount++ } + onRollback { onRollbackCount++ } + } + assertEquals(1, onCommitCount) + assertEquals(0, onRollbackCount) + } + + @Test + fun `onCommit not called and onRollback called on rollback`() { + class TestException : Exception() + + var onCommitCount = 0 + var onRollbackCount = 0 + try { + database.transaction { + onCommit { onCommitCount++ } + onRollback { onRollbackCount++ } + throw TestException() + } + } catch (e: TestException) { + } + assertEquals(0, onCommitCount) + assertEquals(1, onRollbackCount) + } +} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/RetryFlowMockTest.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/RetryFlowMockTest.kt new file mode 100644 index 0000000000..12b8d8af23 --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/RetryFlowMockTest.kt @@ -0,0 +1,166 @@ +package net.corda.node.services.statemachine + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.concurrent.CordaFuture +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession +import net.corda.core.flows.InitiatedBy +import net.corda.core.flows.InitiatingFlow +import net.corda.core.identity.Party +import net.corda.core.messaging.MessageRecipients +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.unwrap +import net.corda.node.internal.StartedNode +import net.corda.node.services.messaging.Message +import net.corda.node.services.persistence.DBTransactionStorage +import net.corda.nodeapi.internal.persistence.contextTransaction +import net.corda.testing.node.internal.InternalMockNetwork +import net.corda.testing.node.internal.MessagingServiceSpy +import net.corda.testing.node.internal.newContext +import net.corda.testing.node.internal.setMessagingServiceSpy +import org.assertj.core.api.Assertions +import org.junit.After +import org.junit.Before +import org.junit.Test +import java.sql.SQLException +import java.time.Duration +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class RetryFlowMockTest { + private lateinit var mockNet: InternalMockNetwork + private lateinit var internalNodeA: StartedNode + private lateinit var internalNodeB: StartedNode + + @Before + fun start() { + mockNet = InternalMockNetwork(threadPerNode = true, cordappPackages = listOf(this.javaClass.`package`.name)) + internalNodeA = mockNet.createNode() + internalNodeB = mockNet.createNode() + mockNet.startNodes() + RetryFlow.count = 0 + SendAndRetryFlow.count = 0 + RetryInsertFlow.count = 0 + } + + private fun StartedNode.startFlow(logic: FlowLogic): CordaFuture = this.services.startFlow(logic, this.services.newContext()).getOrThrow().resultFuture + + @After + fun cleanUp() { + mockNet.stopNodes() + } + + @Test + fun `Single retry`() { + assertEquals(Unit, internalNodeA.startFlow(RetryFlow(1)).get()) + assertEquals(2, RetryFlow.count) + } + + @Test + fun `Retry forever`() { + Assertions.assertThatThrownBy { + internalNodeA.startFlow(RetryFlow(Int.MAX_VALUE)).getOrThrow() + }.isInstanceOf(LimitedRetryCausingError::class.java) + assertEquals(5, RetryFlow.count) + } + + @Test + fun `Retry does not set senderUUID`() { + val messagesSent = mutableListOf() + val partyB = internalNodeB.info.legalIdentities.first() + internalNodeA.setMessagingServiceSpy(object : MessagingServiceSpy(internalNodeA.network) { + override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any) { + messagesSent.add(message) + messagingService.send(message, target, retryId) + } + }) + internalNodeA.startFlow(SendAndRetryFlow(1, partyB)).get() + assertNotNull(messagesSent.first().senderUUID) + assertNull(messagesSent.last().senderUUID) + assertEquals(2, SendAndRetryFlow.count) + } + + @Test + fun `Retry duplicate insert`() { + assertEquals(Unit, internalNodeA.startFlow(RetryInsertFlow(1)).get()) + assertEquals(2, RetryInsertFlow.count) + } + + @Test + fun `Patient records do not leak in hospital`() { + assertEquals(Unit, internalNodeA.startFlow(RetryFlow(1)).get()) + assertEquals(0, StaffedFlowHospital.numberOfPatients) + assertEquals(2, RetryFlow.count) + } +} + +class LimitedRetryCausingError : org.hibernate.exception.ConstraintViolationException("Test message", SQLException(), "Test constraint") + +class RetryCausingError : SQLException("deadlock") + +class RetryFlow(val i: Int) : FlowLogic() { + companion object { + var count = 0 + } + + @Suspendable + override fun call() { + logger.info("Hello $count") + if (count++ < i) { + if (i == Int.MAX_VALUE) { + throw LimitedRetryCausingError() + } else { + throw RetryCausingError() + } + } + } +} + +@InitiatingFlow +class SendAndRetryFlow(val i: Int, val other: Party) : FlowLogic() { + companion object { + var count = 0 + } + + @Suspendable + override fun call() { + logger.info("Sending...") + val session = initiateFlow(other) + session.send("Boo") + if (count++ < i) { + throw RetryCausingError() + } + } +} + +@InitiatedBy(SendAndRetryFlow::class) +class ReceiveFlow2(val other: FlowSession) : FlowLogic() { + @Suspendable + override fun call() { + val received = other.receive().unwrap { it } + logger.info("Received... $received") + } +} + +class RetryInsertFlow(val i: Int) : FlowLogic() { + companion object { + var count = 0 + } + + @Suspendable + override fun call() { + logger.info("Hello") + doInsert() + // Checkpoint so we roll back to here + FlowLogic.sleep(Duration.ofSeconds(0)) + if (count++ < i) { + doInsert() + } + } + + private fun doInsert() { + val tx = DBTransactionStorage.DBTransaction("Foo") + contextTransaction.session.save(tx) + } +} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index cdba4ced28..f64efcf72f 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -28,7 +28,10 @@ import net.corda.nodeapi.internal.persistence.DatabaseTransaction import net.corda.testing.core.* import net.corda.testing.internal.TEST_TX_TIME import net.corda.testing.internal.rigorousMock -import net.corda.testing.internal.vault.* +import net.corda.testing.internal.vault.DUMMY_LINEAR_CONTRACT_PROGRAM_ID +import net.corda.testing.internal.vault.DummyLinearContract +import net.corda.testing.internal.vault.DummyLinearStateSchemaV1 +import net.corda.testing.internal.vault.VaultFiller import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices import net.corda.testing.node.makeTestIdentityService @@ -171,17 +174,11 @@ abstract class VaultQueryTestsBase : VaultQueryParties { @JvmField val expectedEx: ExpectedException = ExpectedException.none() - @Suppress("LeakingThis") - @Rule - @JvmField - val transactionRule = VaultQueryRollbackRule(this) - companion object { @ClassRule @JvmField val testSerialization = SerializationEnvironmentRule() } - /** * Helper method for generating a Persistent H2 test database */ @@ -194,7 +191,7 @@ abstract class VaultQueryTestsBase : VaultQueryParties { database.close() } - private fun consumeCash(amount: Amount) = vaultFiller.consumeCash(amount, CHARLIE) + protected fun consumeCash(amount: Amount) = vaultFiller.consumeCash(amount, CHARLIE) private fun setUpDb(_database: CordaPersistence, delay: Long = 0) { _database.transaction { // create new states @@ -1988,239 +1985,6 @@ abstract class VaultQueryTestsBase : VaultQueryParties { } } - /** - * Dynamic trackBy() tests - */ - - @Test - fun trackCashStates_unconsumed() { - val updates = database.transaction { - val updates = - // DOCSTART VaultQueryExample15 - vaultService.trackBy().updates // UNCONSUMED default - // DOCEND VaultQueryExample15 - - vaultFiller.fillWithSomeTestCash(100.DOLLARS, notaryServices, 5, DUMMY_CASH_ISSUER) - val linearStates = vaultFiller.fillWithSomeTestLinearStates(10).states - val dealStates = vaultFiller.fillWithSomeTestDeals(listOf("123", "456", "789")).states - // add more cash - vaultFiller.fillWithSomeTestCash(100.POUNDS, notaryServices, 1, DUMMY_CASH_ISSUER) - // add another deal - vaultFiller.fillWithSomeTestDeals(listOf("SAMPLE DEAL")) - this.session.flush() - - // consume stuff - consumeCash(100.DOLLARS) - vaultFiller.consumeDeals(dealStates.toList()) - vaultFiller.consumeLinearStates(linearStates.toList()) - - close() // transaction needs to be closed to trigger events - updates - } - - updates.expectEvents { - sequence( - expect { (consumed, produced, flowId) -> - require(flowId == null) {} - require(consumed.isEmpty()) {} - require(produced.size == 5) {} - }, - expect { (consumed, produced, flowId) -> - require(flowId == null) {} - require(consumed.isEmpty()) {} - require(produced.size == 1) {} - } - ) - } - } - - @Test - fun trackCashStates_consumed() { - - val updates = database.transaction { - val criteria = VaultQueryCriteria(status = Vault.StateStatus.CONSUMED) - val updates = vaultService.trackBy(criteria).updates - - vaultFiller.fillWithSomeTestCash(100.DOLLARS, notaryServices, 5, DUMMY_CASH_ISSUER) - val linearStates = vaultFiller.fillWithSomeTestLinearStates(10).states - val dealStates = vaultFiller.fillWithSomeTestDeals(listOf("123", "456", "789")).states - // add more cash - vaultFiller.fillWithSomeTestCash(100.POUNDS, notaryServices, 1, DUMMY_CASH_ISSUER) - // add another deal - vaultFiller.fillWithSomeTestDeals(listOf("SAMPLE DEAL")) - this.session.flush() - - consumeCash(100.POUNDS) - - // consume more stuff - consumeCash(100.DOLLARS) - vaultFiller.consumeDeals(dealStates.toList()) - vaultFiller.consumeLinearStates(linearStates.toList()) - - close() // transaction needs to be closed to trigger events - updates - } - - updates.expectEvents { - sequence( - expect { (consumed, produced, flowId) -> - require(flowId == null) {} - require(consumed.size == 1) {} - require(produced.isEmpty()) {} - }, - expect { (consumed, produced, flowId) -> - require(flowId == null) {} - require(consumed.size == 5) {} - require(produced.isEmpty()) {} - } - ) - } - } - - @Test - fun trackCashStates_all() { - val updates = database.transaction { - val updates = - database.transaction { - val criteria = VaultQueryCriteria(status = Vault.StateStatus.ALL) - vaultService.trackBy(criteria).updates - } - vaultFiller.fillWithSomeTestCash(100.DOLLARS, notaryServices, 5, DUMMY_CASH_ISSUER) - val linearStates = vaultFiller.fillWithSomeTestLinearStates(10).states - val dealStates = vaultFiller.fillWithSomeTestDeals(listOf("123", "456", "789")).states - // add more cash - vaultFiller.fillWithSomeTestCash(100.POUNDS, notaryServices, 1, DUMMY_CASH_ISSUER) - // add another deal - vaultFiller.fillWithSomeTestDeals(listOf("SAMPLE DEAL")) - this.session.flush() - -// consume stuff - consumeCash(99.POUNDS) - - consumeCash(100.DOLLARS) - vaultFiller.consumeDeals(dealStates.toList()) - vaultFiller.consumeLinearStates(linearStates.toList()) - - close() // transaction needs to be closed to trigger events - updates - } - - updates.expectEvents { - sequence( - expect { (consumed, produced, flowId) -> - require(flowId == null) {} - require(consumed.isEmpty()) {} - require(produced.size == 5) {} - }, - expect { (consumed, produced, flowId) -> - require(flowId == null) {} - require(consumed.isEmpty()) {} - require(produced.size == 1) {} - }, - expect { (consumed, produced, flowId) -> - require(flowId == null) {} - require(consumed.size == 1) {} - require(produced.size == 1) {} - }, - expect { (consumed, produced, flowId) -> - require(flowId == null) {} - require(consumed.size == 5) {} - require(produced.isEmpty()) {} - } - ) - } - } - - @Test - fun trackLinearStates() { - - val updates = database.transaction { - // DOCSTART VaultQueryExample16 - val (snapshot, updates) = vaultService.trackBy() - // DOCEND VaultQueryExample16 - assertThat(snapshot.states).hasSize(0) - - vaultFiller.fillWithSomeTestCash(100.DOLLARS, notaryServices, 3, DUMMY_CASH_ISSUER) - val linearStates = vaultFiller.fillWithSomeTestLinearStates(10).states - val dealStates = vaultFiller.fillWithSomeTestDeals(listOf("123", "456", "789")).states - // add more cash - vaultFiller.fillWithSomeTestCash(100.POUNDS, notaryServices, 1, DUMMY_CASH_ISSUER) - // add another deal - vaultFiller.fillWithSomeTestDeals(listOf("SAMPLE DEAL")) - this.session.flush() - - // consume stuff - consumeCash(100.DOLLARS) - vaultFiller.consumeDeals(dealStates.toList()) - vaultFiller.consumeLinearStates(linearStates.toList()) - - close() // transaction needs to be closed to trigger events - updates - } - - updates.expectEvents { - sequence( - expect { (consumed, produced, flowId) -> - require(flowId == null) {} - require(consumed.isEmpty()) {} - require(produced.size == 10) {} - }, - expect { (consumed, produced, flowId) -> - require(flowId == null) {} - require(consumed.isEmpty()) {} - require(produced.size == 3) {} - }, - expect { (consumed, produced, flowId) -> - require(flowId == null) {} - require(consumed.isEmpty()) {} - require(produced.size == 1) {} - } - ) - } - } - - @Test - fun trackDealStates() { - val updates = database.transaction { - // DOCSTART VaultQueryExample17 - val (snapshot, updates) = vaultService.trackBy() - // DOCEND VaultQueryExample17 - assertThat(snapshot.states).hasSize(0) - - vaultFiller.fillWithSomeTestCash(100.DOLLARS, notaryServices, 3, DUMMY_CASH_ISSUER) - val linearStates = vaultFiller.fillWithSomeTestLinearStates(10).states - val dealStates = vaultFiller.fillWithSomeTestDeals(listOf("123", "456", "789")).states - // add more cash - vaultFiller.fillWithSomeTestCash(100.POUNDS, notaryServices, 1, DUMMY_CASH_ISSUER) - // add another deal - vaultFiller.fillWithSomeTestDeals(listOf("SAMPLE DEAL")) - this.session.flush() - - // consume stuff - consumeCash(100.DOLLARS) - vaultFiller.consumeDeals(dealStates.toList()) - vaultFiller.consumeLinearStates(linearStates.toList()) - - close() - updates - } - - updates.expectEvents { - sequence( - expect { (consumed, produced, flowId) -> - require(flowId == null) {} - require(consumed.isEmpty()) {} - require(produced.size == 3) {} - }, - expect { (consumed, produced, flowId) -> - require(flowId == null) {} - require(consumed.isEmpty()) {} - require(produced.size == 1) {} - } - ) - } - } - @Test fun unconsumedCashStatesForSpending_single_issuer_reference() { database.transaction { @@ -2281,10 +2045,241 @@ abstract class VaultQueryTestsBase : VaultQueryParties { */ } -class VaultQueryTests : VaultQueryTestsBase(), VaultQueryParties by vaultQueryTestRule { +class VaultQueryTests : VaultQueryTestsBase(), VaultQueryParties by delegate { companion object { - @ClassRule @JvmField - val vaultQueryTestRule = VaultQueryTestRule() + val delegate = VaultQueryTestRule() + } + + @Rule + @JvmField + val vaultQueryTestRule = delegate + + /** + * Dynamic trackBy() tests are H2 only, since rollback stops events being emitted. + */ + + @Test + fun trackCashStates_unconsumed() { + val updates = database.transaction { + val updates = + // DOCSTART VaultQueryExample15 + vaultService.trackBy().updates // UNCONSUMED default + // DOCEND VaultQueryExample15 + + vaultFiller.fillWithSomeTestCash(100.DOLLARS, notaryServices, 5, DUMMY_CASH_ISSUER) + val linearStates = vaultFiller.fillWithSomeTestLinearStates(10).states + val dealStates = vaultFiller.fillWithSomeTestDeals(listOf("123", "456", "789")).states + // add more cash + vaultFiller.fillWithSomeTestCash(100.POUNDS, notaryServices, 1, DUMMY_CASH_ISSUER) + // add another deal + vaultFiller.fillWithSomeTestDeals(listOf("SAMPLE DEAL")) + this.session.flush() + + // consume stuff + consumeCash(100.DOLLARS) + vaultFiller.consumeDeals(dealStates.toList()) + vaultFiller.consumeLinearStates(linearStates.toList()) + + updates + } + + updates.expectEvents { + sequence( + expect { (consumed, produced, flowId) -> + require(flowId == null) {} + require(consumed.isEmpty()) {} + require(produced.size == 5) {} + }, + expect { (consumed, produced, flowId) -> + require(flowId == null) {} + require(consumed.isEmpty()) {} + require(produced.size == 1) {} + } + ) + } + } + + @Test + fun trackCashStates_consumed() { + + val updates = database.transaction { + val criteria = VaultQueryCriteria(status = Vault.StateStatus.CONSUMED) + val updates = vaultService.trackBy(criteria).updates + + vaultFiller.fillWithSomeTestCash(100.DOLLARS, notaryServices, 5, DUMMY_CASH_ISSUER) + val linearStates = vaultFiller.fillWithSomeTestLinearStates(10).states + val dealStates = vaultFiller.fillWithSomeTestDeals(listOf("123", "456", "789")).states + // add more cash + vaultFiller.fillWithSomeTestCash(100.POUNDS, notaryServices, 1, DUMMY_CASH_ISSUER) + // add another deal + vaultFiller.fillWithSomeTestDeals(listOf("SAMPLE DEAL")) + this.session.flush() + + consumeCash(100.POUNDS) + + // consume more stuff + consumeCash(100.DOLLARS) + vaultFiller.consumeDeals(dealStates.toList()) + vaultFiller.consumeLinearStates(linearStates.toList()) + + updates + } + + updates.expectEvents { + sequence( + expect { (consumed, produced, flowId) -> + require(flowId == null) {} + require(consumed.size == 1) {} + require(produced.isEmpty()) {} + }, + expect { (consumed, produced, flowId) -> + require(flowId == null) {} + require(consumed.size == 5) {} + require(produced.isEmpty()) {} + } + ) + } + } + + @Test + fun trackCashStates_all() { + val updates = database.transaction { + val updates = + database.transaction { + val criteria = VaultQueryCriteria(status = Vault.StateStatus.ALL) + vaultService.trackBy(criteria).updates + } + vaultFiller.fillWithSomeTestCash(100.DOLLARS, notaryServices, 5, DUMMY_CASH_ISSUER) + val linearStates = vaultFiller.fillWithSomeTestLinearStates(10).states + val dealStates = vaultFiller.fillWithSomeTestDeals(listOf("123", "456", "789")).states + // add more cash + vaultFiller.fillWithSomeTestCash(100.POUNDS, notaryServices, 1, DUMMY_CASH_ISSUER) + // add another deal + vaultFiller.fillWithSomeTestDeals(listOf("SAMPLE DEAL")) + this.session.flush() + + // consume stuff + consumeCash(99.POUNDS) + + consumeCash(100.DOLLARS) + vaultFiller.consumeDeals(dealStates.toList()) + vaultFiller.consumeLinearStates(linearStates.toList()) + + updates + } + + updates.expectEvents { + sequence( + expect { (consumed, produced, flowId) -> + require(flowId == null) {} + require(consumed.isEmpty()) {} + require(produced.size == 5) {} + }, + expect { (consumed, produced, flowId) -> + require(flowId == null) {} + require(consumed.isEmpty()) {} + require(produced.size == 1) {} + }, + expect { (consumed, produced, flowId) -> + require(flowId == null) {} + require(consumed.size == 1) {} + require(produced.size == 1) {} + }, + expect { (consumed, produced, flowId) -> + require(flowId == null) {} + require(consumed.size == 5) {} + require(produced.isEmpty()) {} + } + ) + } + } + + @Test + fun trackLinearStates() { + + val updates = database.transaction { + // DOCSTART VaultQueryExample16 + val (snapshot, updates) = vaultService.trackBy() + // DOCEND VaultQueryExample16 + assertThat(snapshot.states).hasSize(0) + + vaultFiller.fillWithSomeTestCash(100.DOLLARS, notaryServices, 3, DUMMY_CASH_ISSUER) + val linearStates = vaultFiller.fillWithSomeTestLinearStates(10).states + val dealStates = vaultFiller.fillWithSomeTestDeals(listOf("123", "456", "789")).states + // add more cash + vaultFiller.fillWithSomeTestCash(100.POUNDS, notaryServices, 1, DUMMY_CASH_ISSUER) + // add another deal + vaultFiller.fillWithSomeTestDeals(listOf("SAMPLE DEAL")) + this.session.flush() + + // consume stuff + consumeCash(100.DOLLARS) + vaultFiller.consumeDeals(dealStates.toList()) + vaultFiller.consumeLinearStates(linearStates.toList()) + + updates + } + + updates.expectEvents { + sequence( + expect { (consumed, produced, flowId) -> + require(flowId == null) {} + require(consumed.isEmpty()) {} + require(produced.size == 10) {} + }, + expect { (consumed, produced, flowId) -> + require(flowId == null) {} + require(consumed.isEmpty()) {} + require(produced.size == 3) {} + }, + expect { (consumed, produced, flowId) -> + require(flowId == null) {} + require(consumed.isEmpty()) {} + require(produced.size == 1) {} + } + ) + } + } + + @Test + fun trackDealStates() { + val updates = database.transaction { + // DOCSTART VaultQueryExample17 + val (snapshot, updates) = vaultService.trackBy() + // DOCEND VaultQueryExample17 + assertThat(snapshot.states).hasSize(0) + + vaultFiller.fillWithSomeTestCash(100.DOLLARS, notaryServices, 3, DUMMY_CASH_ISSUER) + val linearStates = vaultFiller.fillWithSomeTestLinearStates(10).states + val dealStates = vaultFiller.fillWithSomeTestDeals(listOf("123", "456", "789")).states + // add more cash + vaultFiller.fillWithSomeTestCash(100.POUNDS, notaryServices, 1, DUMMY_CASH_ISSUER) + // add another deal + vaultFiller.fillWithSomeTestDeals(listOf("SAMPLE DEAL")) + this.session.flush() + + // consume stuff + consumeCash(100.DOLLARS) + vaultFiller.consumeDeals(dealStates.toList()) + vaultFiller.consumeLinearStates(linearStates.toList()) + + updates + } + + updates.expectEvents { + sequence( + expect { (consumed, produced, flowId) -> + require(flowId == null) {} + require(consumed.isEmpty()) {} + require(produced.size == 3) {} + }, + expect { (consumed, produced, flowId) -> + require(flowId == null) {} + require(consumed.isEmpty()) {} + require(produced.size == 1) {} + } + ) + } } } \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt b/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt index ab4227230b..c2986223cb 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt @@ -5,8 +5,8 @@ import net.corda.core.internal.bufferUntilSubscribed import net.corda.core.internal.tee import net.corda.node.internal.configureDatabase import net.corda.nodeapi.internal.persistence.* -import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.internal.rigorousMock +import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Test @@ -14,6 +14,7 @@ import rx.Observable import rx.subjects.PublishSubject import java.io.Closeable import java.util.* +import kotlin.test.fail class ObservablesTests { private fun isInDatabaseTransaction() = contextTransactionOrNull != null @@ -58,6 +59,72 @@ class ObservablesTests { assertThat(secondEvent.get()).isEqualTo(0 to false) } + class TestException : Exception("Synthetic exception for tests") {} + + @Test + fun `bufferUntilDatabaseCommit swallows if transaction rolled back`() { + val database = createDatabase() + + val source = PublishSubject.create() + val observable: Observable = source + + val firstEvent = SettableFuture.create>() + val secondEvent = SettableFuture.create>() + + observable.first().subscribe { firstEvent.set(it to isInDatabaseTransaction()) } + observable.skip(1).first().subscribe { secondEvent.set(it to isInDatabaseTransaction()) } + + try { + database.transaction { + val delayedSubject = source.bufferUntilDatabaseCommit() + assertThat(source).isNotEqualTo(delayedSubject) + delayedSubject.onNext(0) + source.onNext(1) + assertThat(firstEvent.isDone).isTrue() + assertThat(secondEvent.isDone).isFalse() + throw TestException() + } + fail("Should not have successfully completed transaction") + } catch (e: TestException) { + } + assertThat(secondEvent.isDone).isFalse() + + assertThat(firstEvent.get()).isEqualTo(1 to true) + } + + @Test + fun `bufferUntilDatabaseCommit propagates error if transaction rolled back`() { + val database = createDatabase() + + val source = PublishSubject.create() + val observable: Observable = source + + val firstEvent = SettableFuture.create>() + val secondEvent = SettableFuture.create>() + + observable.first().subscribe({ firstEvent.set(it to isInDatabaseTransaction()) }, {}) + observable.skip(1).subscribe({ secondEvent.set(it to isInDatabaseTransaction()) }, {}) + observable.skip(1).subscribe({}, { secondEvent.set(2 to isInDatabaseTransaction()) }) + + try { + database.transaction { + val delayedSubject = source.bufferUntilDatabaseCommit(propagateRollbackAsError = true) + assertThat(source).isNotEqualTo(delayedSubject) + delayedSubject.onNext(0) + source.onNext(1) + assertThat(firstEvent.isDone).isTrue() + assertThat(secondEvent.isDone).isFalse() + throw TestException() + } + fail("Should not have successfully completed transaction") + } catch (e: TestException) { + } + assertThat(secondEvent.isDone).isTrue() + + assertThat(firstEvent.get()).isEqualTo(1 to true) + assertThat(secondEvent.get()).isEqualTo(2 to false) + } + @Test fun `bufferUntilDatabaseCommit delays until transaction closed repeatable`() { val database = createDatabase() diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt index 5fe858355f..a67fd09f35 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt @@ -20,6 +20,8 @@ import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.trace import net.corda.node.services.messaging.* import net.corda.node.services.statemachine.DeduplicationId +import net.corda.node.services.statemachine.ExternalEvent +import net.corda.node.services.statemachine.SenderDeduplicationId import net.corda.node.utilities.AffinityExecutor import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.testing.node.internal.InMemoryMessage @@ -108,7 +110,7 @@ class InMemoryMessagingNetwork private constructor( get() = _receivedMessages internal val endpoints: List @Synchronized get() = handleEndpointMap.values.toList() /** Get a [List] of all the [MockMessagingService] endpoints **/ - val endpointsExternal: List @Synchronized get() = handleEndpointMap.values.map{ MockMessagingService.createMockMessagingService(it) }.toList() + val endpointsExternal: List @Synchronized get() = handleEndpointMap.values.map { MockMessagingService.createMockMessagingService(it) }.toList() /** * Creates a node at the given address: useful if you want to recreate a node to simulate a restart. @@ -135,7 +137,10 @@ class InMemoryMessagingNetwork private constructor( ?: emptyList() //TODO only notary can be distributed? synchronized(this) { val node = InMemoryMessaging(manuallyPumped, peerHandle, executor, database) - handleEndpointMap[peerHandle] = node + val oldNode = handleEndpointMap.put(peerHandle, node) + if (oldNode != null) { + node.inheritPendingRedelivery(oldNode) + } serviceHandles.forEach { serviceToPeersMapping.getOrPut(it) { LinkedHashSet() }.add(peerHandle) } @@ -161,7 +166,10 @@ class InMemoryMessagingNetwork private constructor( @Synchronized private fun netNodeHasShutdown(peerHandle: PeerHandle) { - handleEndpointMap.remove(peerHandle) + val endpoint = handleEndpointMap[peerHandle] + if (!(endpoint?.hasPendingDeliveries() ?: false)) { + handleEndpointMap.remove(peerHandle) + } } @Synchronized @@ -266,6 +274,30 @@ class InMemoryMessagingNetwork private constructor( return transfer } + /** + * When a new message handler is added, this implies we have started a new node. The add handler logic uses this to + * push back any un-acknowledged messages for this peer onto the head of the queue (rather than the tail) to maintain message + * delivery order. We push them back because their consumption was not complete and a restarted node would + * see them re-delivered if this was Artemis. + */ + @Synchronized + private fun unPopMessages(transfers: Collection, us: PeerHandle) { + messageReceiveQueues.compute(us) { _, existing -> + if (existing == null) { + LinkedBlockingQueue().apply { + addAll(transfers) + } + } else { + existing.apply { + val drained = mutableListOf() + existing.drainTo(drained) + existing.addAll(transfers) + existing.addAll(drained) + } + } + } + } + private fun pumpSendInternal(transfer: MessageTransfer) { when (transfer.recipients) { is PeerHandle -> getQueueForPeerHandle(transfer.recipients).add(transfer) @@ -338,6 +370,7 @@ class InMemoryMessagingNetwork private constructor( private val processedMessages: MutableSet = Collections.synchronizedSet(HashSet()) override val myAddress: PeerHandle get() = peerHandle + override val ourSenderUUID: String = UUID.randomUUID().toString() private val backgroundThread = if (manuallyPumped) null else thread(isDaemon = true, name = "In-memory message dispatcher") { @@ -370,10 +403,16 @@ class InMemoryMessagingNetwork private constructor( Pair(handler, pending) } - transfers.forEach { pumpSendInternal(it) } + unPopMessages(transfers, peerHandle) return handler } + fun inheritPendingRedelivery(other: InMemoryMessaging) { + state.locked { + pendingRedelivery.addAll(other.state.locked { pendingRedelivery }) + } + } + override fun removeMessageHandler(registration: MessageHandlerRegistration) { check(running) state.locked { check(handlers.remove(registration as Handler)) } @@ -405,8 +444,8 @@ class InMemoryMessagingNetwork private constructor( override fun cancelRedelivery(retryId: Long) {} /** Returns the given (topic & session, data) pair as a newly created message object. */ - override fun createMessage(topic: String, data: ByteArray, deduplicationId: DeduplicationId, additionalHeaders: Map): Message { - return InMemoryMessage(topic, OpaqueBytes(data), deduplicationId) + override fun createMessage(topic: String, data: ByteArray, deduplicationId: SenderDeduplicationId, additionalHeaders: Map): Message { + return InMemoryMessage(topic, OpaqueBytes(data), deduplicationId.deduplicationId, senderUUID = deduplicationId.senderUUID) } /** @@ -470,13 +509,14 @@ class InMemoryMessagingNetwork private constructor( database.transaction { for (handler in deliverTo) { try { - handler.callback(transfer.toReceivedMessage(), handler, DummyDeduplicationHandler()) + val receivedMessage = transfer.toReceivedMessage() + state.locked { pendingRedelivery.add(transfer) } + handler.callback(receivedMessage, handler, InMemoryDeduplicationHandler(receivedMessage, transfer)) } catch (e: Exception) { log.error("Caught exception in handler for $this/${handler.topicSession}", e) } } _receivedMessages.onNext(transfer) - processedMessages += transfer.message.uniqueMessageId messagesInFlight.countDown() } } @@ -493,13 +533,23 @@ class InMemoryMessagingNetwork private constructor( message.uniqueMessageId, message.debugTimestamp, sender.name) - } - private class DummyDeduplicationHandler : DeduplicationHandler { - override fun afterDatabaseTransaction() { - } - override fun insideDatabaseTransaction() { + private inner class InMemoryDeduplicationHandler(override val receivedMessage: ReceivedMessage, val transfer: MessageTransfer) : DeduplicationHandler, ExternalEvent.ExternalMessageEvent { + override val externalCause: ExternalEvent + get() = this + override val deduplicationHandler: DeduplicationHandler + get() = this + + override fun afterDatabaseTransaction() { + this@InMemoryMessaging.state.locked { pendingRedelivery.remove(transfer) } + } + + override fun insideDatabaseTransaction() { + processedMessages += transfer.message.uniqueMessageId + } } + + fun hasPendingDeliveries(): Boolean = state.locked { pendingRedelivery.isNotEmpty() } } }