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
15 changed files with 301 additions and 37 deletions

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() {