CORDA-1965 Auto-accept network parameter changes (#4222)

* add auto acceptance of certain network parameters

* Remove incorrect nullification of newNetworkParameters object within NetworkMapUpdater

* Automatically update network parameters if update accepted and flag day occured

* Comment cleanup

* Add node configuration for auto accepting network parameter changes

* Remove hot swapping of network parameters

* Add docs for auto accept config flag

* Minor change to log line

* Remove unrelated fix that was corrected on master

* Minor name change within NetworkParameters class

* Minor doc rewording

* Fix typo in docs

* Address PR comments

* Add node config option to turn off network param auto-accept on a per param basis

* Address PR comments

* Fix failing Network Map update integration test
This commit is contained in:
Oliver Knowles 2018-11-20 09:50:42 +00:00 committed by GitHub
parent 2762c34ebe
commit 373d99435c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 301 additions and 37 deletions

View File

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

View File

@ -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<NotaryInfo>,
val maxMessageSize: Int,
val maxTransactionSize: Int,
val modifiedTime: Instant,
val epoch: Int,
val whitelistedContractImplementations: Map<String, List<AttachmentId>>,
@AutoAcceptable val modifiedTime: Instant,
@AutoAcceptable val epoch: Int,
@AutoAcceptable val whitelistedContractImplementations: Map<String, List<AttachmentId>>,
val eventHorizon: Duration,
val packageOwnership: Map<JavaPackageName, PublicKey>
@AutoAcceptable val packageOwnership: Map<JavaPackageName, PublicKey>
) {
// DOCEND 1
@DeprecatedConstructorForDeserialization(1)
constructor (minimumPlatformVersion: Int,
notaries: List<NotaryInfo>,
@ -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<String>): 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<JavaPackageName>) = packages.all { currentPackage ->
packages.none { otherPackage -> otherPackage != currentPackage && otherPackage.name.startsWith("${currentPackage.name}.") }
}
private fun KProperty1<out NetworkParameters, Any?>.isAutoAcceptable(): Boolean {
return this.findAnnotation<AutoAcceptable>() != null
}

View File

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

View File

@ -248,6 +248,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
--------

View File

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

View File

@ -21,6 +21,10 @@ 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.
<< MORE TO COME >>
.. _release_notes_v3_3:

View File

@ -94,7 +94,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()
@ -103,7 +106,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)

View File

@ -345,7 +345,13 @@ abstract class AbstractNode<S>(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.

View File

@ -85,6 +85,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()
@ -173,6 +175,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<String> = emptySet()
)
/**
* Currently only used for notarisation requests.
*

View File

@ -76,7 +76,8 @@ data class NodeConfigurationImpl(
override val flowOverrides: FlowOverrideConfig?,
override val cordappSignerKeyFingerprintBlacklist: List<String> = Defaults.cordappSignerKeyFingerprintBlacklist,
override val cryptoServiceName: SupportedCryptoServices? = null,
override val cryptoServiceConf: String? = null
override val cryptoServiceConf: String? = null,
override val networkParameterAcceptanceSettings: NetworkParameterAcceptanceSettings = Defaults.networkParameterAcceptanceSettings
) : NodeConfiguration {
internal object Defaults {
val jmxMonitoringHttpPort: Int? = null
@ -108,6 +109,7 @@ data class NodeConfigurationImpl(
val flowMonitorSuspensionLoggingThresholdMillis: Duration = NodeConfiguration.DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS
val jmxReporterType: JmxReporterType = NodeConfiguration.defaultJmxReporterType
val cordappSignerKeyFingerprintBlacklist: List<String> = DEV_PUB_KEY_HASHES.map { it.toString() }
val networkParameterAcceptanceSettings: NetworkParameterAcceptanceSettings = NetworkParameterAcceptanceSettings()
fun cordappsDirectories(baseDirectory: Path) = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT)

View File

@ -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<ParametersUpdate, SignedNetworkParameters>? = 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<String>
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<SecureHash>) {

View File

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

View File

@ -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<Void?>()
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<String> = 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<SignedNetworkParameters>()
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<SignedNetworkParameters>()
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() {

View File

@ -35,6 +35,7 @@ import net.corda.node.services.api.FlowStarter
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.api.StartedNodeServices
import net.corda.node.services.config.FlowTimeoutConfiguration
import net.corda.node.services.config.NetworkParameterAcceptanceSettings
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.VerifierType
import net.corda.node.services.identity.PersistentIdentityService
@ -618,6 +619,7 @@ private fun mockNodeConfiguration(certificatesDirectory: Path): NodeConfiguratio
doReturn(null).whenever(it).devModeOptions
doReturn(null).whenever(it).cryptoServiceName
doReturn(null).whenever(it).cryptoServiceConf
doReturn(NetworkParameterAcceptanceSettings()).whenever(it).networkParameterAcceptanceSettings
}
}

View File

@ -20,9 +20,10 @@ import java.security.cert.X509Certificate
class TestNodeInfoBuilder(private val intermediateAndRoot: Pair<CertificateAndKeyPair, X509Certificate> = DEV_INTERMEDIATE_CA to DEV_ROOT_CA.certificate) {
private val identitiesAndPrivateKeys = ArrayList<Pair<PartyAndCertificate, PrivateKey>>()
fun addLegalIdentity(name: CordaX500Name, nodeKeyPair: KeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)): Pair<PartyAndCertificate, PrivateKey> {
fun addLegalIdentity(name: CordaX500Name, nodeKeyPair:
KeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME),
identityKeyPair: KeyPair = Crypto.generateKeyPair()): Pair<PartyAndCertificate, PrivateKey> {
val nodeCertificateAndKeyPair = createDevNodeCa(intermediateAndRoot.first, name, nodeKeyPair)
val identityKeyPair = Crypto.generateKeyPair()
val identityCert = X509Utilities.createCertificate(
CertificateType.LEGAL_IDENTITY,
nodeCertificateAndKeyPair.certificate,