First approach to network parameters updates (#2412)

* Network parameters updates

Add two RPC methods networkParametersFeed and
acceptNewNetworkParameters. Implementation of client handling of network
parameters update event. Partial implementation of accepting new
parameters and installing them on the node as well as node startup with
updated parameters.

Move reading of network parameters on startup to separate
NetworkParametersReader class. Add tests.

Move NetworkParameters and NotaryInfo classes to core.

* Ignore evolvability test - to be fixed later

* Add documentation on update process
This commit is contained in:
Katarzyna Streich
2018-02-08 14:31:43 +00:00
committed by GitHub
parent cbe947694d
commit 6acff3a7df
29 changed files with 588 additions and 132 deletions

View File

@ -7,6 +7,8 @@ import net.corda.core.node.NodeInfo
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import java.security.PublicKey
import java.security.SignatureException
/**
@ -44,3 +46,11 @@ class SignedNodeInfo(val raw: SerializedBytes<NodeInfo>, val signatures: List<Di
return nodeInfo
}
}
inline fun NodeInfo.sign(signer: (PublicKey, SerializedBytes<NodeInfo>) -> DigitalSignature): SignedNodeInfo {
// For now we exclude any composite identities, see [SignedNodeInfo]
val owningKeys = legalIdentities.map { it.owningKey }.filter { it !is CompositeKey }
val serialised = serialize()
val signatures = owningKeys.map { signer(it, serialised) }
return SignedNodeInfo(serialised, signatures)
}

View File

@ -4,10 +4,7 @@ import net.corda.core.CordaOID
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignatureScheme
import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.CertRole
import net.corda.core.internal.reader
import net.corda.core.internal.uncheckedCast
import net.corda.core.internal.writer
import net.corda.core.internal.*
import net.corda.core.utilities.days
import net.corda.core.utilities.millis
import org.bouncycastle.asn1.*

View File

@ -5,7 +5,9 @@ import net.corda.cordform.CordformNode
import net.corda.core.identity.Party
import net.corda.core.internal.*
import net.corda.core.internal.concurrent.fork
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.node.NotaryInfo
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
@ -167,7 +169,7 @@ class NetworkBootstrapper {
epoch = 1
), overwriteFile = true)
nodeDirs.forEach(copier::install)
nodeDirs.forEach { copier.install(it) }
}
private fun NotaryInfo.prettyPrint(): String = "${identity.name} (${if (validating) "" else "non-"}validating)"

View File

@ -1,58 +1,48 @@
package net.corda.nodeapi.internal.network
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.internal.CertRole
import net.corda.core.internal.SignedDataWithCert
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.CordaSerializable
import net.corda.nodeapi.internal.crypto.X509Utilities
import java.security.cert.X509Certificate
import java.time.Instant
const val NETWORK_PARAMS_FILE_NAME = "network-parameters"
const val NETWORK_PARAMS_UPDATE_FILE_NAME = "network-parameters-update"
/**
* Data class containing hash of [NetworkParameters] and network participant's [NodeInfo] hashes.
* Data structure representing the network map available from the HTTP network map service as a serialised blob.
* @property nodeInfoHashes list of network participant's [NodeInfo] hashes
* @property networkParameterHash hash of the current active [NetworkParameters]
* @property parametersUpdate if present means that network operator has scheduled an update of the network parameters
*/
@CordaSerializable
data class NetworkMap(val nodeInfoHashes: List<SecureHash>, val networkParameterHash: SecureHash)
data class NetworkMap(
val nodeInfoHashes: List<SecureHash>,
val networkParameterHash: SecureHash,
val parametersUpdate: ParametersUpdate?
)
/**
* @property minimumPlatformVersion Minimum version of Corda platform that is required for nodes in the network.
* @property notaries List of well known and trusted notary identities with information on validation type.
* @property maxMessageSize Maximum P2P message sent over the wire in bytes.
* @property maxTransactionSize Maximum permitted transaction size in bytes.
* @property modifiedTime
* @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set
* of parameters.
* Data class representing scheduled network parameters update.
* @property newParametersHash Hash of the new [NetworkParameters] which can be requested from the network map
* @property description Short description of the update
* @property updateDeadline deadline by which new network parameters need to be accepted, after this date network operator
* can switch to new parameters which will result in getting nodes with old parameters out of the network
*/
// TODO Add eventHorizon - how many days a node can be offline before being automatically ejected from the network.
// It needs separate design.
// TODO Currently maxTransactionSize is not wired.
@CordaSerializable
data class NetworkParameters(
val minimumPlatformVersion: Int,
val notaries: List<NotaryInfo>,
val maxMessageSize: Int,
val maxTransactionSize: Int,
val modifiedTime: Instant,
val epoch: Int
) {
init {
require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" }
require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" }
require(epoch > 0) { "epoch must be at least 1" }
require(maxMessageSize > 0) { "maxMessageSize must be at least 1" }
require(maxTransactionSize > 0) { "maxTransactionSize must be at least 1" }
}
}
@CordaSerializable
data class NotaryInfo(val identity: Party, val validating: Boolean)
data class ParametersUpdate(
val newParametersHash: SecureHash,
val description: String,
val updateDeadline: Instant
)
fun <T : Any> SignedDataWithCert<T>.verifiedNetworkMapCert(rootCert: X509Certificate): T {
require(CertRole.extract(sig.by) == CertRole.NETWORK_MAP) { "Incorrect cert role: ${CertRole.extract(sig.by)}" }
X509Utilities.validateCertificateChain(rootCert, sig.by, rootCert)
return verified()
}
}

