diff --git a/core/src/main/kotlin/net/corda/core/node/AutoAcceptable.kt b/core/src/main/kotlin/net/corda/core/node/AutoAcceptable.kt new file mode 100644 index 0000000000..bf868cbf19 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/node/AutoAcceptable.kt @@ -0,0 +1,10 @@ +package net.corda.core.node + +/** + * Tagging a network parameter with this annotation signifies that any update which modifies the parameter could be accepted without + * the need for the node operator to run a manual accept command. If the update contains changes for any non-auto-acceptable parameter + * or the behaviour is switched off via configuration then it will not be auto-accepted + */ +@Target(AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.RUNTIME) +annotation class AutoAcceptable diff --git a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt index 2c1c807633..9a71d84f90 100644 --- a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt +++ b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt @@ -8,10 +8,16 @@ import net.corda.core.node.services.AttachmentId import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.utilities.days +import java.lang.reflect.Method import java.security.PublicKey import java.time.Duration import java.time.Instant +import kotlin.reflect.KProperty1 +import kotlin.reflect.full.declaredMemberProperties +import kotlin.reflect.full.findAnnotation +import kotlin.reflect.jvm.javaGetter +// DOCSTART 1 /** * Network parameters are a set of values that every node participating in the zone needs to agree on and use to * correctly interoperate with each other. @@ -19,12 +25,14 @@ import java.time.Instant * @property notaries List of well known and trusted notary identities with information on validation type. * @property maxMessageSize This is currently ignored. However, it will be wired up in a future release. * @property maxTransactionSize Maximum permitted transaction size in bytes. - * @property modifiedTime Last modification time of network parameters set. - * @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set + * @property modifiedTime ([AutoAcceptable]) Last modification time of network parameters set. + * @property epoch ([AutoAcceptable]) Version number of the network parameters. Starting from 1, this will always increment on each new set * of parameters. - * @property whitelistedContractImplementations List of whitelisted jars containing contract code for each contract class. - * This will be used by [net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint]. [You can learn more about contract constraints here](https://docs.corda.net/api-contract-constraints.html). - * @property packageOwnership List of the network-wide java packages that were successfully claimed by their owners. Any CorDapp JAR that offers contracts and states in any of these packages must be signed by the owner. + * @property whitelistedContractImplementations ([AutoAcceptable]) List of whitelisted jars containing contract code for each contract class. + * This will be used by [net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint]. + * [You can learn more about contract constraints here](https://docs.corda.net/api-contract-constraints.html). + * @property packageOwnership ([AutoAcceptable]) List of the network-wide java packages that were successfully claimed by their owners. + * Any CorDapp JAR that offers contracts and states in any of these packages must be signed by the owner. * @property eventHorizon Time after which nodes will be removed from the network map if they have not been seen * during this period */ @@ -35,12 +43,13 @@ data class NetworkParameters( val notaries: List, val maxMessageSize: Int, val maxTransactionSize: Int, - val modifiedTime: Instant, - val epoch: Int, - val whitelistedContractImplementations: Map>, + @AutoAcceptable val modifiedTime: Instant, + @AutoAcceptable val epoch: Int, + @AutoAcceptable val whitelistedContractImplementations: Map>, val eventHorizon: Duration, - val packageOwnership: Map + @AutoAcceptable val packageOwnership: Map ) { + // DOCEND 1 @DeprecatedConstructorForDeserialization(1) constructor (minimumPlatformVersion: Int, notaries: List, @@ -80,6 +89,14 @@ data class NetworkParameters( emptyMap() ) + companion object { + private val memberPropertyPartition = NetworkParameters::class.declaredMemberProperties.asSequence() + .partition { it.isAutoAcceptable() } + private val autoAcceptableNamesAndGetters = memberPropertyPartition.first.associateBy({it.name}, {it.javaGetter}) + private val nonAutoAcceptableGetters = memberPropertyPartition.second.map { it.javaGetter } + val autoAcceptablePropertyNames = autoAcceptableNamesAndGetters.keys + } + init { require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" } require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" } @@ -149,6 +166,21 @@ data class NetworkParameters( * Returns the public key of the package owner of the [contractClassName], or null if not owned. */ fun getOwnerOf(contractClassName: String): PublicKey? = this.packageOwnership.filterKeys { it.owns(contractClassName) }.values.singleOrNull() + + /** + * Returns true if the only properties changed in [newNetworkParameters] are [AutoAcceptable] and not + * included in the [excludedParameterNames] + */ + fun canAutoAccept(newNetworkParameters: NetworkParameters, excludedParameterNames: Set): Boolean { + return nonAutoAcceptableGetters.none { valueChanged(newNetworkParameters, it) } && + autoAcceptableNamesAndGetters.none { excludedParameterNames.contains(it.key) && valueChanged(newNetworkParameters, it.value) } + } + + private fun valueChanged(newNetworkParameters: NetworkParameters, getter: Method?): Boolean { + val propertyValue = getter?.invoke(this) + val newPropertyValue = getter?.invoke(newNetworkParameters) + return propertyValue != newPropertyValue + } } /** @@ -196,3 +228,7 @@ private fun isPackageValid(packageName: String): Boolean = packageName.isNotEmpt private fun noOverlap(packages: Collection) = packages.all { currentPackage -> packages.none { otherPackage -> otherPackage != currentPackage && otherPackage.name.startsWith("${currentPackage.name}.") } } + +private fun KProperty1.isAutoAcceptable(): Boolean { + return this.findAnnotation() != null +} \ No newline at end of file diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 912da63c6f..b246d33e41 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -7,6 +7,9 @@ release, see :doc:`upgrade-notes`. Unreleased ---------- +* Added auto-acceptance of network parameters for network updates. This behaviour is available for a subset of the network parameters + and is configurable via the node config. See :doc:`network-map` for more information. + * Deprecated `SerializationContext.withAttachmentsClassLoader`. This functionality has always been disabled by flags and there is no reason for a CorDapp developer to use it. It is just an internal implementation detail of Corda. @@ -279,6 +282,12 @@ and there is no reason for a CorDapp developer to use it. It is just an internal * `issuer_ref` column in `FungibleStateSchema` was updated to be nullable to support the introduction of the `FungibleState` interface. The `vault_fungible_states` table can hold both `FungibleAssets` and `FungibleStates`. +* CorDapps built by ``corda-gradle-plugins`` are now signed and sealed JAR files. + Signing can be configured or disabled, and it defaults to using the Corda development certificate. + +* Finance CorDapp is now build as a sealed and signed JAR file. + Custom classes can no longer be placed in the packages defined in Finance Cordapp or access it's non-public members. + Version 3.3 ----------- diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index 8cf7d1e452..19487a73c8 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -330,6 +330,9 @@ The available config fields are listed below. The option takes effect only in production mode and defaults to Corda development keys (``["56CA54E803CB87C8472EBD3FBC6A2F1876E814CEEBF74860BD46997F40729367", "83088052AF16700457AE2C978A7D8AC38DD6A7C713539D00B897CD03A5E5D31D"]``), in development mode any key is allowed to sign Cordpapp JARs. +:autoAcceptNetworkParameterChanges: This flag toggles auto accepting of network parameter changes and is enabled by default. If a network operator issues a network parameter change which modifies + only auto-acceptable options and this behaviour is enabled then the changes will be accepted without any manual intervention from the node operator. See + :doc:`network-map` for more information on the update process and current auto-acceptable parameters. Set to ``false`` to disable. Examples -------- diff --git a/docs/source/network-map.rst b/docs/source/network-map.rst index 28a19cd0d8..af93c035ca 100644 --- a/docs/source/network-map.rst +++ b/docs/source/network-map.rst @@ -159,14 +159,55 @@ The fact a new set of parameters is being advertised shows up in the node logs w the ``CordaRPCOps.networkParametersFeed`` method. Typically a zone operator would also email node operators to let them know about the details of the impending change, along with the justification, how to object, deadlines and so on. -.. container:: codeset +.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt + :language: kotlin + :start-after: DOCSTART 1 + :end-before: DOCEND 1 - .. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt - :language: kotlin - :start-after: DOCSTART 1 - :end-before: DOCEND 1 +Auto Acceptance +``````````````` -The node administrator can review the change and decide if they are going to accept it. The approval should be do +If the only changes between the current and new parameters are for auto-acceptable parameters then, unless configured otherwise, the new +parameters will be accepted without user input. The following parameters with the ``@AutoAcceptable`` annotation are auto-acceptable: + +.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt + :language: kotlin + :start-after: DOCSTART 1 + :end-before: DOCEND 1 + +This behaviour can be turned off by setting the optional node configuration property ``NetworkParameterAcceptanceSettings.autoAcceptEnabled`` +to ``false``. For example: + +.. sourcecode:: guess + + ... + NetworkParameterAcceptanceSettings { + autoAcceptEnabled = false + } + ... + +It is also possible to switch off this behaviour at a more granular parameter level. This can be achieved by specifying the set of +``@AutoAcceptable`` parameters that should not be auto-acceptable in the optional +``NetworkParameterAcceptanceSettings.excludedAutoAcceptableParameters`` node configuration property. + +For example, auto-acceptance can be switched off for any updates that change the ``packageOwnership`` map by adding the following to the +node configuration: + +.. sourcecode:: guess + + ... + NetworkParameterAcceptanceSettings { + excludedAutoAcceptableParameters: ["packageOwnership"] + } + ... + +Manual Acceptance +````````````````` + +If the auto-acceptance behaviour is turned off via the configuration or the network parameters change involves parameters that are +not auto-acceptable then manual approval is required. + +In this case the node administrator can review the change and decide if they are going to accept it. The approval should be done before the update Deadline. Nodes that don't approve before the deadline will likely be removed from the network map by the zone operator, but that is a decision that is left to the operator's discretion. For example the operator might also choose to change the deadline instead. diff --git a/docs/source/release-notes.rst b/docs/source/release-notes.rst index 9975c00816..0713dc5eca 100644 --- a/docs/source/release-notes.rst +++ b/docs/source/release-notes.rst @@ -21,6 +21,17 @@ Significant Changes in 4.0 for "current-ness". In other words, the contract logic isn't run for the referencing transaction only. It's still a normal state when it occurs in an input or output position. +* **Added auto-acceptance for network parameters updates** + Added auto-accepting for a subset of network parameters, negating the need for a node operator to manually run an accept + command on every parameter update. This behaviour can be turned off via the node configuration. + +* **CorDapp JAR Signing and Sealing**: + + CorDapps built by corda-gradle-plugins are now signed and sealed JAR files. + Signing can be configured or disabled, and it defaults to using the Corda development certificate. + Signed CorDapps facilitate signature constraints checks. + Sealed JARs require a unique package to be shipped within a single CorDapp JAR. Sealing can be disabled. + << MORE TO COME >> .. _release_notes_v3_3: diff --git a/node/src/integration-test/kotlin/net/corda/node/AddressBindingFailureTests.kt b/node/src/integration-test/kotlin/net/corda/node/AddressBindingFailureTests.kt index 3269a4def1..506572bdac 100644 --- a/node/src/integration-test/kotlin/net/corda/node/AddressBindingFailureTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/AddressBindingFailureTests.kt @@ -1,5 +1,6 @@ package net.corda.node +import net.corda.core.identity.CordaX500Name import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.getOrThrow import net.corda.core.internal.errors.AddressBindingException @@ -12,6 +13,7 @@ import net.corda.testing.driver.driver import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.IntegrationTestSchemas import net.corda.testing.internal.toDatabaseSchemaName +import net.corda.testing.node.NotarySpec import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Assume.assumeTrue @@ -45,6 +47,25 @@ class AddressBindingFailureTests: IntegrationTest() { assertBindExceptionForOverrides { address -> mapOf("h2Settings" to mapOf("address" to address.toString()), "dataSourceProperties.dataSource.password" to "password") } } + @Test + fun `notary P2P address`() { + ServerSocket(0).use { socket -> + + val notaryName = CordaX500Name.parse("O=Notary Cleaning Service, L=Zurich, C=CH") + val address = InetSocketAddress(socket.localPort).toNetworkHostAndPort() + + assertThatThrownBy { + driver(DriverParameters(startNodesInProcess = false, + notarySpecs = listOf(NotarySpec(notaryName)), + notaryCustomOverrides = mapOf("p2pAddress" to address.toString()), + portAllocation = portAllocation) + ) {} }.isInstanceOfSatisfying(IllegalStateException::class.java) { error -> + + assertThat(error.message).contains("Unable to start notaries") + } + } + } + private fun assertBindExceptionForOverrides(overrides: (NetworkHostAndPort) -> Map) { ServerSocket(0).use { socket -> 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 8f527468e5..4b08748b09 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 @@ -104,7 +104,10 @@ class NetworkMapTest(var initFunc: (URL, NetworkMapServer) -> CompatibilityZoneP notarySpecs = emptyList() ) { val alice = startNode(providedName = ALICE_NAME, devMode = false).getOrThrow() as NodeHandleInternal - val nextParams = networkMapServer.networkParameters.copy(epoch = 3, modifiedTime = Instant.ofEpochMilli(random63BitValue())) + val nextParams = networkMapServer.networkParameters.copy( + epoch = 3, + modifiedTime = Instant.ofEpochMilli(random63BitValue()), + maxMessageSize = networkMapServer.networkParameters.maxMessageSize + 1) val nextHash = nextParams.serialize().hash val snapshot = alice.rpc.networkParametersFeed().snapshot val updates = alice.rpc.networkParametersFeed().updates.bufferUntilSubscribed() @@ -113,7 +116,10 @@ class NetworkMapTest(var initFunc: (URL, NetworkMapServer) -> CompatibilityZoneP networkMapServer.scheduleParametersUpdate(nextParams, "Next parameters", Instant.ofEpochMilli(random63BitValue())) // Wait for network map client to poll for the next update. Thread.sleep(cacheTimeout.toMillis() * 2) - val laterParams = networkMapServer.networkParameters.copy(epoch = 4, modifiedTime = Instant.ofEpochMilli(random63BitValue())) + val laterParams = networkMapServer.networkParameters.copy( + epoch = 4, + modifiedTime = Instant.ofEpochMilli(random63BitValue()), + maxMessageSize = nextParams.maxMessageSize + 1) val laterHash = laterParams.serialize().hash networkMapServer.scheduleParametersUpdate(laterParams, "Another update", Instant.ofEpochMilli(random63BitValue())) Thread.sleep(cacheTimeout.toMillis() * 2) 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 22c5785519..0344a734e7 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -359,7 +359,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val (nodeInfo, signedNodeInfo) = nodeInfoAndSigned identityService.ourNames = nodeInfo.legalIdentities.map { it.name }.toSet() services.start(nodeInfo, netParams) - networkMapUpdater.start(trustRoot, signedNetParams.raw.hash, signedNodeInfo.raw.hash) + networkMapUpdater.start( + trustRoot, + signedNetParams.raw.hash, + signedNodeInfo, + netParams, + keyManagementService, + configuration.networkParameterAcceptanceSettings) startMessagingService(rpcOps, nodeInfo, myNotaryIdentity, netParams) // Do all of this in a database transaction so anything that might need a connection has one. 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 18c1830c8d..209d3ffde3 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 @@ -91,6 +91,8 @@ interface NodeConfiguration { val cryptoServiceName: SupportedCryptoServices? val cryptoServiceConf: String? // Location for the cryptoService conf file. + val networkParameterAcceptanceSettings: NetworkParameterAcceptanceSettings + companion object { // default to at least 8MB and a bit extra for larger heap sizes internal val defaultTransactionCacheSize: Long = 8.MB + getAdditionalCacheMemory() @@ -192,6 +194,19 @@ data class NetworkServicesConfig( val inferred: Boolean = false ) +/** + * Specifies the auto-acceptance behaviour for network parameter updates + * + * @property autoAcceptEnabled Specifies whether network parameter auto-accepting is enabled. Only parameters annotated with the + * AutoAcceptable annotation can be auto-accepted. + * @property excludedAutoAcceptableParameters Set of parameters to explicitly exclude from auto-accepting. Note that if [autoAcceptEnabled] + * is false then this parameter is redundant. + */ +data class NetworkParameterAcceptanceSettings( + val autoAcceptEnabled: Boolean = true, + val excludedAutoAcceptableParameters: Set = emptySet() +) + /** * Currently only used for notarisation requests. * diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt index 7ad3084792..4cd571763d 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt @@ -82,7 +82,8 @@ data class NodeConfigurationImpl( override val flowOverrides: FlowOverrideConfig?, override val cordappSignerKeyFingerprintBlacklist: List = Defaults.cordappSignerKeyFingerprintBlacklist, override val cryptoServiceName: SupportedCryptoServices? = Defaults.cryptoServiceName, - override val cryptoServiceConf: String? = Defaults.cryptoServiceConf + override val cryptoServiceConf: String? = Defaults.cryptoServiceConf, + override val networkParameterAcceptanceSettings: NetworkParameterAcceptanceSettings = Defaults.networkParameterAcceptanceSettings ) : NodeConfiguration { internal object Defaults { val jmxMonitoringHttpPort: Int? = null @@ -119,6 +120,7 @@ data class NodeConfigurationImpl( val useOpenSsl: Boolean = false val cryptoServiceName: SupportedCryptoServices? = null val cryptoServiceConf: String? = null + val networkParameterAcceptanceSettings: NetworkParameterAcceptanceSettings = NetworkParameterAcceptanceSettings() fun cordappsDirectories(baseDirectory: Path) = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT) diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt index 6536f5869c..66355758c7 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt @@ -4,18 +4,19 @@ import com.google.common.util.concurrent.MoreExecutors import net.corda.core.CordaRuntimeException import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SignedData -import net.corda.core.internal.copyTo -import net.corda.core.internal.div -import net.corda.core.internal.exists -import net.corda.core.internal.readObject +import net.corda.core.internal.* import net.corda.core.messaging.DataFeed import net.corda.core.messaging.ParametersUpdateInfo +import net.corda.core.node.NetworkParameters +import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.serialize import net.corda.core.utilities.contextLogger import net.corda.core.utilities.minutes import net.corda.node.services.api.NetworkMapCacheInternal +import net.corda.node.services.config.NetworkParameterAcceptanceSettings import net.corda.node.utilities.NamedThreadFactory import net.corda.nodeapi.exceptions.OutdatedNetworkParameterHashException +import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.network.* import rx.Subscription import rx.subjects.PublishSubject @@ -45,20 +46,40 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, } private var newNetworkParameters: Pair? = null private var fileWatcherSubscription: Subscription? = null + private var autoAcceptNetworkParameters: Boolean = true private lateinit var trustRoot: X509Certificate private lateinit var currentParametersHash: SecureHash + private lateinit var ourNodeInfo: SignedNodeInfo private lateinit var ourNodeInfoHash: SecureHash + private lateinit var networkParameters: NetworkParameters + private lateinit var keyManagementService: KeyManagementService + private lateinit var excludedAutoAcceptNetworkParameters: Set override fun close() { fileWatcherSubscription?.unsubscribe() MoreExecutors.shutdownAndAwaitTermination(networkMapPoller, 50, TimeUnit.SECONDS) } - fun start(trustRoot: X509Certificate, currentParametersHash: SecureHash, ourNodeInfoHash: SecureHash) { + fun start(trustRoot: X509Certificate, + currentParametersHash: SecureHash, + ourNodeInfo: SignedNodeInfo, + networkParameters: NetworkParameters, + keyManagementService: KeyManagementService, + networkParameterAcceptanceSettings: NetworkParameterAcceptanceSettings) { require(fileWatcherSubscription == null) { "Should not call this method twice." } this.trustRoot = trustRoot this.currentParametersHash = currentParametersHash - this.ourNodeInfoHash = ourNodeInfoHash + this.ourNodeInfo = ourNodeInfo + this.ourNodeInfoHash = ourNodeInfo.raw.hash + this.networkParameters = networkParameters + this.keyManagementService = keyManagementService + this.autoAcceptNetworkParameters = networkParameterAcceptanceSettings.autoAcceptEnabled + this.excludedAutoAcceptNetworkParameters = networkParameterAcceptanceSettings.excludedAutoAcceptableParameters + + val autoAcceptNetworkParametersNames = NetworkParameters.autoAcceptablePropertyNames - excludedAutoAcceptNetworkParameters + if (autoAcceptNetworkParameters && autoAcceptNetworkParametersNames.isNotEmpty()) { + logger.info("Auto-accept enabled for network parameter changes which modify only: $autoAcceptNetworkParametersNames") + } watchForNodeInfoFiles() if (networkMapClient != null) { watchHttpNetworkMap() @@ -195,7 +216,15 @@ The node will shutdown now.""") newNetParams, update.description, update.updateDeadline) - parametersUpdatesTrack.onNext(updateInfo) + + if (autoAcceptNetworkParameters && networkParameters.canAutoAccept(newNetParams, excludedAutoAcceptNetworkParameters)) { + logger.info("Auto-accepting network parameter update ${update.newParametersHash}") + acceptNewNetworkParameters(update.newParametersHash) { hash -> + hash.serialize().sign { keyManagementService.sign(it.bytes, ourNodeInfo.verified().legalIdentities[0].owningKey) } + } + } else { + parametersUpdatesTrack.onNext(updateInfo) + } } fun acceptNewNetworkParameters(parametersHash: SecureHash, sign: (SecureHash) -> SignedData) { diff --git a/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt b/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt index 09faa02886..4be3c41682 100644 --- a/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt @@ -4,6 +4,7 @@ import net.corda.core.crypto.generateKeyPair import net.corda.core.node.JavaPackageName import net.corda.core.node.NetworkParameters import net.corda.core.node.NotaryInfo +import net.corda.core.node.services.AttachmentId import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.days import net.corda.core.utilities.getOrThrow @@ -129,7 +130,42 @@ class NetworkParametersTest { assert(JavaPackageName("com.example").owns("com.example.something.MyClass")) assert(!JavaPackageName("com.example").owns("com.examplesomething.MyClass")) assert(!JavaPackageName("com.exam").owns("com.example.something.MyClass")) + } + @Test + fun `auto acceptance checks are correct`() { + val packageOwnership = mapOf( + JavaPackageName("com.example1") to generateKeyPair().public, + JavaPackageName("com.example2") to generateKeyPair().public) + val whitelistedContractImplementations = mapOf( + "example1" to listOf(AttachmentId.randomSHA256()), + "example2" to listOf(AttachmentId.randomSHA256())) + + val netParams = testNetworkParameters() + val netParamsAutoAcceptable = netParams.copy( + packageOwnership = packageOwnership, + whitelistedContractImplementations = whitelistedContractImplementations) + val netParamsNotAutoAcceptable = netParamsAutoAcceptable.copy( + maxMessageSize = netParams.maxMessageSize + 1) + + assert(netParams.canAutoAccept(netParams, emptySet())) { + "auto-acceptable if identical" + } + assert(netParams.canAutoAccept(netParams, NetworkParameters.autoAcceptablePropertyNames)) { + "auto acceptable if identical regardless of exclusions" + } + assert(netParams.canAutoAccept(netParamsAutoAcceptable, emptySet())) { + "auto-acceptable if only AutoAcceptable params have changed" + } + assert(netParams.canAutoAccept(netParamsAutoAcceptable, setOf("modifiedTime"))) { + "auto-acceptable if only AutoAcceptable params have changed and excluded param has not changed" + } + assert(!netParams.canAutoAccept(netParamsNotAutoAcceptable, emptySet())) { + "not auto-acceptable if non-AutoAcceptable param has changed" + } + assert(!netParams.canAutoAccept(netParamsAutoAcceptable, setOf("whitelistedContractImplementations"))) { + "not auto-acceptable if only AutoAcceptable params have changed but one has been added to the exclusion set" + } } // Helpers diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt index bbe4812c8d..b6451f3217 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt @@ -11,13 +11,17 @@ import net.corda.core.identity.Party import net.corda.core.internal.* import net.corda.core.internal.concurrent.openFuture import net.corda.core.messaging.ParametersUpdateInfo +import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo import net.corda.core.serialization.serialize import net.corda.core.utilities.millis import net.corda.node.VersionInfo import net.corda.node.services.api.NetworkMapCacheInternal +import net.corda.node.services.config.NetworkParameterAcceptanceSettings import net.corda.core.internal.NODE_INFO_DIRECTORY import net.corda.nodeapi.internal.NodeInfoAndSigned +import net.corda.nodeapi.internal.SignedNodeInfo +import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME import net.corda.nodeapi.internal.network.NodeInfoFilesCopier import net.corda.nodeapi.internal.network.SignedNetworkParameters @@ -27,7 +31,9 @@ import net.corda.testing.core.* import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.internal.TestNodeInfoBuilder import net.corda.testing.internal.createNodeInfoAndSigned +import net.corda.testing.node.internal.MockKeyManagementService import net.corda.testing.node.internal.network.NetworkMapServer +import net.corda.testing.node.makeTestIdentityService import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.After @@ -37,6 +43,7 @@ import org.junit.Test import rx.schedulers.TestScheduler import java.io.IOException import java.net.URL +import java.security.KeyPair import java.time.Instant import java.time.temporal.ChronoUnit import java.util.* @@ -58,12 +65,16 @@ class NetworkMapUpdaterTest { private val fileWatcher = NodeInfoWatcher(baseDir, scheduler) private val nodeReadyFuture = openFuture() private val networkMapCache = createMockNetworkMapCache() + private lateinit var ourKeyPair: KeyPair + private lateinit var ourNodeInfo: SignedNodeInfo private lateinit var server: NetworkMapServer private lateinit var networkMapClient: NetworkMapClient private lateinit var updater: NetworkMapUpdater @Before fun setUp() { + ourKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + ourNodeInfo = createNodeInfoAndSigned("Our info", ourKeyPair).signed server = NetworkMapServer(cacheExpiryMs.millis) val address = server.start() networkMapClient = NetworkMapClient(URL("http://$address"), @@ -81,8 +92,16 @@ class NetworkMapUpdaterTest { updater = NetworkMapUpdater(networkMapCache, fileWatcher, netMapClient, baseDir, extraNetworkMapKeys) } - private fun startUpdater(ourNodeHash: SecureHash = SecureHash.randomSHA256()) { - updater.start(DEV_ROOT_CA.certificate, server.networkParameters.serialize().hash, ourNodeHash) + private fun startUpdater(ourNodeInfo: SignedNodeInfo = this.ourNodeInfo, + networkParameters: NetworkParameters = server.networkParameters, + autoAcceptNetworkParameters: Boolean = true, + excludedAutoAcceptNetworkParameters: Set = emptySet()) { + updater.start(DEV_ROOT_CA.certificate, + server.networkParameters.serialize().hash, + ourNodeInfo, + networkParameters, + MockKeyManagementService(makeTestIdentityService(), ourKeyPair), + NetworkParameterAcceptanceSettings(autoAcceptNetworkParameters, excludedAutoAcceptNetworkParameters)) } @Test @@ -192,7 +211,7 @@ class NetworkMapUpdaterTest { val snapshot = paramsFeed.snapshot val updates = paramsFeed.updates.bufferUntilSubscribed() assertEquals(null, snapshot) - val newParameters = testNetworkParameters(epoch = 2) + val newParameters = testNetworkParameters(epoch = 2, maxMessageSize = 10485761) val updateDeadline = Instant.now().plus(1, ChronoUnit.DAYS) server.scheduleParametersUpdate(newParameters, "Test update", updateDeadline) startUpdater() @@ -211,18 +230,65 @@ class NetworkMapUpdaterTest { @Test fun `ack network parameters update`() { setUpdater() - val newParameters = testNetworkParameters(epoch = 314) + val newParameters = testNetworkParameters(epoch = 314, maxMessageSize = 10485761) + server.scheduleParametersUpdate(newParameters, "Test update", Instant.MIN) + startUpdater() + // TODO: Remove sleep in unit test. + Thread.sleep(2L * cacheExpiryMs) + val newHash = newParameters.serialize().hash + val updateFile = baseDir / NETWORK_PARAMS_UPDATE_FILE_NAME + assert(!updateFile.exists()) { "network parameters should not be auto accepted" } + updater.acceptNewNetworkParameters(newHash) { it.serialize().sign(ourKeyPair) } + val signedNetworkParams = updateFile.readObject() + val paramsFromFile = signedNetworkParams.verifiedNetworkMapCert(DEV_ROOT_CA.certificate) + assertEquals(newParameters, paramsFromFile) + assertEquals(newHash, server.latestParametersAccepted(ourKeyPair.public)) + } + + @Test + fun `network parameters auto-accepted when update only changes whitelist`() { + setUpdater() + val newParameters = testNetworkParameters( + epoch = 314, + whitelistedContractImplementations = mapOf("key" to listOf(SecureHash.randomSHA256()))) server.scheduleParametersUpdate(newParameters, "Test update", Instant.MIN) startUpdater() // TODO: Remove sleep in unit test. Thread.sleep(2L * cacheExpiryMs) val newHash = newParameters.serialize().hash - val keyPair = Crypto.generateKeyPair() - updater.acceptNewNetworkParameters(newHash) { it.serialize().sign(keyPair) } val updateFile = baseDir / NETWORK_PARAMS_UPDATE_FILE_NAME val signedNetworkParams = updateFile.readObject() val paramsFromFile = signedNetworkParams.verifiedNetworkMapCert(DEV_ROOT_CA.certificate) assertEquals(newParameters, paramsFromFile) + assertEquals(newHash, server.latestParametersAccepted(ourKeyPair.public)) + } + + @Test + fun `network parameters not auto-accepted when update only changes whitelist but parameter included in exclusion`() { + setUpdater() + val newParameters = testNetworkParameters( + epoch = 314, + whitelistedContractImplementations = mapOf("key" to listOf(SecureHash.randomSHA256()))) + server.scheduleParametersUpdate(newParameters, "Test update", Instant.MIN) + startUpdater(excludedAutoAcceptNetworkParameters = setOf("whitelistedContractImplementations")) + // TODO: Remove sleep in unit test. + Thread.sleep(2L * cacheExpiryMs) + val updateFile = baseDir / NETWORK_PARAMS_UPDATE_FILE_NAME + assert(!updateFile.exists()) { "network parameters should not be auto accepted" } + } + + @Test + fun `network parameters not auto-accepted when update only changes whitelist but auto accept configured to be false`() { + setUpdater() + val newParameters = testNetworkParameters( + epoch = 314, + whitelistedContractImplementations = mapOf("key" to listOf(SecureHash.randomSHA256()))) + server.scheduleParametersUpdate(newParameters, "Test update", Instant.MIN) + startUpdater(autoAcceptNetworkParameters = false) + // TODO: Remove sleep in unit test. + Thread.sleep(2L * cacheExpiryMs) + val updateFile = baseDir / NETWORK_PARAMS_UPDATE_FILE_NAME + assert(!updateFile.exists()) { "network parameters should not be auto accepted" } } @Test @@ -306,7 +372,7 @@ class NetworkMapUpdaterTest { setUpdater() networkMapCache.addNode(myInfo) // Simulate behaviour on node startup when our node info is added to cache networkMapClient.publish(signedOtherInfo) - startUpdater(ourNodeHash = signedMyInfo.raw.hash) + startUpdater(ourNodeInfo = signedMyInfo) Thread.sleep(2L * cacheExpiryMs) verify(networkMapCache, never()).removeNode(myInfo) assertThat(server.networkMapHashes()).containsOnly(signedOtherInfo.raw.hash) @@ -363,8 +429,12 @@ class NetworkMapUpdaterTest { } } - private fun createNodeInfoAndSigned(org: String): NodeInfoAndSigned { - return createNodeInfoAndSigned(CordaX500Name(org, "London", "GB")) + private fun createNodeInfoAndSigned(org: String, keyPair: KeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)): NodeInfoAndSigned { + val serial: Long = 1 + val platformVersion = 1 + val nodeInfoBuilder = TestNodeInfoBuilder() + nodeInfoBuilder.addLegalIdentity(CordaX500Name(org, "London", "GB"), keyPair, keyPair) + return nodeInfoBuilder.buildWithSigned(serial, platformVersion) } private fun advanceTime() { 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 351133475c..98521932f9 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 @@ -68,6 +68,7 @@ import java.util.* import java.util.concurrent.Executors import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException import java.util.concurrent.atomic.AtomicInteger import kotlin.collections.ArrayList import kotlin.collections.HashMap @@ -436,6 +437,13 @@ class DriverDSLImpl( NotaryHandle(identity, validating, nodeHandlesFuture) } } + try { + _notaries.map { notary -> notary.map { handle -> handle.nodeHandles } }.getOrThrow(notaryHandleTimeout).forEach { future -> future.getOrThrow(notaryHandleTimeout) } + } catch (e: ListenProcessDeathException) { + throw IllegalStateException("Unable to start notaries. A required port might be bound already.", e) + } catch (e: TimeoutException) { + throw IllegalStateException("Unable to start notaries. A required port might be bound already.", e) + } } private fun startNotaryIdentityGeneration(): CordaFuture> { @@ -743,6 +751,7 @@ class DriverDSLImpl( companion object { internal val log = contextLogger() + private val notaryHandleTimeout = Duration.ofMinutes(1) private val defaultRpcUserList = listOf(InternalUser("default", "default", setOf("ALL")).toConfig().root().unwrapped()) private val names = arrayOf(ALICE_NAME, BOB_NAME, DUMMY_BANK_A_NAME) /** 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 b9a5d535a9..b10dbfa94f 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 @@ -621,6 +621,7 @@ private fun mockNodeConfiguration(certificatesDirectory: Path): NodeConfiguratio mutualExclusionConfiguration = MutualExclusionConfiguration(false, "", 20000, 40000), useMultiThreadedSMM = false )).whenever(it).enterpriseConfiguration + doReturn(NetworkParameterAcceptanceSettings()).whenever(it).networkParameterAcceptanceSettings } } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/TestNodeInfoBuilder.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/TestNodeInfoBuilder.kt index 63194080fc..e7f16ad0ab 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/TestNodeInfoBuilder.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/TestNodeInfoBuilder.kt @@ -20,9 +20,10 @@ import java.security.cert.X509Certificate class TestNodeInfoBuilder(private val intermediateAndRoot: Pair = DEV_INTERMEDIATE_CA to DEV_ROOT_CA.certificate) { private val identitiesAndPrivateKeys = ArrayList>() - fun addLegalIdentity(name: CordaX500Name, nodeKeyPair: KeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)): Pair { + fun addLegalIdentity(name: CordaX500Name, nodeKeyPair: + KeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME), + identityKeyPair: KeyPair = Crypto.generateKeyPair()): Pair { val nodeCertificateAndKeyPair = createDevNodeCa(intermediateAndRoot.first, name, nodeKeyPair) - val identityKeyPair = Crypto.generateKeyPair() val identityCert = X509Utilities.createCertificate( CertificateType.LEGAL_IDENTITY, nodeCertificateAndKeyPair.certificate,