mirror of
https://github.com/corda/corda.git
synced 2025-04-07 11:27:01 +00:00
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:
parent
2762c34ebe
commit
373d99435c
10
core/src/main/kotlin/net/corda/core/node/AutoAcceptable.kt
Normal file
10
core/src/main/kotlin/net/corda/core/node/AutoAcceptable.kt
Normal 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
|
@ -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
|
||||
}
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
--------
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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>) {
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user