View File

@ -1,8 +1,7 @@
package net.corda.nodeapi.internal.network
import net.corda.core.internal.copyTo
import net.corda.core.internal.div
import net.corda.core.internal.signWithCert
import net.corda.core.internal.*
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.serialize
import net.corda.nodeapi.internal.createDevNetworkMapCa
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
@ -13,7 +12,9 @@ import java.nio.file.StandardCopyOption
class NetworkParametersCopier(
networkParameters: NetworkParameters,
networkMapCa: CertificateAndKeyPair = createDevNetworkMapCa(),
overwriteFile: Boolean = false
overwriteFile: Boolean = false,
@VisibleForTesting
val update: Boolean = false
) {
private val copyOptions = if (overwriteFile) arrayOf(StandardCopyOption.REPLACE_EXISTING) else emptyArray()
private val serialisedSignedNetParams = networkParameters.signWithCert(
@ -22,8 +23,9 @@ class NetworkParametersCopier(
).serialize()
fun install(nodeDir: Path) {
val fileName = if (update) NETWORK_PARAMS_UPDATE_FILE_NAME else NETWORK_PARAMS_FILE_NAME
try {
serialisedSignedNetParams.open().copyTo(nodeDir / NETWORK_PARAMS_FILE_NAME, *copyOptions)
serialisedSignedNetParams.open().copyTo(nodeDir / fileName, *copyOptions)
} catch (e: FileAlreadyExistsException) {
// This is only thrown if the file already exists and we didn't specify to overwrite it. In that case we
// ignore this exception as we're happy with the existing file.

View File

@ -3,10 +3,10 @@ package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.crypto.Crypto.generateKeyPair
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.sign
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NotaryInfo
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializedBytes
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.NotaryInfo
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.TestIdentity
@ -485,6 +485,7 @@ class EvolvabilityTests {
// the resulting file and add to the repo, changing the filename as appropriate
//
@Test
@Ignore("Test fails after moving NetworkParameters and NotaryInfo into core from node-api")
fun readBrokenNetworkParameters(){
val sf = testDefaultFactory()
sf.register(net.corda.nodeapi.internal.serialization.amqp.custom.InstantSerializer(sf))