Merge branch 'feature/EG-177' of https://github.com/corda/corda into feature/EG-177

This commit is contained in:
Peter Nemeth 2020-07-30 16:20:01 +01:00
commit 777be6c11a
11 changed files with 412 additions and 26 deletions

View File

@ -1,8 +1,10 @@
package net.corda.node.services.network
import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.Party
import net.corda.core.internal.*
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.getOrThrow
@ -11,6 +13,7 @@ import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME
import net.corda.nodeapi.internal.network.SignedNetworkParameters
import net.corda.testing.common.internal.addNotary
import net.corda.testing.common.internal.eventually
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.*
@ -74,7 +77,6 @@ class NetworkMapTest(var initFunc: (URL, NetworkMapServer) -> CompatibilityZoneP
)
}
@Before
fun start() {
networkMapServer = NetworkMapServer(cacheTimeout, portAllocation.nextHostAndPort())
@ -141,6 +143,102 @@ class NetworkMapTest(var initFunc: (URL, NetworkMapServer) -> CompatibilityZoneP
}
}
@Test(timeout = 300_000)
fun `Can hotload parameters if the notary changes`() {
internalDriver(
portAllocation = portAllocation,
compatibilityZone = compatibilityZone,
notarySpecs = emptyList()
) {
val notary: Party = TestIdentity.fresh("test notary").party
val oldParams = networkMapServer.networkParameters
val paramsWithNewNotary = oldParams.copy(
epoch = 3,
modifiedTime = Instant.ofEpochMilli(random63BitValue())).addNotary(notary)
val alice = startNodeAndRunFlagDay(paramsWithNewNotary)
eventually { assertEquals(paramsWithNewNotary, alice.rpc.networkParameters) }
}
}
@Test(timeout = 300_000)
fun `If only the notary changes but parameters were not accepted, the node will still shut down on the flag day`() {
internalDriver(
portAllocation = portAllocation,
compatibilityZone = compatibilityZone,
notarySpecs = emptyList()
) {
val notary: Party = TestIdentity.fresh("test notary").party
val oldParams = networkMapServer.networkParameters
val paramsWithNewNotary = oldParams.copy(
epoch = 3,
modifiedTime = Instant.ofEpochMilli(random63BitValue())).addNotary(notary)
val alice = startNode(providedName = ALICE_NAME, devMode = false).getOrThrow() as NodeHandleInternal
networkMapServer.scheduleParametersUpdate(paramsWithNewNotary, "Next parameters", Instant.ofEpochMilli(random63BitValue()))
// Wait for network map client to poll for the next update.
Thread.sleep(cacheTimeout.toMillis() * 2)
networkMapServer.advertiseNewParameters()
eventually { assertThatThrownBy { alice.rpc.networkParameters }.hasMessageContaining("Connection failure detected") }
}
}
@Test(timeout = 300_000)
fun `Can not hotload parameters if non-hotloadable parameter changes and the node will shut down`() {
internalDriver(
portAllocation = portAllocation,
compatibilityZone = compatibilityZone,
notarySpecs = emptyList()
) {
val oldParams = networkMapServer.networkParameters
val paramsWithUpdatedMaxMessageSize = oldParams.copy(
epoch = 3,
modifiedTime = Instant.ofEpochMilli(random63BitValue()),
maxMessageSize = oldParams.maxMessageSize + 1)
val alice = startNodeAndRunFlagDay(paramsWithUpdatedMaxMessageSize)
eventually { assertThatThrownBy { alice.rpc.networkParameters }.hasMessageContaining("Connection failure detected") }
}
}
@Test(timeout = 300_000)
fun `Can not hotload parameters if notary and a non-hotloadable parameter changes and the node will shut down`() {
internalDriver(
portAllocation = portAllocation,
compatibilityZone = compatibilityZone,
notarySpecs = emptyList()
) {
val oldParams = networkMapServer.networkParameters
val notary: Party = TestIdentity.fresh("test notary").party
val paramsWithUpdatedMaxMessageSizeAndNotary = oldParams.copy(
epoch = 3,
modifiedTime = Instant.ofEpochMilli(random63BitValue()),
maxMessageSize = oldParams.maxMessageSize + 1).addNotary(notary)
val alice = startNodeAndRunFlagDay(paramsWithUpdatedMaxMessageSizeAndNotary)
eventually { assertThatThrownBy { alice.rpc.networkParameters }.hasMessageContaining("Connection failure detected") }
}
}
private fun DriverDSLImpl.startNodeAndRunFlagDay(newParams: NetworkParameters): NodeHandleInternal {
val alice = startNode(providedName = ALICE_NAME, devMode = false).getOrThrow() as NodeHandleInternal
val nextHash = newParams.serialize().hash
networkMapServer.scheduleParametersUpdate(newParams, "Next parameters", Instant.ofEpochMilli(random63BitValue()))
// Wait for network map client to poll for the next update.
Thread.sleep(cacheTimeout.toMillis() * 2)
alice.rpc.acceptNewNetworkParameters(nextHash)
assertEquals(nextHash, networkMapServer.latestParametersAccepted(alice.nodeInfo.legalIdentities.first().owningKey))
assertEquals(networkMapServer.networkParameters, alice.rpc.networkParameters)
networkMapServer.advertiseNewParameters()
return alice
}
@Test(timeout=300_000)
fun `nodes process additions and removals from the network map correctly (and also download the network parameters)`() {
internalDriver(

View File

@ -109,6 +109,8 @@ import net.corda.node.services.messaging.DeduplicationHandler
import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.network.NetworkMapClient
import net.corda.node.services.network.NetworkMapUpdater
import net.corda.node.services.network.NetworkParameterUpdateListener
import net.corda.node.services.network.NetworkParametersHotloader
import net.corda.node.services.network.NodeInfoWatcher
import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.persistence.AbstractPartyDescriptor
@ -461,6 +463,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
}
@Suppress("ComplexMethod")
open fun start(): S {
check(started == null) { "Node has already been started" }
@ -486,7 +489,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
startShell()
networkMapClient?.start(trustRoot)
val (netParams, signedNetParams) = NetworkParametersReader(trustRoot, networkMapClient, configuration.baseDirectory).read()
val networkParametersReader = NetworkParametersReader(trustRoot, networkMapClient, configuration.baseDirectory)
val (netParams, signedNetParams) = networkParametersReader.read()
log.info("Loaded network parameters: $netParams")
check(netParams.minimumPlatformVersion <= versionInfo.platformVersion) {
"Node's platform version is lower than network's required minimumPlatformVersion"
@ -507,13 +511,27 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val (nodeInfo, signedNodeInfo) = nodeInfoAndSigned
identityService.ourNames = nodeInfo.legalIdentities.map { it.name }.toSet()
services.start(nodeInfo, netParams)
val networkParametersHotloader = if (networkMapClient == null) {
null
} else {
NetworkParametersHotloader(networkMapClient, trustRoot, netParams, networkParametersReader, networkParametersStorage).also {
it.addNotaryUpdateListener(networkMapCache)
it.addNotaryUpdateListener(identityService)
it.addNetworkParametersChangedListeners(services)
it.addNetworkParametersChangedListeners(networkMapUpdater)
}
}
networkMapUpdater.start(
trustRoot,
signedNetParams.raw.hash,
signedNodeInfo,
netParams,
keyManagementService,
configuration.networkParameterAcceptanceSettings!!)
configuration.networkParameterAcceptanceSettings!!,
networkParametersHotloader)
try {
startMessagingService(rpcOps, nodeInfo, myNotaryIdentity, netParams)
} catch (e: Exception) {
@ -1153,7 +1171,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
}
inner class ServiceHubInternalImpl : SingletonSerializeAsToken(), ServiceHubInternal, ServicesForResolution by servicesForResolution {
inner class ServiceHubInternalImpl : SingletonSerializeAsToken(), ServiceHubInternal, ServicesForResolution by servicesForResolution, NetworkParameterUpdateListener {
override val rpcFlows = ArrayList<Class<out FlowLogic<*>>>()
override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage(database)
override val identityService: IdentityService get() = this@AbstractNode.identityService
@ -1186,6 +1204,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
override val attachmentsClassLoaderCache: AttachmentsClassLoaderCache get() = this@AbstractNode.attachmentsClassLoaderCache
@Volatile
private lateinit var _networkParameters: NetworkParameters
override val networkParameters: NetworkParameters get() = _networkParameters
@ -1272,6 +1291,10 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val ledgerTransaction = servicesForResolution.specialise(ltx)
return verifierFactoryService.apply(ledgerTransaction)
}
override fun onNewNetworkParameters(networkParameters: NetworkParameters) {
this._networkParameters = networkParameters
}
}
}

View File

@ -12,6 +12,7 @@ import net.corda.core.internal.CertRole
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.hash
import net.corda.core.internal.toSet
import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.UnknownAnonymousPartyException
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
@ -19,6 +20,7 @@ import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.node.services.network.NotaryUpdateListener
import net.corda.node.services.persistence.PublicKeyHashToExternalId
import net.corda.node.services.persistence.WritablePublicKeyToOwningIdentityCache
import net.corda.node.utilities.AppendOnlyPersistentMap
@ -53,7 +55,8 @@ import kotlin.streams.toList
* cached for efficient lookup.
*/
@ThreadSafe
class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSerializeAsToken(), IdentityServiceInternal {
@Suppress("TooManyFunctions")
class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSerializeAsToken(), IdentityServiceInternal, NotaryUpdateListener {
companion object {
private val log = contextLogger()
@ -197,7 +200,8 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
override val trustAnchor: TrustAnchor get() = _trustAnchor
/** Stores notary identities obtained from the network parameters, for which we don't need to perform a database lookup. */
private val notaryIdentityCache = HashSet<Party>()
@Volatile
private var notaryIdentityCache = HashSet<Party>()
// CordaPersistence is not a c'tor parameter to work around the cyclic dependency
lateinit var database: CordaPersistence
@ -453,4 +457,8 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
keys
}
}
override fun onNewNotaryList(notaries: List<NotaryInfo>) {
notaryIdentityCache = HashSet(notaries.map { it.identity })
}
}

View File

@ -1,6 +1,7 @@
package net.corda.node.services.network
import com.google.common.util.concurrent.MoreExecutors
import net.corda.cliutils.ExitCodes
import net.corda.core.CordaRuntimeException
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignedData
@ -62,7 +63,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
private val baseDirectory: Path,
private val extraNetworkMapKeys: List<UUID>,
private val networkParametersStorage: NetworkParametersStorage
) : AutoCloseable {
) : AutoCloseable, NetworkParameterUpdateListener {
companion object {
private val logger = contextLogger()
private val defaultRetryInterval = 1.minutes
@ -77,12 +78,15 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
private val fileWatcherSubscription = AtomicReference<Subscription?>()
private var autoAcceptNetworkParameters: Boolean = true
private lateinit var trustRoot: X509Certificate
@Volatile
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>
private var networkParametersHotloader: NetworkParametersHotloader? = null
override fun close() {
fileWatcherSubscription.updateAndGet { subscription ->
@ -95,13 +99,15 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
}
MoreExecutors.shutdownAndAwaitTermination(networkMapPoller, 50, TimeUnit.SECONDS)
}
@Suppress("LongParameterList")
fun start(trustRoot: X509Certificate,
currentParametersHash: SecureHash,
ourNodeInfo: SignedNodeInfo,
networkParameters: NetworkParameters,
keyManagementService: KeyManagementService,
networkParameterAcceptanceSettings: NetworkParameterAcceptanceSettings) {
networkParameterAcceptanceSettings: NetworkParameterAcceptanceSettings,
networkParametersHotloader: NetworkParametersHotloader?
) {
fileWatcherSubscription.updateAndGet { subscription ->
require(subscription == null) { "Should not call this method twice" }
this.trustRoot = trustRoot
@ -112,6 +118,8 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
this.keyManagementService = keyManagementService
this.autoAcceptNetworkParameters = networkParameterAcceptanceSettings.autoAcceptEnabled
this.excludedAutoAcceptNetworkParameters = networkParameterAcceptanceSettings.excludedAutoAcceptableParameters
this.networkParametersHotloader = networkParametersHotloader
val autoAcceptNetworkParametersNames = autoAcceptablePropertyNames - excludedAutoAcceptNetworkParameters
if (autoAcceptNetworkParameters && autoAcceptNetworkParametersNames.isNotEmpty()) {
@ -180,7 +188,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
val additionalHashes = getPrivateNetworkNodeHashes(version)
val allHashesFromNetworkMap = (globalNetworkMap.nodeInfoHashes + additionalHashes).toSet()
if (currentParametersHash != globalNetworkMap.networkParameterHash) {
exitOnParametersMismatch(globalNetworkMap)
hotloadOrExitOnParametersMismatch(globalNetworkMap)
}
// Calculate any nodes that are now gone and remove _only_ them from the cache
// NOTE: We won't remove them until after the add/update cycle as only then will we definitely know which nodes are no longer
@ -276,22 +284,26 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
}
}
private fun exitOnParametersMismatch(networkMap: NetworkMap) {
private fun hotloadOrExitOnParametersMismatch(networkMap: NetworkMap) {
val updatesFile = baseDirectory / NETWORK_PARAMS_UPDATE_FILE_NAME
val acceptedHash = if (updatesFile.exists()) updatesFile.readObject<SignedNetworkParameters>().raw.hash else null
val exitCode = if (acceptedHash == networkMap.networkParameterHash) {
logger.info("Flag day occurred. Network map switched to the new network parameters: " +
"${networkMap.networkParameterHash}. Node will shutdown now and needs to be started again.")
0
} else {
// TODO This needs special handling (node omitted update process or didn't accept new parameters)
val newParameterHash = networkMap.networkParameterHash
val nodeAcceptedNewParameters = updatesFile.exists() && newParameterHash == updatesFile.readObject<SignedNetworkParameters>().raw.hash
if (!nodeAcceptedNewParameters) {
logger.error(
"""Node is using network parameters with hash $currentParametersHash but the network map is advertising ${networkMap.networkParameterHash}.
To resolve this mismatch, and move to the current parameters, delete the $NETWORK_PARAMS_FILE_NAME file from the node's directory and restart.
The node will shutdown now.""")
1
exitProcess(ExitCodes.FAILURE)
}
exitProcess(exitCode)
val hotloadSucceeded = networkParametersHotloader!!.attemptHotload(newParameterHash)
if (!hotloadSucceeded) {
logger.info("Flag day occurred. Network map switched to the new network parameters: " +
"${networkMap.networkParameterHash}. Node will shutdown now and needs to be started again.")
exitProcess(ExitCodes.SUCCESS)
}
currentParametersHash = newParameterHash
}
private fun handleUpdateNetworkParameters(networkMapClient: NetworkMapClient, update: ParametersUpdate) {
@ -340,6 +352,10 @@ The node will shutdown now.""")
throw OutdatedNetworkParameterHashException(parametersHash, newParametersHash)
}
}
override fun onNewNetworkParameters(networkParameters: NetworkParameters) {
this.networkParameters = networkParameters
}
}
private val memberPropertyPartition = NetworkParameters::class.declaredMemberProperties.partition { it.isAutoAcceptable() }
@ -360,8 +376,8 @@ internal fun NetworkParameters.canAutoAccept(newNetworkParameters: NetworkParame
private fun KProperty1<out NetworkParameters, Any?>.isAutoAcceptable(): Boolean = findAnnotation<AutoAcceptable>() != null
private fun NetworkParameters.valueChanged(newNetworkParameters: NetworkParameters, getter: Method?): Boolean {
internal fun NetworkParameters.valueChanged(newNetworkParameters: NetworkParameters, getter: Method?): Boolean {
val propertyValue = getter?.invoke(this)
val newPropertyValue = getter?.invoke(newNetworkParameters)
return propertyValue != newPropertyValue
}
}

View File

@ -0,0 +1,11 @@
package net.corda.node.services.network
import net.corda.core.node.NetworkParameters
/**
* When network parameters change on a flag day, onNewNetworkParameters will be invoked with the new parameters.
* Used inside {@link net.corda.node.services.network.NetworkParametersUpdater}
*/
interface NetworkParameterUpdateListener {
fun onNewNetworkParameters(networkParameters: NetworkParameters)
}

View File

@ -0,0 +1,88 @@
package net.corda.node.services.network
import net.corda.core.crypto.SecureHash
import net.corda.core.internal.NetworkParametersStorage
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NotaryInfo
import net.corda.core.utilities.contextLogger
import net.corda.node.internal.NetworkParametersReader
import net.corda.nodeapi.internal.network.verifiedNetworkParametersCert
import java.security.cert.X509Certificate
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.jvm.javaGetter
/**
* This class is responsible for hotloading new network parameters or shut down the node if it's not possible.
* Currently only hotloading notary changes are supported.
*/
class NetworkParametersHotloader(private val networkMapClient: NetworkMapClient,
private val trustRoot: X509Certificate,
@Volatile private var networkParameters: NetworkParameters,
private val networkParametersReader: NetworkParametersReader,
private val networkParametersStorage: NetworkParametersStorage) {
companion object {
private val logger = contextLogger()
private val alwaysHotloadable = listOf(NetworkParameters::epoch, NetworkParameters::modifiedTime)
}
private val networkParameterUpdateListeners = mutableListOf<NetworkParameterUpdateListener>()
private val notaryUpdateListeners = mutableListOf<NotaryUpdateListener>()
fun addNetworkParametersChangedListeners(listener: NetworkParameterUpdateListener) {
networkParameterUpdateListeners.add(listener)
}
fun addNotaryUpdateListener(listener: NotaryUpdateListener) {
notaryUpdateListeners.add(listener)
}
private fun notifyListenersFor(notaries: List<NotaryInfo>) = notaryUpdateListeners.forEach { it.onNewNotaryList(notaries) }
private fun notifyListenersFor(networkParameters: NetworkParameters) = networkParameterUpdateListeners.forEach { it.onNewNetworkParameters(networkParameters) }
fun attemptHotload(newNetworkParameterHash: SecureHash): Boolean {
val newSignedNetParams = networkMapClient.getNetworkParameters(newNetworkParameterHash)
val newNetParams = newSignedNetParams.verifiedNetworkParametersCert(trustRoot)
if (canHotload(newNetParams)) {
logger.info("All changed parameters are hotloadable")
hotloadParameters(newNetParams)
return true
} else {
return false
}
}
/**
* Ignoring always hotloadable properties (epoch, modifiedTime) return true if the notary is the only property that is different in the new network parameters
*/
private fun canHotload(newNetworkParameters: NetworkParameters): Boolean {
val propertiesChanged = NetworkParameters::class.declaredMemberProperties
.minus(alwaysHotloadable)
.filter { networkParameters.valueChanged(newNetworkParameters, it.javaGetter) }
logger.info("Updated NetworkParameters properties: $propertiesChanged")
val noPropertiesChanged = propertiesChanged.isEmpty()
val onlyNotariesChanged = propertiesChanged == listOf(NetworkParameters::notaries)
return when {
noPropertiesChanged -> true
onlyNotariesChanged -> true
else -> false
}
}
/**
* Update local networkParameters and currentParametersHash with new values.
* Notify all listeners for network parameter changes
*/
private fun hotloadParameters(newNetworkParameters: NetworkParameters) {
networkParameters = newNetworkParameters
val networkParametersAndSigned = networkParametersReader.read()
networkParametersStorage.setCurrentParameters(networkParametersAndSigned.signed, trustRoot)
notifyListenersFor(newNetworkParameters)
notifyListenersFor(newNetworkParameters.notaries)
}
}

View File

@ -0,0 +1,11 @@
package net.corda.node.services.network
import net.corda.core.node.NotaryInfo
/**
* When notaries inside network parameters change on a flag day, onNewNotaryList will be invoked with the new notary list.
* Used inside {@link net.corda.node.services.network.NetworkParametersUpdater}
*/
interface NotaryUpdateListener {
fun onNewNotaryList(notaries: List<NotaryInfo>)
}

View File

@ -38,9 +38,10 @@ import javax.persistence.PersistenceException
/** Database-based network map cache. */
@ThreadSafe
@Suppress("TooManyFunctions")
open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
private val database: CordaPersistence,
private val identityService: IdentityService) : NetworkMapCacheInternal, SingletonSerializeAsToken() {
private val identityService: IdentityService) : NetworkMapCacheInternal, SingletonSerializeAsToken(), NotaryUpdateListener {
companion object {
private val logger = contextLogger()
@ -53,6 +54,7 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
override val nodeReady: OpenFuture<Void?> = openFuture()
@Volatile
private lateinit var notaries: List<NotaryInfo>
override val notaryIdentities: List<Party> get() = notaries.map { it.identity }
@ -386,4 +388,8 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
for (nodeInfo in result) session.remove(nodeInfo)
}
}
override fun onNewNotaryList(notaries: List<NotaryInfo>) {
this.notaries = notaries
}
}

View File

@ -78,7 +78,6 @@ class NetworkMapUpdaterTest {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule(true)
private val cacheExpiryMs = 1000
private val privateNetUUID = UUID.randomUUID()
private val fs = Jimfs.newFileSystem(unix())
@ -120,12 +119,13 @@ class NetworkMapUpdaterTest {
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))
NetworkParameterAcceptanceSettings(autoAcceptNetworkParameters, excludedAutoAcceptNetworkParameters), null)
}
@Test(timeout=300_000)

View File

@ -0,0 +1,125 @@
package net.corda.node.services.network
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.never
import com.nhaarman.mockito_kotlin.verify
import net.corda.core.identity.Party
import net.corda.core.internal.NetworkParametersStorage
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NotaryInfo
import net.corda.core.serialization.serialize
import net.corda.coretesting.internal.DEV_ROOT_CA
import net.corda.node.internal.NetworkParametersReader
import net.corda.nodeapi.internal.createDevNetworkMapCa
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.testing.common.internal.addNotary
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito
class NetworkParametersHotloaderTest {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule(true)
private val networkMapCertAndKeyPair: CertificateAndKeyPair = createDevNetworkMapCa()
private val trustRoot = DEV_ROOT_CA.certificate
private val originalNetworkParameters = testNetworkParameters()
private val notary: Party = TestIdentity.fresh("test notary").party
private val networkParametersWithNotary = originalNetworkParameters.addNotary(notary)
private val networkParametersStorage = Mockito.mock(NetworkParametersStorage::class.java)
@Test(timeout = 300_000)
fun `can hotload if notary changes`() {
`can hotload`(networkParametersWithNotary)
}
@Test(timeout = 300_000)
fun `can not hotload if notary changes but another non-hotloadable property also changes`() {
val newnetParamsWithNewNotaryAndMaxMsgSize = networkParametersWithNotary.copy(maxMessageSize = networkParametersWithNotary.maxMessageSize + 1)
`can not hotload`(newnetParamsWithNewNotaryAndMaxMsgSize)
}
@Test(timeout = 300_000)
fun `can hotload if only always hotloadable properties change`() {
val newParametersWithAlwaysHotloadableProperties = originalNetworkParameters.copy(epoch = originalNetworkParameters.epoch + 1, modifiedTime = originalNetworkParameters.modifiedTime.plusSeconds(60))
`can hotload`(newParametersWithAlwaysHotloadableProperties)
}
@Test(timeout = 300_000)
fun `can not hotload if maxMessageSize changes`() {
val parametersWithNewMaxMessageSize = originalNetworkParameters.copy(maxMessageSize = originalNetworkParameters.maxMessageSize + 1)
`can not hotload`(parametersWithNewMaxMessageSize)
}
@Test(timeout = 300_000)
fun `can not hotload if maxTransactionSize changes`() {
val parametersWithNewMaxTransactionSize = originalNetworkParameters.copy(maxTransactionSize = originalNetworkParameters.maxMessageSize + 1)
`can not hotload`(parametersWithNewMaxTransactionSize)
}
@Test(timeout = 300_000)
fun `can not hotload if minimumPlatformVersion changes`() {
val parametersWithNewMinimumPlatformVersion = originalNetworkParameters.copy(minimumPlatformVersion = originalNetworkParameters.minimumPlatformVersion + 1)
`can not hotload`(parametersWithNewMinimumPlatformVersion)
}
private fun `can hotload`(newNetworkParameters: NetworkParameters) {
val notaryUpdateListener = Mockito.spy(object : NotaryUpdateListener {
override fun onNewNotaryList(notaries: List<NotaryInfo>) {
}
})
val networkParametersChangedListener = Mockito.spy(object : NetworkParameterUpdateListener {
override fun onNewNetworkParameters(networkParameters: NetworkParameters) {
}
})
val networkParametersHotloader = createHotloaderWithMockedServices(newNetworkParameters).also {
it.addNotaryUpdateListener(notaryUpdateListener)
it.addNetworkParametersChangedListeners(networkParametersChangedListener)
}
Assert.assertTrue(networkParametersHotloader.attemptHotload(newNetworkParameters.serialize().hash))
verify(notaryUpdateListener).onNewNotaryList(newNetworkParameters.notaries)
verify(networkParametersChangedListener).onNewNetworkParameters(newNetworkParameters)
}
private fun `can not hotload`(newNetworkParameters: NetworkParameters) {
val notaryUpdateListener = Mockito.spy(object : NotaryUpdateListener {
override fun onNewNotaryList(notaries: List<NotaryInfo>) {
}
})
val networkParametersChangedListener = Mockito.spy(object : NetworkParameterUpdateListener {
override fun onNewNetworkParameters(networkParameters: NetworkParameters) {
}
})
val networkParametersHotloader = createHotloaderWithMockedServices(newNetworkParameters).also {
it.addNotaryUpdateListener(notaryUpdateListener)
it.addNetworkParametersChangedListeners(networkParametersChangedListener)
}
Assert.assertFalse(networkParametersHotloader.attemptHotload(newNetworkParameters.serialize().hash))
verify(notaryUpdateListener, never()).onNewNotaryList(any());
verify(networkParametersChangedListener, never()).onNewNetworkParameters(any());
}
private fun createHotloaderWithMockedServices(newNetworkParameters: NetworkParameters): NetworkParametersHotloader {
val signedNetworkParameters = networkMapCertAndKeyPair.sign(newNetworkParameters)
val networkMapClient = Mockito.mock(NetworkMapClient::class.java)
Mockito.`when`(networkMapClient.getNetworkParameters(newNetworkParameters.serialize().hash)).thenReturn(signedNetworkParameters)
val networkParametersReader = Mockito.mock(NetworkParametersReader::class.java)
Mockito.`when`(networkParametersReader.read())
.thenReturn(NetworkParametersReader.NetworkParametersAndSigned(signedNetworkParameters, trustRoot))
return NetworkParametersHotloader(networkMapClient, trustRoot, originalNetworkParameters, networkParametersReader, networkParametersStorage)
}
}

View File

@ -117,7 +117,7 @@ class NetworkMapServer(private val pollInterval: Duration,
// Mapping from the UUID of the network (null for global one) to hashes of the nodes in network
private val networkMaps = mutableMapOf<UUID?, MutableSet<SecureHash>>()
val latestAcceptedParametersMap = mutableMapOf<PublicKey, SecureHash>()
private val signedNetParams by lazy { networkMapCertAndKeyPair.sign(networkParameters) }
private val signedNetParams get() = networkMapCertAndKeyPair.sign(networkParameters)
@POST
@Path("publish")