mirror of
https://github.com/corda/corda.git
synced 2025-02-28 12:01:14 +00:00
* CORDA-866: Implement removal of stale nodes from network - backport (#3128) * CORDA-866: Implement removal of stale nodes from network Backported * Implement removal of stale nodes from network Add eventHorizon to NetworkParameters structure. Add republishing of node info on 1 day intervals - it is treated by network map as heartbeat from node indicating if it's alive or not. Add removal of old node infos on network map signing. * Add copy method to NetworkParameters data class Add JvmOverloads annotation to the constructor, because it's data class exposed in API * Fix test * ENT-1933: make NetworkParameters serialization compatible (#3234) * ENT-1933: make NetworkParameters serialization compatible * Fixes after cherry-pick
This commit is contained in:
parent
593708e885
commit
f132923b86
@ -3,6 +3,9 @@ package net.corda.core.node
|
|||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.services.AttachmentId
|
import net.corda.core.node.services.AttachmentId
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
|
||||||
|
import net.corda.core.utilities.days
|
||||||
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -17,9 +20,9 @@ import java.time.Instant
|
|||||||
* of parameters.
|
* of parameters.
|
||||||
* @property whitelistedContractImplementations List of whitelisted jars containing contract code for each contract class.
|
* @property whitelistedContractImplementations List of whitelisted jars containing contract code for each contract class.
|
||||||
* This will be used by [net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint]. Read more about contract constraints here: <https://docs.corda.net/api-contract-constraints.html>
|
* This will be used by [net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint]. Read more about contract constraints here: <https://docs.corda.net/api-contract-constraints.html>
|
||||||
|
* @property eventHorizon Time after which nodes will be removed from the network map if they have not been seen
|
||||||
|
* during this period
|
||||||
*/
|
*/
|
||||||
// TODO Add eventHorizon - how many days a node can be offline before being automatically ejected from the network.
|
|
||||||
// It needs separate design.
|
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class NetworkParameters(
|
data class NetworkParameters(
|
||||||
val minimumPlatformVersion: Int,
|
val minimumPlatformVersion: Int,
|
||||||
@ -28,14 +31,52 @@ data class NetworkParameters(
|
|||||||
val maxTransactionSize: Int,
|
val maxTransactionSize: Int,
|
||||||
val modifiedTime: Instant,
|
val modifiedTime: Instant,
|
||||||
val epoch: Int,
|
val epoch: Int,
|
||||||
val whitelistedContractImplementations: Map<String, List<AttachmentId>>
|
val whitelistedContractImplementations: Map<String, List<AttachmentId>>,
|
||||||
|
val eventHorizon: Duration
|
||||||
) {
|
) {
|
||||||
|
@DeprecatedConstructorForDeserialization(1)
|
||||||
|
constructor (minimumPlatformVersion: Int,
|
||||||
|
notaries: List<NotaryInfo>,
|
||||||
|
maxMessageSize: Int,
|
||||||
|
maxTransactionSize: Int,
|
||||||
|
modifiedTime: Instant,
|
||||||
|
epoch: Int,
|
||||||
|
whitelistedContractImplementations: Map<String, List<AttachmentId>>
|
||||||
|
) : this(minimumPlatformVersion,
|
||||||
|
notaries,
|
||||||
|
maxMessageSize,
|
||||||
|
maxTransactionSize,
|
||||||
|
modifiedTime,
|
||||||
|
epoch,
|
||||||
|
whitelistedContractImplementations,
|
||||||
|
Int.MAX_VALUE.days
|
||||||
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" }
|
require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" }
|
||||||
require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" }
|
require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" }
|
||||||
require(epoch > 0) { "epoch must be at least 1" }
|
require(epoch > 0) { "epoch must be at least 1" }
|
||||||
require(maxMessageSize > 0) { "maxMessageSize must be at least 1" }
|
require(maxMessageSize > 0) { "maxMessageSize must be at least 1" }
|
||||||
require(maxTransactionSize > 0) { "maxTransactionSize must be at least 1" }
|
require(maxTransactionSize > 0) { "maxTransactionSize must be at least 1" }
|
||||||
|
require(!eventHorizon.isNegative) { "eventHorizon must be positive value" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun copy(minimumPlatformVersion: Int,
|
||||||
|
notaries: List<NotaryInfo>,
|
||||||
|
maxMessageSize: Int,
|
||||||
|
maxTransactionSize: Int,
|
||||||
|
modifiedTime: Instant,
|
||||||
|
epoch: Int,
|
||||||
|
whitelistedContractImplementations: Map<String, List<AttachmentId>>
|
||||||
|
): NetworkParameters {
|
||||||
|
return copy(minimumPlatformVersion = minimumPlatformVersion,
|
||||||
|
notaries = notaries,
|
||||||
|
maxMessageSize = maxMessageSize,
|
||||||
|
maxTransactionSize = maxTransactionSize,
|
||||||
|
modifiedTime = modifiedTime,
|
||||||
|
epoch = epoch,
|
||||||
|
whitelistedContractImplementations = whitelistedContractImplementations,
|
||||||
|
eventHorizon = eventHorizon)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
@ -47,6 +88,7 @@ data class NetworkParameters(
|
|||||||
whitelistedContractImplementations {
|
whitelistedContractImplementations {
|
||||||
${whitelistedContractImplementations.entries.joinToString("\n ")}
|
${whitelistedContractImplementations.entries.joinToString("\n ")}
|
||||||
}
|
}
|
||||||
|
eventHorizon=$eventHorizon
|
||||||
modifiedTime=$modifiedTime
|
modifiedTime=$modifiedTime
|
||||||
epoch=$epoch
|
epoch=$epoch
|
||||||
}"""
|
}"""
|
||||||
|
@ -119,6 +119,9 @@ The current set of network parameters:
|
|||||||
For each contract class there is a list of hashes of the approved CorDapp jar versions containing that contract.
|
For each contract class there is a list of hashes of the approved CorDapp jar versions containing that contract.
|
||||||
Read more about *Zone constraints* here :doc:`api-contract-constraints`
|
Read more about *Zone constraints* here :doc:`api-contract-constraints`
|
||||||
|
|
||||||
|
:eventHorizon: Time after which nodes are considered to be unresponsive and removed from network map. Nodes republish their
|
||||||
|
``NodeInfo`` on a regular interval. Network map treats that as a heartbeat from the node.
|
||||||
|
|
||||||
More parameters will be added in future releases to regulate things like allowed port numbers, how long a node can be
|
More parameters will be added in future releases to regulate things like allowed port numbers, how long a node can be
|
||||||
offline before it is evicted from the zone, whether or not IPv6 connectivity is required for zone members, required
|
offline before it is evicted from the zone, whether or not IPv6 connectivity is required for zone members, required
|
||||||
cryptographic algorithms and rollout schedules (e.g. for moving to post quantum cryptography), parameters related to
|
cryptographic algorithms and rollout schedules (e.g. for moving to post quantum cryptography), parameters related to
|
||||||
|
@ -64,7 +64,7 @@ above. A sensible default for the missing value is provided for instantiation of
|
|||||||
order, see the discussion below.
|
order, see the discussion below.
|
||||||
|
|
||||||
As before, instances of the class at version A will be able to deserialize serialized forms of example B as it
|
As before, instances of the class at version A will be able to deserialize serialized forms of example B as it
|
||||||
will simply treat them as if the property has been removed (as from its perspective, they will have been.)
|
will simply treat them as if the property has been removed (as from its perspective, they will have been).
|
||||||
|
|
||||||
|
|
||||||
Constructor Versioning
|
Constructor Versioning
|
||||||
@ -144,7 +144,6 @@ be:
|
|||||||
|
|
||||||
// The third alteration, and how it currently exists, property e added
|
// The third alteration, and how it currently exists, property e added
|
||||||
data class Example3 (val a: Int, val b: Int, val c: Int, val d: Int, val: Int e) {
|
data class Example3 (val a: Int, val b: Int, val c: Int, val d: Int, val: Int e) {
|
||||||
// NOTE: version number purposefully omitted from annotation for demonstration purposes
|
|
||||||
@DeprecatedConstructorForDeserialization(1)
|
@DeprecatedConstructorForDeserialization(1)
|
||||||
constructor (a: Int, b: Int) : this(a, b, -1, -1, -1) // alt constructor 1
|
constructor (a: Int, b: Int) : this(a, b, -1, -1, -1) // alt constructor 1
|
||||||
@DeprecatedConstructorForDeserialization(2)
|
@DeprecatedConstructorForDeserialization(2)
|
||||||
|
@ -18,6 +18,7 @@ import net.corda.core.serialization.SerializationContext
|
|||||||
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
||||||
import net.corda.core.serialization.internal._contextSerializationEnv
|
import net.corda.core.serialization.internal._contextSerializationEnv
|
||||||
import net.corda.core.utilities.ByteSequence
|
import net.corda.core.utilities.ByteSequence
|
||||||
|
import net.corda.core.utilities.days
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||||
@ -181,7 +182,8 @@ class NetworkBootstrapper {
|
|||||||
maxMessageSize = 10485760,
|
maxMessageSize = 10485760,
|
||||||
maxTransactionSize = Int.MAX_VALUE,
|
maxTransactionSize = Int.MAX_VALUE,
|
||||||
epoch = 1,
|
epoch = 1,
|
||||||
whitelistedContractImplementations = whitelist
|
whitelistedContractImplementations = whitelist,
|
||||||
|
eventHorizon = 30.days
|
||||||
), overwriteFile = true)
|
), overwriteFile = true)
|
||||||
|
|
||||||
nodeDirs.forEach { copier.install(it) }
|
nodeDirs.forEach { copier.install(it) }
|
||||||
|
@ -114,7 +114,7 @@ abstract class EvolutionSerializer(
|
|||||||
this.resultsIndex = it.index
|
this.resultsIndex = it.index
|
||||||
} ?: if (!it.value.type.isMarkedNullable) {
|
} ?: if (!it.value.type.isMarkedNullable) {
|
||||||
throw NotSerializableException(
|
throw NotSerializableException(
|
||||||
"New parameter ${it.value.name} is mandatory, should be nullable for evolution to worK")
|
"New parameter ${it.value.name} is mandatory, should be nullable for evolution to work")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return EvolutionSerializerViaConstructor (new.type, factory, readersAsSerialized, constructor, constructorArgs)
|
return EvolutionSerializerViaConstructor (new.type, factory, readersAsSerialized, constructor, constructorArgs)
|
||||||
|
@ -193,6 +193,23 @@ class NetworkMapTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test node heartbeat`() {
|
||||||
|
internalDriver(
|
||||||
|
portAllocation = portAllocation,
|
||||||
|
compatibilityZone = compatibilityZone,
|
||||||
|
initialiseSerialization = false,
|
||||||
|
systemProperties = mapOf("net.corda.node.internal.nodeinfo.publish.interval" to 1.seconds.toString())
|
||||||
|
) {
|
||||||
|
val aliceNode = startNode(providedName = ALICE_NAME).getOrThrow()
|
||||||
|
assertThat(networkMapServer.networkMapHashes()).contains(aliceNode.nodeInfo.serialize().hash)
|
||||||
|
networkMapServer.removeNodeInfo(aliceNode.nodeInfo)
|
||||||
|
assertThat(networkMapServer.networkMapHashes()).doesNotContain(aliceNode.nodeInfo.serialize().hash)
|
||||||
|
Thread.sleep(2000)
|
||||||
|
assertThat(networkMapServer.networkMapHashes()).contains(aliceNode.nodeInfo.serialize().hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun NodeHandle.onlySees(vararg nodes: NodeInfo) {
|
private fun NodeHandle.onlySees(vararg nodes: NodeInfo) {
|
||||||
// Make sure the nodes aren't getting the node infos from their additional directories
|
// Make sure the nodes aren't getting the node infos from their additional directories
|
||||||
val nodeInfosDir = baseDirectory / CordformNode.NODE_INFO_DIRECTORY
|
val nodeInfosDir = baseDirectory / CordformNode.NODE_INFO_DIRECTORY
|
||||||
|
@ -26,9 +26,7 @@ import net.corda.core.serialization.SerializeAsToken
|
|||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.*
|
||||||
import net.corda.core.utilities.debug
|
|
||||||
import net.corda.core.utilities.getOrThrow
|
|
||||||
import net.corda.node.CordaClock
|
import net.corda.node.CordaClock
|
||||||
import net.corda.node.VersionInfo
|
import net.corda.node.VersionInfo
|
||||||
import net.corda.node.internal.classloading.requireAnnotation
|
import net.corda.node.internal.classloading.requireAnnotation
|
||||||
@ -83,6 +81,7 @@ import java.security.cert.X509Certificate
|
|||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
import java.time.format.DateTimeParseException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
@ -198,8 +197,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
val identityService = makeIdentityService(identity.certificate)
|
val identityService = makeIdentityService(identity.certificate)
|
||||||
|
|
||||||
networkMapClient = configuration.compatibilityZoneURL?.let { NetworkMapClient(it, identityService.trustRoot) }
|
networkMapClient = configuration.compatibilityZoneURL?.let { NetworkMapClient(it, identityService.trustRoot) }
|
||||||
|
val networkParameteresReader = NetworkParametersReader(identityService.trustRoot, networkMapClient, configuration.baseDirectory)
|
||||||
val networkParameters = NetworkParametersReader(identityService.trustRoot, networkMapClient, configuration.baseDirectory).networkParameters
|
val networkParameters = networkParameteresReader.networkParameters
|
||||||
check(networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) {
|
check(networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) {
|
||||||
"Node's platform version is lower than network's required minimumPlatformVersion"
|
"Node's platform version is lower than network's required minimumPlatformVersion"
|
||||||
}
|
}
|
||||||
@ -267,7 +266,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
networkMapUpdater = NetworkMapUpdater(services.networkMapCache,
|
networkMapUpdater = NetworkMapUpdater(services.networkMapCache,
|
||||||
NodeInfoWatcher(configuration.baseDirectory, getRxIoScheduler(), Duration.ofMillis(configuration.additionalNodeInfoPollingFrequencyMsec)),
|
NodeInfoWatcher(configuration.baseDirectory, getRxIoScheduler(), Duration.ofMillis(configuration.additionalNodeInfoPollingFrequencyMsec)),
|
||||||
networkMapClient,
|
networkMapClient,
|
||||||
networkParameters.serialize().hash,
|
networkParameteresReader.hash,
|
||||||
configuration.baseDirectory)
|
configuration.baseDirectory)
|
||||||
runOnStop += networkMapUpdater::close
|
runOnStop += networkMapUpdater::close
|
||||||
|
|
||||||
@ -343,6 +342,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
// Write the node-info file even if nothing's changed, just in case the file has been deleted.
|
// Write the node-info file even if nothing's changed, just in case the file has been deleted.
|
||||||
NodeInfoWatcher.saveToFile(configuration.baseDirectory, nodeInfoAndSigned)
|
NodeInfoWatcher.saveToFile(configuration.baseDirectory, nodeInfoAndSigned)
|
||||||
|
|
||||||
|
// Always republish on startup, it's treated by network map server as a heartbeat.
|
||||||
if (networkMapClient != null) {
|
if (networkMapClient != null) {
|
||||||
tryPublishNodeInfoAsync(nodeInfoAndSigned.signed, networkMapClient)
|
tryPublishNodeInfoAsync(nodeInfoAndSigned.signed, networkMapClient)
|
||||||
}
|
}
|
||||||
@ -350,18 +350,31 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
return Pair(keyPairs, nodeInfo)
|
return Pair(keyPairs, nodeInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Publish node info on startup and start task that sends every day a heartbeat - republishes node info.
|
||||||
private fun tryPublishNodeInfoAsync(signedNodeInfo: SignedNodeInfo, networkMapClient: NetworkMapClient) {
|
private fun tryPublishNodeInfoAsync(signedNodeInfo: SignedNodeInfo, networkMapClient: NetworkMapClient) {
|
||||||
|
// By default heartbeat interval should be set to 1 day, but for testing we may change it.
|
||||||
|
val republishProperty = System.getProperty("net.corda.node.internal.nodeinfo.publish.interval")
|
||||||
|
val heartbeatInterval = if (republishProperty != null) {
|
||||||
|
try {
|
||||||
|
Duration.parse(republishProperty)
|
||||||
|
} catch (e: DateTimeParseException) {
|
||||||
|
1.days
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
1.days
|
||||||
|
}
|
||||||
val executor = Executors.newSingleThreadScheduledExecutor(NamedThreadFactory("Network Map Updater", Executors.defaultThreadFactory()))
|
val executor = Executors.newSingleThreadScheduledExecutor(NamedThreadFactory("Network Map Updater", Executors.defaultThreadFactory()))
|
||||||
|
|
||||||
executor.submit(object : Runnable {
|
executor.submit(object : Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
try {
|
val republishInterval = try {
|
||||||
networkMapClient.publish(signedNodeInfo)
|
networkMapClient.publish(signedNodeInfo)
|
||||||
|
heartbeatInterval
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
log.warn("Error encountered while publishing node info, will retry again", t)
|
log.warn("Error encountered while publishing node info, will retry again", t)
|
||||||
// TODO: Exponential backoff?
|
// TODO: Exponential backoff? It should reach max interval of eventHorizon/2.
|
||||||
executor.schedule(this, 1, TimeUnit.MINUTES)
|
1.minutes
|
||||||
}
|
}
|
||||||
|
executor.schedule(this, republishInterval.toMinutes(), TimeUnit.MINUTES)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -21,11 +21,14 @@ class NetworkParametersReader(private val trustRoot: X509Certificate,
|
|||||||
private val logger = contextLogger()
|
private val logger = contextLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private data class NetworkParamsAndHash(val networkParameters: NetworkParameters, val hash: SecureHash)
|
||||||
private val networkParamsFile = baseDirectory / NETWORK_PARAMS_FILE_NAME
|
private val networkParamsFile = baseDirectory / NETWORK_PARAMS_FILE_NAME
|
||||||
private val parametersUpdateFile = baseDirectory / NETWORK_PARAMS_UPDATE_FILE_NAME
|
private val parametersUpdateFile = baseDirectory / NETWORK_PARAMS_UPDATE_FILE_NAME
|
||||||
val networkParameters by lazy { retrieveNetworkParameters() }
|
private val netParamsAndHash by lazy { retrieveNetworkParameters() }
|
||||||
|
val networkParameters get() = netParamsAndHash.networkParameters
|
||||||
|
val hash get() = netParamsAndHash.hash
|
||||||
|
|
||||||
private fun retrieveNetworkParameters(): NetworkParameters {
|
private fun retrieveNetworkParameters(): NetworkParamsAndHash {
|
||||||
val advertisedParametersHash = try {
|
val advertisedParametersHash = try {
|
||||||
networkMapClient?.getNetworkMap()?.payload?.networkParameterHash
|
networkMapClient?.getNetworkMap()?.payload?.networkParameterHash
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -43,17 +46,17 @@ class NetworkParametersReader(private val trustRoot: X509Certificate,
|
|||||||
// on the other we have parameters update process - it needs to be unified. Say you start the node, you don't have matching parameters,
|
// on the other we have parameters update process - it needs to be unified. Say you start the node, you don't have matching parameters,
|
||||||
// you get them from network map, but you have to run the approval step.
|
// you get them from network map, but you have to run the approval step.
|
||||||
if (signedParametersFromFile == null) { // Node joins for the first time.
|
if (signedParametersFromFile == null) { // Node joins for the first time.
|
||||||
downloadParameters(trustRoot, advertisedParametersHash)
|
downloadParameters(advertisedParametersHash)
|
||||||
} else if (signedParametersFromFile.raw.hash == advertisedParametersHash) { // Restarted with the same parameters.
|
} else if (signedParametersFromFile.raw.hash == advertisedParametersHash) { // Restarted with the same parameters.
|
||||||
signedParametersFromFile.verifiedNetworkMapCert(trustRoot)
|
signedParametersFromFile
|
||||||
} else { // Update case.
|
} else { // Update case.
|
||||||
readParametersUpdate(advertisedParametersHash, signedParametersFromFile.raw.hash).verifiedNetworkMapCert(trustRoot)
|
readParametersUpdate(advertisedParametersHash, signedParametersFromFile.raw.hash)
|
||||||
}
|
}
|
||||||
} else { // No compatibility zone configured. Node should proceed with parameters from file.
|
} else { // No compatibility zone configured. Node should proceed with parameters from file.
|
||||||
signedParametersFromFile?.verifiedNetworkMapCert(trustRoot) ?: throw IllegalArgumentException("Couldn't find network parameters file and compatibility zone wasn't configured/isn't reachable")
|
signedParametersFromFile ?: throw IllegalArgumentException("Couldn't find network parameters file and compatibility zone wasn't configured/isn't reachable")
|
||||||
}
|
}
|
||||||
logger.info("Loaded network parameters: $parameters")
|
logger.info("Loaded network parameters: $parameters")
|
||||||
return parameters
|
return NetworkParamsAndHash(parameters.verifiedNetworkMapCert(trustRoot), parameters.raw.hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readParametersUpdate(advertisedParametersHash: SecureHash, previousParametersHash: SecureHash): SignedNetworkParameters {
|
private fun readParametersUpdate(advertisedParametersHash: SecureHash, previousParametersHash: SecureHash): SignedNetworkParameters {
|
||||||
@ -74,14 +77,13 @@ class NetworkParametersReader(private val trustRoot: X509Certificate,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Used only when node joins for the first time.
|
// Used only when node joins for the first time.
|
||||||
private fun downloadParameters(trustRoot: X509Certificate, parametersHash: SecureHash): NetworkParameters {
|
private fun downloadParameters(parametersHash: SecureHash): SignedNetworkParameters {
|
||||||
logger.info("No network-parameters file found. Expecting network parameters to be available from the network map.")
|
logger.info("No network-parameters file found. Expecting network parameters to be available from the network map.")
|
||||||
val networkMapClient = checkNotNull(networkMapClient) {
|
val networkMapClient = checkNotNull(networkMapClient) {
|
||||||
"Node hasn't been configured to connect to a network map from which to get the network parameters"
|
"Node hasn't been configured to connect to a network map from which to get the network parameters"
|
||||||
}
|
}
|
||||||
val signedParams = networkMapClient.getNetworkParameters(parametersHash)
|
val signedParams = networkMapClient.getNetworkParameters(parametersHash)
|
||||||
val verifiedParams = signedParams.verifiedNetworkMapCert(trustRoot)
|
|
||||||
signedParams.serialize().open().copyTo(baseDirectory / NETWORK_PARAMS_FILE_NAME)
|
signedParams.serialize().open().copyTo(baseDirectory / NETWORK_PARAMS_FILE_NAME)
|
||||||
return verifiedParams
|
return signedParams
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,7 +151,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
|||||||
val (update, signedNewNetParams) = requireNotNull(newNetworkParameters) { "Couldn't find parameters update for the hash: $parametersHash" }
|
val (update, signedNewNetParams) = requireNotNull(newNetworkParameters) { "Couldn't find parameters update for the hash: $parametersHash" }
|
||||||
// We should check that we sign the right data structure hash.
|
// We should check that we sign the right data structure hash.
|
||||||
val newNetParams = signedNewNetParams.verifiedNetworkMapCert(networkMapClient.trustedRoot)
|
val newNetParams = signedNewNetParams.verifiedNetworkMapCert(networkMapClient.trustedRoot)
|
||||||
val newParametersHash = newNetParams.serialize().hash
|
val newParametersHash = signedNewNetParams.raw.hash
|
||||||
if (parametersHash == newParametersHash) {
|
if (parametersHash == newParametersHash) {
|
||||||
// The latest parameters have priority.
|
// The latest parameters have priority.
|
||||||
signedNewNetParams.serialize()
|
signedNewNetParams.serialize()
|
||||||
|
@ -6,6 +6,8 @@ import net.corda.core.internal.createDirectories
|
|||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.internal.exists
|
import net.corda.core.internal.exists
|
||||||
import net.corda.core.internal.readObject
|
import net.corda.core.internal.readObject
|
||||||
|
import net.corda.core.serialization.deserialize
|
||||||
|
import net.corda.core.utilities.days
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.node.internal.NetworkParametersReader
|
import net.corda.node.internal.NetworkParametersReader
|
||||||
import net.corda.nodeapi.internal.network.*
|
import net.corda.nodeapi.internal.network.*
|
||||||
@ -22,6 +24,7 @@ import org.junit.Test
|
|||||||
import java.net.URL
|
import java.net.URL
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
|
||||||
class NetworkParametersReaderTest {
|
class NetworkParametersReaderTest {
|
||||||
@Rule
|
@Rule
|
||||||
@ -72,4 +75,14 @@ class NetworkParametersReaderTest {
|
|||||||
val parameters = NetworkParametersReader(DEV_ROOT_CA.certificate, networkMapClient, baseDirectory).networkParameters
|
val parameters = NetworkParametersReader(DEV_ROOT_CA.certificate, networkMapClient, baseDirectory).networkParameters
|
||||||
assertThat(parameters).isEqualTo(fileParameters)
|
assertThat(parameters).isEqualTo(fileParameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `serialized parameters compatibility`() {
|
||||||
|
// Network parameters file from before eventHorizon extension
|
||||||
|
val inputStream = javaClass.classLoader.getResourceAsStream("network-compatibility/network-parameters")
|
||||||
|
assertNotNull(inputStream)
|
||||||
|
val inByteArray: ByteArray = inputStream.readBytes()
|
||||||
|
val parameters = inByteArray.deserialize<SignedNetworkParameters>()
|
||||||
|
assertThat(parameters.verified().eventHorizon).isEqualTo(Int.MAX_VALUE.days)
|
||||||
|
}
|
||||||
}
|
}
|
BIN
node/src/test/resources/network-compatibility/network-parameters
Normal file
BIN
node/src/test/resources/network-compatibility/network-parameters
Normal file
Binary file not shown.
@ -13,6 +13,7 @@ import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
|||||||
import net.corda.core.node.NetworkParameters
|
import net.corda.core.node.NetworkParameters
|
||||||
import net.corda.nodeapi.internal.network.NetworkMap
|
import net.corda.nodeapi.internal.network.NetworkMap
|
||||||
import net.corda.nodeapi.internal.network.ParametersUpdate
|
import net.corda.nodeapi.internal.network.ParametersUpdate
|
||||||
|
import net.corda.testing.common.internal.testNetworkParameters
|
||||||
import org.eclipse.jetty.server.Server
|
import org.eclipse.jetty.server.Server
|
||||||
import org.eclipse.jetty.server.ServerConnector
|
import org.eclipse.jetty.server.ServerConnector
|
||||||
import org.eclipse.jetty.server.handler.HandlerCollection
|
import org.eclipse.jetty.server.handler.HandlerCollection
|
||||||
@ -39,7 +40,7 @@ class NetworkMapServer(private val cacheTimeout: Duration,
|
|||||||
private val myHostNameValue: String = "test.host.name",
|
private val myHostNameValue: String = "test.host.name",
|
||||||
vararg additionalServices: Any) : Closeable {
|
vararg additionalServices: Any) : Closeable {
|
||||||
companion object {
|
companion object {
|
||||||
private val stubNetworkParameters = NetworkParameters(1, emptyList(), 10485760, Int.MAX_VALUE, Instant.now(), 10, emptyMap())
|
private val stubNetworkParameters = testNetworkParameters(epoch = 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val server: Server
|
private val server: Server
|
||||||
@ -77,6 +78,8 @@ class NetworkMapServer(private val cacheTimeout: Duration,
|
|||||||
.let { NetworkHostAndPort(it.host, it.localPort) }
|
.let { NetworkHostAndPort(it.host, it.localPort) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun networkMapHashes(): List<SecureHash> = service.nodeInfoMap.keys.toList()
|
||||||
|
|
||||||
fun removeNodeInfo(nodeInfo: NodeInfo) {
|
fun removeNodeInfo(nodeInfo: NodeInfo) {
|
||||||
service.removeNodeInfo(nodeInfo)
|
service.removeNodeInfo(nodeInfo)
|
||||||
}
|
}
|
||||||
@ -102,7 +105,7 @@ class NetworkMapServer(private val cacheTimeout: Duration,
|
|||||||
|
|
||||||
@Path("network-map")
|
@Path("network-map")
|
||||||
inner class InMemoryNetworkMapService {
|
inner class InMemoryNetworkMapService {
|
||||||
private val nodeInfoMap = mutableMapOf<SecureHash, SignedNodeInfo>()
|
val nodeInfoMap = mutableMapOf<SecureHash, SignedNodeInfo>()
|
||||||
val latestAcceptedParametersMap = mutableMapOf<PublicKey, SecureHash>()
|
val latestAcceptedParametersMap = mutableMapOf<PublicKey, SecureHash>()
|
||||||
private val signedNetParams by lazy {
|
private val signedNetParams by lazy {
|
||||||
networkParameters.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate)
|
networkParameters.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate)
|
||||||
|
@ -2,6 +2,8 @@ package net.corda.testing.common.internal
|
|||||||
|
|
||||||
import net.corda.core.node.NetworkParameters
|
import net.corda.core.node.NetworkParameters
|
||||||
import net.corda.core.node.NotaryInfo
|
import net.corda.core.node.NotaryInfo
|
||||||
|
import net.corda.core.utilities.days
|
||||||
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
fun testNetworkParameters(
|
fun testNetworkParameters(
|
||||||
@ -11,7 +13,8 @@ fun testNetworkParameters(
|
|||||||
maxMessageSize: Int = 10485760,
|
maxMessageSize: Int = 10485760,
|
||||||
// TODO: Make this configurable and consistence across driver, bootstrapper, demobench and NetworkMapServer
|
// TODO: Make this configurable and consistence across driver, bootstrapper, demobench and NetworkMapServer
|
||||||
maxTransactionSize: Int = maxMessageSize,
|
maxTransactionSize: Int = maxMessageSize,
|
||||||
epoch: Int = 1
|
epoch: Int = 1,
|
||||||
|
eventHorizon: Duration = 30.days
|
||||||
): NetworkParameters {
|
): NetworkParameters {
|
||||||
return NetworkParameters(
|
return NetworkParameters(
|
||||||
minimumPlatformVersion = minimumPlatformVersion,
|
minimumPlatformVersion = minimumPlatformVersion,
|
||||||
@ -20,6 +23,7 @@ fun testNetworkParameters(
|
|||||||
maxMessageSize = maxMessageSize,
|
maxMessageSize = maxMessageSize,
|
||||||
maxTransactionSize = maxTransactionSize,
|
maxTransactionSize = maxTransactionSize,
|
||||||
epoch = epoch,
|
epoch = epoch,
|
||||||
whitelistedContractImplementations = emptyMap()
|
whitelistedContractImplementations = emptyMap(),
|
||||||
|
eventHorizon = eventHorizon
|
||||||
)
|
)
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user