mirror of
https://github.com/corda/corda.git
synced 2024-12-20 21:43:14 +00:00
Merge branch 'feature/EG-177' of https://github.com/corda/corda into feature/EG-177
This commit is contained in:
commit
777be6c11a
@ -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(
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 })
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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>)
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
|
Loading…
Reference in New Issue
Block a user