mirror of
https://github.com/corda/corda.git
synced 2024-12-26 08:01:09 +00:00
ENT-1433: Implementation of NetworkParameters updates (#477)
* Implementation of NetworkParameters updates Implementation of NetworkParameters updates for network-management server side of the process. Change the way network parameters are read up on network-management startup. Introduced 3 flags to clearly communicate intentions: initial-network-parameters, update-network-parameters and flag-day. * Address Shams comments * Address comments * NetworkMapStorage refactor getLatestNetworkParameters returns NetworkParametersEntity * TODO Exit after parameters update Started implementing feature requested by devops. TODO: Fix NodeRegistrationTest * Code cleanup * Merged the two cmd line flags into one --set-network-parameters. The idea being to simply check if there are any active network parameters to determine if it's an update or not. Applying network parameters causes the server to exit once done (ENT-1559). * Merge fixes * Minor cleanup * Small fixes * Merge fixes and cleanup * TODO Implementation of cancelUpdate flag Will slightly change after more manual testing * Small cleanup * Address comments Add OneToOne relation between ParametersUpdate and NetworkParameters. * Address comments fix tests
This commit is contained in:
parent
785891515e
commit
75b467e340
@ -59,9 +59,12 @@ java -DdefaultLogLevel=TRACE -DconsoleLogLevel=TRACE -jar doorman-<version>.jar
|
||||
#Configuring network management service
|
||||
### Local signing
|
||||
|
||||
When `keystorePath` is provided in the config file, a signer will be created to handle all the signing periodically using the CA keys in the provided keystore.
|
||||
When `keystorePath` is provided in the config file, a signer will be created to handle all the signing periodically
|
||||
using the CA keys in the provided keystore.
|
||||
|
||||
The network management service can be started without a signer, the signing will be delegated to external process (e.g. HSM) connecting to the same database, the server will poll the database periodically for newly signed data and update the statuses accordingly.
|
||||
The network management service can be started without a signer, the signing will be delegated to external process
|
||||
(e.g. HSM) connecting to the same database, the server will poll the database periodically for newly signed data and
|
||||
update the statuses accordingly.
|
||||
|
||||
Additional configuration needed for local signer:
|
||||
```
|
||||
@ -77,7 +80,8 @@ Doorman service can be started with the following options :
|
||||
|
||||
### JIRA
|
||||
|
||||
The doorman service can use JIRA to manage the certificate signing request approval workflow. This can be turned on by providing JIRA connection configuration in the config file.
|
||||
The doorman service can use JIRA to manage the certificate signing request approval workflow. This can be turned on by providing
|
||||
JIRA connection configuration in the config file.
|
||||
```
|
||||
doorman {
|
||||
jira {
|
||||
@ -93,10 +97,12 @@ The doorman service can use JIRA to manage the certificate signing request appro
|
||||
```
|
||||
#### JIRA project configuration
|
||||
* The JIRA project should setup as "Business Project" with "Task" workflow.
|
||||
* Custom text field input "Request ID", and "Reject Reason" should be created in JIRA, doorman will exit with error without these custom fields.
|
||||
* Custom text field input "Request ID", and "Reject Reason" should be created in JIRA, doorman will exit with error without
|
||||
these custom fields.
|
||||
|
||||
### Auto approval
|
||||
When `approveAll` is set to `true`, the doorman will approve all requests on receive. (*This should only be enabled in a test environment)
|
||||
When `approveAll` is set to `true`, the doorman will approve all requests on receive. (*This should only be enabled in a
|
||||
test environment)
|
||||
|
||||
### Network map service
|
||||
Network map service can be enabled by providing the following config:
|
||||
@ -106,11 +112,10 @@ The doorman service can use JIRA to manage the certificate signing request appro
|
||||
signInterval = 10000
|
||||
}
|
||||
```
|
||||
`cacheTimeout`(ms) determines how often the server should poll the database for a newly signed network map and also how often nodes should poll for a new network map (by including this value in the HTTP response header). **This is not how often changes to the network map are signed, which is a different process.**
|
||||
|
||||
`cacheTimeout`(ms) determines how often the server should poll the database for a newly signed network map and also how often nodes should poll for a new network map (by including this value in the HTTP response header). **This is not how often changes to the network map are signed, which is a different process.**
|
||||
`signInterval`(ms) this is only relevant when local signer is enabled. The signer poll the database according to the `signInterval`, and create a new network map if the collection of node info hashes is different from the current network map.
|
||||
|
||||
##Example config file
|
||||
## Example config file
|
||||
```
|
||||
basedir = "."
|
||||
address = "localhost:0"
|
||||
@ -214,11 +219,17 @@ networkMap {
|
||||
|
||||
Save the parameters to `network-parameters.conf`
|
||||
|
||||
### 6. Load initial network parameters file for network map service
|
||||
A network parameters file is required to start the network map service for the first time. The initial network parameters file can be loaded using the `--update-network-parameters` flag.
|
||||
We can now restart the network management server with both doorman and network map service.
|
||||
### 6. Load the initial network parameters
|
||||
A network parameters file is required to start the network map service for the first time. The initial network parameters
|
||||
file can be loaded using the `--set-network-parameters` flag. We can now restart the network management server with
|
||||
both doorman and network map service.
|
||||
```
|
||||
java -jar doorman-<version>.jar --config-file <config file> --update-network-parameters network-parameters.conf
|
||||
java -jar doorman-<version>.jar --config-file <config file> --set-network-parameters network-parameters.conf
|
||||
```
|
||||
|
||||
The server will terminate once this process is complete. Start it back up again with both the doorman and network map service.
|
||||
```
|
||||
java -jar doorman-<version>.jar --config-file <config file>
|
||||
```
|
||||
|
||||
### 7. Archive policy
|
||||
@ -230,11 +241,42 @@ Run the following SQL script to archive the node info table (change the timestam
|
||||
delect from node_info where is_current = false and published_at < '2018-03-12'
|
||||
```
|
||||
|
||||
## Private Network Map
|
||||
# Updating the network parameters
|
||||
The initial network parameters can be subsequently changed through an update process. However, these changes must first
|
||||
be advertised to the entire network to allow nodes time to agree to the changes.
|
||||
|
||||
The server needs to be shutdown and started with the same `set-network-parameters` as before but this time the network
|
||||
parameters file must have `parametersUpdate` config block:
|
||||
|
||||
parametersUpdate {
|
||||
description = "Important update"
|
||||
updateDeadline = "2017-08-31T05:10:36.297Z" # ISO-8601 time, substitute it with update deadline
|
||||
}
|
||||
|
||||
`description` is a short description of the update that will be communicated to the nodes and `updateDeadline` is
|
||||
the time (in ISO-8601 format) by which all nodes in the network must decide that they have accepted the new parameters.
|
||||
|
||||
NOTE: Currently only backwards compatible changes to the network parameters can be made, i.e. notaries can't be removed,
|
||||
max transaction size can only increase, etc.
|
||||
|
||||
It is possible to poll the network map database to check how many network participants have accepted the new network parameters
|
||||
- the information is stored in the `node-info.accepted_parameters_hash` column.
|
||||
|
||||
When the time for switching the parameters comes, doorman should be restarted again, this time only with `flag-day` flag
|
||||
(no parameters file).
|
||||
```
|
||||
java -jar doorman-<version>.jar --flag-day
|
||||
```
|
||||
|
||||
This will switch the parameters that were previously advertised as an update to be the current ones in the network map.
|
||||
All nodes in the network need to restart to apply the new parameters. Any node which has not accepted the new parameters
|
||||
will fail to start.
|
||||
|
||||
# Private Network Map
|
||||
The private network is a tactical solution to provide temporary privacy to the initial network map.
|
||||
|
||||
### Creating a private network
|
||||
To create a new private network, a entry has to be create in the ``private_network`` table manually.
|
||||
## Creating a private network
|
||||
To create a new private network, an entry has to be created in the ``private_network`` table manually.
|
||||
|
||||
Run the following SQL script to create a new private network:
|
||||
|
||||
@ -248,12 +290,12 @@ Then use the following SQL to retrieve the private network ID for the private ne
|
||||
select id from private_network where name = 'Private Network Name'
|
||||
```
|
||||
|
||||
### Modify existing private network registration
|
||||
## Modify existing private network registration
|
||||
Since this is a tactical solution, any modification will require manual database changes.
|
||||
|
||||
**We should try to keep these changes to the minimal**
|
||||
|
||||
#### Add nodes to a private network
|
||||
### Add nodes to a private network
|
||||
|
||||
```
|
||||
update certificate_signing_request
|
||||
@ -269,7 +311,7 @@ set private_network = '<<private_network_id>>'
|
||||
where status = 'APPROVED'
|
||||
```
|
||||
|
||||
#### Move a node from its private network and into the global network map**
|
||||
### Move a node from its private network and into the global network map**
|
||||
|
||||
```
|
||||
update certificate_signing_request
|
||||
|
@ -5,7 +5,6 @@ notaries : [{
|
||||
notaryNodeInfoFile: "/Path/To/NodeInfo/File2"
|
||||
validating: false
|
||||
}]
|
||||
eventHorizonDays = 100
|
||||
minimumPlatformVersion = 1
|
||||
maxMessageSize = 10485760
|
||||
maxTransactionSize = 100
|
||||
maxTransactionSize = 10485760
|
||||
|
@ -0,0 +1,168 @@
|
||||
package com.r3.corda.networkmanage.doorman
|
||||
|
||||
import com.r3.corda.networkmanage.common.makeTestDataSourceProperties
|
||||
import com.r3.corda.networkmanage.common.utils.CertPathAndKey
|
||||
import com.r3.corda.networkmanage.doorman.signer.LocalSigner
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.internal.bufferUntilSubscribed
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.readObject
|
||||
import net.corda.core.messaging.ParametersUpdateInfo
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.nodeapi.internal.createDevNetworkMapCa
|
||||
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
|
||||
import net.corda.nodeapi.internal.network.SignedNetworkParameters
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.driver.internal.NodeHandleInternal
|
||||
import net.corda.testing.internal.createDevIntermediateCaCertPath
|
||||
import net.corda.testing.node.internal.CompatibilityZoneParams
|
||||
import net.corda.testing.node.internal.internalDriver
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.net.URL
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Instant
|
||||
|
||||
class NetworkParametersUpdateTest {
|
||||
companion object {
|
||||
private val timeoutMillis = 5.seconds.toMillis()
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule(true)
|
||||
|
||||
private val portAllocation = PortAllocation.Incremental(10000)
|
||||
private val serverAddress = portAllocation.nextHostAndPort()
|
||||
|
||||
private lateinit var rootCaCert: X509Certificate
|
||||
private lateinit var doormanCa: CertificateAndKeyPair
|
||||
private lateinit var networkMapCa: CertificateAndKeyPair
|
||||
private lateinit var dbName: String
|
||||
|
||||
private var server: NetworkManagementServer? = null
|
||||
|
||||
@Before
|
||||
fun init() {
|
||||
dbName = random63BitValue().toString()
|
||||
val (rootCa, doormanCa) = createDevIntermediateCaCertPath()
|
||||
rootCaCert = rootCa.certificate
|
||||
this.doormanCa = doormanCa
|
||||
networkMapCa = createDevNetworkMapCa(rootCa)
|
||||
}
|
||||
|
||||
@After
|
||||
fun cleanUp() {
|
||||
server?.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `network parameters update commnunicated to node`() {
|
||||
// Initialise the server with some network parameters
|
||||
val initialNetParams = NetworkParametersCmd.Set(
|
||||
notaries = emptyList(),
|
||||
minimumPlatformVersion = 1,
|
||||
maxMessageSize = 1_000_000,
|
||||
maxTransactionSize = 1_000_000,
|
||||
parametersUpdate = null
|
||||
)
|
||||
applyNetworkParametersAndStart(initialNetParams)
|
||||
|
||||
val compatibilityZone = CompatibilityZoneParams(
|
||||
URL("http://$serverAddress"),
|
||||
publishNotaries = {},
|
||||
rootCert = rootCaCert
|
||||
)
|
||||
|
||||
internalDriver(
|
||||
portAllocation = portAllocation,
|
||||
compatibilityZone = compatibilityZone,
|
||||
initialiseSerialization = false,
|
||||
extraCordappPackagesToScan = listOf("net.corda.finance")
|
||||
) {
|
||||
var (alice) = listOf(
|
||||
startNode(providedName = ALICE_NAME),
|
||||
defaultNotaryNode
|
||||
).transpose().getOrThrow()
|
||||
alice as NodeHandleInternal
|
||||
|
||||
val snapshot = alice.rpc.networkParametersFeed().snapshot
|
||||
val updates = alice.rpc.networkParametersFeed().updates.bufferUntilSubscribed()
|
||||
assertThat(snapshot).isNull()
|
||||
|
||||
val updateDeadline = Instant.now() + 10.seconds
|
||||
|
||||
applyNetworkParametersAndStart(initialNetParams.copy(
|
||||
maxTransactionSize = 1_000_001,
|
||||
parametersUpdate = ParametersUpdateConfig(
|
||||
description = "Very Important Update",
|
||||
updateDeadline = updateDeadline
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
updates.expectEvents(isStrict = true) {
|
||||
sequence(
|
||||
expect { update: ParametersUpdateInfo ->
|
||||
assertEquals(update.description, "Very Important Update")
|
||||
assertEquals(update.parameters.maxTransactionSize, 1_000_001)
|
||||
assertEquals(update.parameters.epoch, 2) // The epoch must increment automatically.
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val paramUpdateInfo = alice.rpc.networkParametersFeed().snapshot!!
|
||||
alice.rpc.acceptNewNetworkParameters(paramUpdateInfo.hash)
|
||||
|
||||
// Make sure we've passed the deadline
|
||||
Thread.sleep(Math.max(updateDeadline.toEpochMilli() - System.currentTimeMillis(), 0))
|
||||
applyNetworkParametersAndStart(NetworkParametersCmd.FlagDay)
|
||||
|
||||
alice.stop()
|
||||
alice = startNode(providedName = ALICE_NAME).getOrThrow() as NodeHandleInternal
|
||||
|
||||
// TODO It is also possible to check what version of parameters node runs by writing flow that reads that value from ServiceHub
|
||||
val networkParameters = (alice.configuration.baseDirectory / NETWORK_PARAMS_FILE_NAME)
|
||||
.readObject<SignedNetworkParameters>().verified()
|
||||
assertEquals(networkParameters, paramUpdateInfo.parameters)
|
||||
assertThat(alice.rpc.networkParametersFeed().snapshot).isNull() // Check that NMS doesn't advertise updates anymore.
|
||||
}
|
||||
}
|
||||
|
||||
private fun startServer(startNetworkMap: Boolean = true): NetworkManagementServer {
|
||||
val server = NetworkManagementServer(makeTestDataSourceProperties(dbName), DatabaseConfig(runMigration = true))
|
||||
server.start(
|
||||
serverAddress,
|
||||
CertPathAndKey(listOf(doormanCa.certificate, rootCaCert), doormanCa.keyPair.private),
|
||||
DoormanConfig(approveAll = true, jira = null, approveInterval = timeoutMillis),
|
||||
if (startNetworkMap) {
|
||||
NetworkMapStartParams(
|
||||
LocalSigner(networkMapCa),
|
||||
NetworkMapConfig(cacheTimeout = timeoutMillis, signInterval = timeoutMillis)
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
return server
|
||||
}
|
||||
|
||||
private fun applyNetworkParametersAndStart(networkParametersCmd: NetworkParametersCmd) {
|
||||
server?.close()
|
||||
NetworkManagementServer(makeTestDataSourceProperties(dbName), DatabaseConfig(runMigration = true)).use {
|
||||
it.processNetworkParameters(networkParametersCmd)
|
||||
}
|
||||
server = startServer(startNetworkMap = true)
|
||||
// Wait for server to process the parameters update and for the nodes to poll again
|
||||
Thread.sleep(timeoutMillis * 2)
|
||||
}
|
||||
}
|
@ -11,17 +11,13 @@
|
||||
package com.r3.corda.networkmanage.doorman
|
||||
|
||||
import com.r3.corda.networkmanage.common.makeTestDataSourceProperties
|
||||
import com.r3.corda.networkmanage.common.persistence.configureDatabase
|
||||
import com.r3.corda.networkmanage.common.utils.CertPathAndKey
|
||||
import com.r3.corda.networkmanage.doorman.signer.LocalSigner
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.exists
|
||||
import net.corda.core.internal.list
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.seconds
|
||||
@ -30,9 +26,7 @@ import net.corda.finance.flows.CashIssueAndPaymentFlow
|
||||
import net.corda.nodeapi.internal.createDevNetworkMapCa
|
||||
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.driver.NodeHandle
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.driver.internal.NodeHandleInternal
|
||||
@ -55,12 +49,11 @@ class NodeRegistrationTest : IntegrationTest() {
|
||||
private val notaryName = CordaX500Name("NotaryService", "Zurich", "CH")
|
||||
private val aliceName = CordaX500Name("Alice", "London", "GB")
|
||||
private val genevieveName = CordaX500Name("Genevieve", "London", "GB")
|
||||
private val timeoutMillis = 5.seconds.toMillis()
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val databaseSchemas = IntegrationTestSchemas(notaryName.organisation, aliceName.organisation, genevieveName.organisation)
|
||||
|
||||
private val timeoutMillis = 5.seconds.toMillis()
|
||||
}
|
||||
|
||||
@Rule
|
||||
@ -71,7 +64,7 @@ class NodeRegistrationTest : IntegrationTest() {
|
||||
private val serverAddress = portAllocation.nextHostAndPort()
|
||||
|
||||
private lateinit var rootCaCert: X509Certificate
|
||||
private lateinit var csrCa: CertificateAndKeyPair
|
||||
private lateinit var doormanCa: CertificateAndKeyPair
|
||||
private lateinit var networkMapCa: CertificateAndKeyPair
|
||||
private lateinit var dbName: String
|
||||
|
||||
@ -82,7 +75,7 @@ class NodeRegistrationTest : IntegrationTest() {
|
||||
dbName = random63BitValue().toString()
|
||||
val (rootCa, doormanCa) = createDevIntermediateCaCertPath()
|
||||
rootCaCert = rootCa.certificate
|
||||
this.csrCa = doormanCa
|
||||
this.doormanCa = doormanCa
|
||||
networkMapCa = createDevNetworkMapCa(rootCa)
|
||||
}
|
||||
|
||||
@ -96,17 +89,19 @@ class NodeRegistrationTest : IntegrationTest() {
|
||||
// Start the server without the network parameters config which won't start the network map. Just the doorman
|
||||
// registration process will start up, allowing us to register the notaries which will then be used in the network
|
||||
// parameters.
|
||||
server = startNetworkManagementServer(networkParameters = null)
|
||||
server = startServer(startNetworkMap = false)
|
||||
val compatibilityZone = CompatibilityZoneParams(
|
||||
URL("http://$serverAddress"),
|
||||
publishNotaries = { notaryInfos ->
|
||||
val setNetParams = NetworkParametersCmd.Set(
|
||||
notaries = notaryInfos,
|
||||
minimumPlatformVersion = 1,
|
||||
maxMessageSize = 10485760,
|
||||
maxTransactionSize = 10485760,
|
||||
parametersUpdate = null
|
||||
)
|
||||
// Restart the server once we're able to generate the network parameters
|
||||
server!!.close()
|
||||
server = startNetworkManagementServer(testNetworkParameters(notaryInfos))
|
||||
// Once restarted we delay starting the nodes to make sure the network map server has processed the
|
||||
// network parameters, otherwise the nodes will fail to start as the network parameters won't be
|
||||
// available for them to download.
|
||||
Thread.sleep(2 * timeoutMillis)
|
||||
applyNetworkParametersAndStart(setNetParams)
|
||||
},
|
||||
rootCert = rootCaCert
|
||||
)
|
||||
@ -149,25 +144,6 @@ class NodeRegistrationTest : IntegrationTest() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun startNetworkManagementServer(networkParameters: NetworkParameters?): NetworkManagementServer {
|
||||
return NetworkManagementServer().apply {
|
||||
start(
|
||||
serverAddress,
|
||||
configureDatabase(makeTestDataSourceProperties(dbName), DatabaseConfig(runMigration = true)),
|
||||
CertPathAndKey(listOf(csrCa.certificate, rootCaCert), csrCa.keyPair.private),
|
||||
DoormanConfig(approveAll = true, jira = null, approveInterval = timeoutMillis),
|
||||
networkParameters?.let {
|
||||
NetworkMapStartParams(
|
||||
LocalSigner(networkMapCa),
|
||||
it,
|
||||
NetworkMapConfig(cacheTimeout = timeoutMillis, signInterval = timeoutMillis)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun NodeHandleInternal.onlySeesFromNetworkMap(vararg nodes: NodeHandle) {
|
||||
// Make sure the nodes aren't getting the node infos from their additional directories
|
||||
val nodeInfosDir = configuration.baseDirectory / CordformNode.NODE_INFO_DIRECTORY
|
||||
@ -176,4 +152,32 @@ class NodeRegistrationTest : IntegrationTest() {
|
||||
}
|
||||
assertThat(rpc.networkMapSnapshot()).containsOnlyElementsOf(nodes.map { it.nodeInfo })
|
||||
}
|
||||
|
||||
private fun startServer(startNetworkMap: Boolean = true): NetworkManagementServer {
|
||||
val server = NetworkManagementServer(makeTestDataSourceProperties(dbName), DatabaseConfig(runMigration = true))
|
||||
server.start(
|
||||
serverAddress,
|
||||
CertPathAndKey(listOf(doormanCa.certificate, rootCaCert), doormanCa.keyPair.private),
|
||||
DoormanConfig(approveAll = true, jira = null, approveInterval = timeoutMillis),
|
||||
if (startNetworkMap) {
|
||||
NetworkMapStartParams(
|
||||
LocalSigner(networkMapCa),
|
||||
NetworkMapConfig(cacheTimeout = timeoutMillis, signInterval = timeoutMillis)
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
return server
|
||||
}
|
||||
|
||||
private fun applyNetworkParametersAndStart(networkParametersCmd: NetworkParametersCmd) {
|
||||
server?.close()
|
||||
NetworkManagementServer(makeTestDataSourceProperties(dbName), DatabaseConfig(runMigration = true)).use {
|
||||
it.processNetworkParameters(networkParametersCmd)
|
||||
}
|
||||
server = startServer(startNetworkMap = true)
|
||||
// Wait for server to process the parameters update and for the nodes to poll again
|
||||
Thread.sleep(timeoutMillis * 2)
|
||||
}
|
||||
}
|
||||
|
@ -96,14 +96,11 @@ class SigningServiceIntegrationTest : HsmBaseTest() {
|
||||
@Test
|
||||
fun `Signing service signs approved CSRs`() {
|
||||
//Start doorman server
|
||||
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true))
|
||||
|
||||
NetworkManagementServer().use { server ->
|
||||
NetworkManagementServer(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true)).use { server ->
|
||||
server.start(
|
||||
hostAndPort = NetworkHostAndPort(HOST, 0),
|
||||
database = database,
|
||||
csrCertPathAndKey = null,
|
||||
doormanServiceParameter = DoormanConfig(approveAll = true, approveInterval = 2.seconds.toMillis(), jira = null),
|
||||
doormanConfig = DoormanConfig(approveAll = true, approveInterval = 2.seconds.toMillis(), jira = null),
|
||||
startNetworkMap = null)
|
||||
val doormanHostAndPort = server.hostAndPort
|
||||
// Start Corda network registration.
|
||||
@ -164,9 +161,6 @@ class SigningServiceIntegrationTest : HsmBaseTest() {
|
||||
doReturn("trustpass").whenever(it).trustStorePassword
|
||||
doReturn("cordacadevpass").whenever(it).keyStorePassword
|
||||
doReturn("iTest@R3.com").whenever(it).emailAddress
|
||||
// doReturn(X509KeyStore.fromFile(it.nodeKeystore, it.keyStorePassword, true)).whenever(it).loadNodeKeyStore(any())
|
||||
// doReturn(X509KeyStore.fromFile(it.sslKeystore, it.keyStorePassword, true)).whenever(it).loadSslKeyStore(any())
|
||||
// doReturn(trustStore).whenever(it).loadTrustStore(any())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.r3.corda.networkmanage.common.persistence
|
||||
|
||||
import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequestStorage
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||
|
||||
/**
|
||||
|
@ -12,16 +12,20 @@ package com.r3.corda.networkmanage.common.persistence
|
||||
|
||||
import com.r3.corda.networkmanage.common.persistence.entity.NetworkMapEntity
|
||||
import com.r3.corda.networkmanage.common.persistence.entity.NetworkParametersEntity
|
||||
import com.r3.corda.networkmanage.common.persistence.entity.ParametersUpdateEntity
|
||||
import com.r3.corda.networkmanage.common.persistence.entity.NodeInfoEntity
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.DigitalSignatureWithCert
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.nodeapi.internal.network.NetworkMapAndSigned
|
||||
import net.corda.nodeapi.internal.network.ParametersUpdate
|
||||
import net.corda.nodeapi.internal.network.SignedNetworkParameters
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Data access object interface for NetworkMap persistence layer
|
||||
*/
|
||||
// TODO This storage abstraction should be removed. It results in less readable code when constructing network map in NetworkMapSigner
|
||||
interface NetworkMapStorage {
|
||||
/**
|
||||
* Returns the active network map, or null
|
||||
@ -52,9 +56,31 @@ interface NetworkMapStorage {
|
||||
fun saveNetworkParameters(networkParameters: NetworkParameters, signature: DigitalSignatureWithCert?): SecureHash
|
||||
|
||||
/**
|
||||
* Retrieves the latest (i.e. most recently inserted) network parameters
|
||||
* Save new parameters update information with corresponding network parameters. Only one parameters update entity can be present at any time. Any existing
|
||||
* parameters update is cleared and overwritten by this one.
|
||||
*/
|
||||
fun saveNewParametersUpdate(networkParameters: NetworkParameters, description: String, updateDeadline: Instant)
|
||||
|
||||
/**
|
||||
* Indicate that it is time to switch network parameters in network map from active ones to the ones from update.
|
||||
* @param parametersHash hash of the parameters from update
|
||||
*/
|
||||
fun setFlagDay(parametersHash: SecureHash)
|
||||
|
||||
/**
|
||||
* Retrieves the latest (i.e. most recently inserted) network parameters entity
|
||||
* Note that they may not have been signed up yet.
|
||||
* @return latest network parameters
|
||||
* @return latest network parameters entity
|
||||
*/
|
||||
fun getLatestNetworkParameters(): NetworkParametersEntity?
|
||||
|
||||
/**
|
||||
* Retrieve any parameters update that may be active, null if none are present.
|
||||
*/
|
||||
fun getParametersUpdate(): ParametersUpdateEntity?
|
||||
|
||||
/**
|
||||
* Removes any scheduled parameters updates.
|
||||
*/
|
||||
fun clearParametersUpdates()
|
||||
}
|
||||
|
@ -32,10 +32,24 @@ interface NodeInfoStorage {
|
||||
*/
|
||||
fun getNodeInfo(nodeInfoHash: SecureHash): SignedNodeInfo?
|
||||
|
||||
/**
|
||||
* Retrieve latest accepted parameters hash for nodeInfo with given hash.
|
||||
* @return Hash of network parameters that the node has accepted or null if couldn't find node info with given hash or
|
||||
* there is no information on accepted parameters hash stored for this entity
|
||||
*/
|
||||
fun getAcceptedParametersUpdateHash(nodeInfoHash: SecureHash): SecureHash?
|
||||
|
||||
/**
|
||||
* The [nodeInfoAndSigned] is keyed by the public key, old node info with the same public key will be replaced by the new node info.
|
||||
* @param nodeInfoAndSigned signed node info data to be stored
|
||||
* @return hash for the newly created node info entry
|
||||
*/
|
||||
fun putNodeInfo(nodeInfoAndSigned: NodeInfoAndSigned): SecureHash
|
||||
|
||||
/**
|
||||
* Store information about latest accepted [NetworkParameters] hash.
|
||||
* @param publicKeyHash Hash of public key that accepted network parameters. This public key should belong to [NodeInfo]
|
||||
* @param acceptedParametersHash Hash of latest accepted network parameters.
|
||||
*/
|
||||
fun ackNodeInfoParametersUpdate(publicKeyHash: SecureHash, acceptedParametersHash: SecureHash)
|
||||
}
|
@ -68,7 +68,8 @@ sealed class NetworkManagementSchemaServices {
|
||||
CertificateRevocationListEntity::class.java,
|
||||
NodeInfoEntity::class.java,
|
||||
NetworkParametersEntity::class.java,
|
||||
NetworkMapEntity::class.java)) {
|
||||
NetworkMapEntity::class.java,
|
||||
ParametersUpdateEntity::class.java)) {
|
||||
override val migrationResource = "network-manager.changelog-master"
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,11 @@ import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.DigitalSignatureWithCert
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.nodeapi.internal.network.ParametersUpdate
|
||||
import net.corda.nodeapi.internal.network.NetworkMapAndSigned
|
||||
import net.corda.nodeapi.internal.network.SignedNetworkParameters
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import java.time.Instant
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseTransaction
|
||||
|
||||
/**
|
||||
@ -86,12 +88,21 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw
|
||||
val serialised = networkParameters.serialize()
|
||||
val hash = serialised.hash
|
||||
database.transaction {
|
||||
session.saveOrUpdate(NetworkParametersEntity(
|
||||
parametersBytes = serialised.bytes,
|
||||
parametersHash = hash.toString(),
|
||||
signature = signature?.bytes,
|
||||
certificate = signature?.by?.encoded
|
||||
))
|
||||
val entity = getNetworkParametersEntity(hash)
|
||||
val newNetworkParamsEntity = if (entity != null) {
|
||||
entity.copy(
|
||||
signature = signature?.bytes,
|
||||
certificate = signature?.by?.encoded
|
||||
)
|
||||
} else {
|
||||
NetworkParametersEntity(
|
||||
parametersBytes = serialised.bytes,
|
||||
parametersHash = hash.toString(),
|
||||
signature = signature?.bytes,
|
||||
certificate = signature?.by?.encoded
|
||||
)
|
||||
}
|
||||
session.merge(newNetworkParamsEntity)
|
||||
}
|
||||
return hash
|
||||
}
|
||||
@ -110,6 +121,52 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw
|
||||
}
|
||||
}
|
||||
|
||||
override fun saveNewParametersUpdate(networkParameters: NetworkParameters, description: String, updateDeadline: Instant) {
|
||||
database.transaction {
|
||||
val hash = saveNetworkParameters(networkParameters, null)
|
||||
val netParamsEntity = getNetworkParametersEntity(hash)!!
|
||||
clearParametersUpdates()
|
||||
session.save(ParametersUpdateEntity(0, netParamsEntity, description, updateDeadline))
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearParametersUpdates() {
|
||||
database.transaction {
|
||||
val delete = "delete from ${ParametersUpdateEntity::class.java.name}"
|
||||
session.createQuery(delete).executeUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getParametersUpdate(): ParametersUpdateEntity? {
|
||||
return database.transaction {
|
||||
val currentParametersHash = getActiveNetworkMap()?.toNetworkMap()?.networkParameterHash
|
||||
val latestParameters = getLatestNetworkParameters()
|
||||
val criteria = session.criteriaBuilder.createQuery(ParametersUpdateEntity::class.java)
|
||||
val root = criteria.from(ParametersUpdateEntity::class.java)
|
||||
val query = criteria.select(root)
|
||||
// We just want the last entry
|
||||
val parametersUpdate = session.createQuery(query).setMaxResults(1).uniqueResult()
|
||||
check(parametersUpdate == null || latestParameters == parametersUpdate.networkParameters) {
|
||||
"ParametersUpdate doesn't correspond to latest network parameters"
|
||||
}
|
||||
// Highly unlikely, but...
|
||||
check (parametersUpdate == null || latestParameters?.parametersHash != currentParametersHash?.toString()) {
|
||||
"Having update for parameters that are already in network map"
|
||||
}
|
||||
parametersUpdate
|
||||
}
|
||||
}
|
||||
|
||||
override fun setFlagDay(parametersHash: SecureHash) {
|
||||
database.transaction {
|
||||
val parametersUpdateEntity = getParametersUpdate() ?: throw IllegalArgumentException("Setting flag day but no parameters update to switch to")
|
||||
if (parametersHash.toString() != parametersUpdateEntity.networkParameters.parametersHash) {
|
||||
throw IllegalArgumentException("Setting flag day for parameters: $parametersHash, but in database we have update for: ${parametersUpdateEntity.networkParameters.parametersHash}")
|
||||
}
|
||||
session.merge(parametersUpdateEntity.copy(flagDay = true))
|
||||
}
|
||||
}
|
||||
|
||||
private fun DatabaseTransaction.getNetworkParametersEntity(hash: SecureHash): NetworkParametersEntity? {
|
||||
return uniqueEntityWhere { builder, path ->
|
||||
builder.equal(path.get<String>(NetworkParametersEntity::parametersHash.name), hash.toString())
|
||||
|
@ -13,6 +13,7 @@ package com.r3.corda.networkmanage.common.persistence
|
||||
import com.r3.corda.networkmanage.common.persistence.entity.CertificateSigningRequestEntity
|
||||
import com.r3.corda.networkmanage.common.persistence.entity.NodeInfoEntity
|
||||
import com.r3.corda.networkmanage.common.utils.buildCertPath
|
||||
import com.r3.corda.networkmanage.common.utils.hashString
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.internal.CertRole
|
||||
@ -54,6 +55,7 @@ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeIn
|
||||
val hash = signedNodeInfo.raw.hash
|
||||
val hashedNodeInfo = NodeInfoEntity(
|
||||
nodeInfoHash = hash.toString(),
|
||||
identityPkHash = nodeInfo.legalIdentities.first().owningKey.hashString(),
|
||||
certificateSigningRequest = request,
|
||||
signedNodeInfoBytes = signedNodeInfo.serialize().bytes,
|
||||
isCurrent = true)
|
||||
@ -68,6 +70,13 @@ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeIn
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAcceptedParametersUpdateHash(nodeInfoHash: SecureHash): SecureHash? {
|
||||
return database.transaction {
|
||||
val hashString = session.find(NodeInfoEntity::class.java, nodeInfoHash.toString())?.acceptedParametersHash
|
||||
if (hashString == null || hashString.isEmpty()) null else SecureHash.parse(hashString)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCertificatePath(publicKeyHash: SecureHash): CertPath? {
|
||||
return database.transaction {
|
||||
val request = getSignedRequestByPublicHash(publicKeyHash)
|
||||
@ -75,6 +84,20 @@ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeIn
|
||||
}
|
||||
}
|
||||
|
||||
override fun ackNodeInfoParametersUpdate(publicKeyHash: SecureHash, acceptedParametersHash: SecureHash) {
|
||||
return database.transaction {
|
||||
val builder = session.criteriaBuilder
|
||||
val query = builder.createQuery(NodeInfoEntity::class.java).run {
|
||||
from(NodeInfoEntity::class.java).run {
|
||||
where(builder.equal(get<NodeInfoEntity>(NodeInfoEntity::identityPkHash.name), publicKeyHash.toString()))
|
||||
}
|
||||
}
|
||||
val nodeInfo = session.createQuery(query).setMaxResults(1).uniqueResult()
|
||||
val newInfo = nodeInfo.copy(acceptedParametersHash = acceptedParametersHash.toString())
|
||||
session.merge(newInfo)
|
||||
}
|
||||
}
|
||||
|
||||
private fun DatabaseTransaction.getSignedRequestByPublicHash(publicKeyHash: SecureHash): CertificateSigningRequestEntity? {
|
||||
return uniqueEntityWhere { builder, path ->
|
||||
val publicKeyEq = builder.equal(path.get<String>(CertificateSigningRequestEntity::publicKeyHash.name), publicKeyHash.toString())
|
||||
|
@ -55,4 +55,19 @@ class NetworkParametersEntity(
|
||||
DigitalSignatureWithCert(X509CertificateFactory().generateCertificate(certificate.inputStream()), signature)
|
||||
)
|
||||
}
|
||||
|
||||
fun copy(parametersHash: String = this.parametersHash,
|
||||
created: Instant = this.created,
|
||||
parametersBytes: ByteArray = this.parametersBytes,
|
||||
signature: ByteArray? = this.signature,
|
||||
certificate: ByteArray? = this.certificate
|
||||
): NetworkParametersEntity {
|
||||
return NetworkParametersEntity(
|
||||
parametersHash = parametersHash,
|
||||
created = created,
|
||||
parametersBytes = parametersBytes,
|
||||
signature = signature,
|
||||
certificate = certificate
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,9 @@ data class NodeInfoEntity(
|
||||
@Column(name = "node_info_hash", length = 64)
|
||||
val nodeInfoHash: String = "",
|
||||
|
||||
@Column(name = "public_key_hash", length = 64)
|
||||
val identityPkHash: String = "",
|
||||
|
||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "certificate_signing_request")
|
||||
val certificateSigningRequest: CertificateSigningRequestEntity,
|
||||
@ -35,10 +38,30 @@ data class NodeInfoEntity(
|
||||
val isCurrent: Boolean,
|
||||
|
||||
@Column(name = "published_at")
|
||||
val publishedAt: Instant = Instant.now()
|
||||
val publishedAt: Instant = Instant.now(),
|
||||
|
||||
@Column(name = "accepted_parameters_hash", length = 64)
|
||||
val acceptedParametersHash: String = ""
|
||||
) {
|
||||
/**
|
||||
* Deserialize NodeInfoEntity.signedNodeInfoBytes into the [SignedNodeInfo] instance
|
||||
*/
|
||||
fun toSignedNodeInfo() = signedNodeInfoBytes.deserialize<SignedNodeInfo>()
|
||||
|
||||
fun copy(nodeInfoHash: String = this.nodeInfoHash,
|
||||
certificateSigningRequest: CertificateSigningRequestEntity = this.certificateSigningRequest,
|
||||
signedNodeInfoBytes: ByteArray = this.signedNodeInfoBytes,
|
||||
isCurrent: Boolean = this.isCurrent,
|
||||
publishedAt: Instant = this.publishedAt,
|
||||
acceptedParametersHash: String = this.acceptedParametersHash
|
||||
): NodeInfoEntity {
|
||||
return NodeInfoEntity(
|
||||
nodeInfoHash = nodeInfoHash,
|
||||
certificateSigningRequest = certificateSigningRequest,
|
||||
signedNodeInfoBytes = signedNodeInfoBytes,
|
||||
isCurrent = isCurrent,
|
||||
publishedAt = publishedAt,
|
||||
acceptedParametersHash = acceptedParametersHash
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
package com.r3.corda.networkmanage.common.persistence.entity
|
||||
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.nodeapi.internal.network.ParametersUpdate
|
||||
import java.time.Instant
|
||||
import javax.persistence.*
|
||||
|
||||
@Entity
|
||||
@Table(name = "parameters_update")
|
||||
class ParametersUpdateEntity(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.SEQUENCE)
|
||||
val id: Long? = null,
|
||||
|
||||
// TODO use @MapsId to get rid of additional sequence
|
||||
@ManyToOne(fetch = FetchType.EAGER, optional = false)
|
||||
@JoinColumn(name = "network_parameters", foreignKey = ForeignKey(name = "FK__param_up__net_param"))
|
||||
val networkParameters: NetworkParametersEntity,
|
||||
|
||||
@Column(name = "description")
|
||||
val description: String,
|
||||
|
||||
@Column(name = "update_deadline")
|
||||
val updateDeadline: Instant,
|
||||
|
||||
// This boolean flag is used when we want to explicitly point that it's time to switch parameters in network map.
|
||||
@Column(name = "flag_day")
|
||||
val flagDay: Boolean = false
|
||||
) {
|
||||
fun toParametersUpdate(): ParametersUpdate = ParametersUpdate(SecureHash.parse(networkParameters.parametersHash), description, updateDeadline)
|
||||
|
||||
fun toNetMapUpdate(): ParametersUpdate? = if (!flagDay) ParametersUpdate(SecureHash.parse(networkParameters.parametersHash), description, updateDeadline) else null
|
||||
|
||||
fun copy(id: Long? = this.id,
|
||||
networkParameters: NetworkParametersEntity = this.networkParameters,
|
||||
description: String = this.description,
|
||||
updateDeadline: Instant = this.updateDeadline,
|
||||
flagDay: Boolean = this.flagDay): ParametersUpdateEntity {
|
||||
return ParametersUpdateEntity(
|
||||
id = id,
|
||||
networkParameters = networkParameters,
|
||||
description = description,
|
||||
updateDeadline = updateDeadline,
|
||||
flagDay = flagDay
|
||||
)
|
||||
}
|
||||
}
|
@ -12,12 +12,15 @@ package com.r3.corda.networkmanage.common.signer
|
||||
|
||||
import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.nodeapi.internal.network.NetworkMap
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.nodeapi.internal.network.NetworkMap
|
||||
import net.corda.nodeapi.internal.network.NetworkMapAndSigned
|
||||
|
||||
|
||||
class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private val signer: Signer) {
|
||||
private companion object {
|
||||
val logger = contextLogger()
|
||||
@ -27,37 +30,39 @@ class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private
|
||||
* Signs the network map and latest network parameters if they haven't been signed yet.
|
||||
*/
|
||||
fun signNetworkMap() {
|
||||
// TODO There is no network parameters update process in place yet. We assume that latest parameters are to be used
|
||||
// in current network map.
|
||||
val latestNetParamsEntity = networkMapStorage.getLatestNetworkParameters()
|
||||
if (latestNetParamsEntity == null) {
|
||||
logger.warn("No network parameters present")
|
||||
logger.debug("Fetching current network map...")
|
||||
val currentNetworkMap = networkMapStorage.getActiveNetworkMap()
|
||||
if (currentNetworkMap == null) {
|
||||
logger.info("There is currently no network map")
|
||||
} else {
|
||||
logger.debug { "Current network map: $currentNetworkMap" }
|
||||
}
|
||||
val currentNetworkParameters = currentNetworkMap?.networkParameters
|
||||
logger.debug("Current network map parameters: ${currentNetworkParameters?.toNetworkParameters()}")
|
||||
logger.debug("Fetching node info hashes with VALID certificates...")
|
||||
val nodeInfoHashes = networkMapStorage.getActiveNodeInfoHashes()
|
||||
logger.debug("Retrieved node info hashes: $nodeInfoHashes")
|
||||
val latestNetworkParameters = networkMapStorage.getLatestNetworkParameters()
|
||||
if (latestNetworkParameters == null) {
|
||||
logger.debug("No network parameters present")
|
||||
return
|
||||
}
|
||||
|
||||
val latestNetParams = latestNetParamsEntity.toNetworkParameters()
|
||||
logger.debug { "Latest network parameters: $latestNetParams" }
|
||||
|
||||
if (!latestNetParamsEntity.isSigned) {
|
||||
signAndPersistNetworkParameters(latestNetParams)
|
||||
logger.debug { "Retrieved latest network parameters: ${latestNetworkParameters.toNetworkParameters()}" }
|
||||
val parametersUpdate = networkMapStorage.getParametersUpdate()
|
||||
logger.debug { "Retrieved parameters update: $parametersUpdate" }
|
||||
// We persist signed parameters only if they were not persisted before (they are not in currentSignedNetworkMap as normal parameters or as an update)
|
||||
if (!latestNetworkParameters.isSigned) {
|
||||
signAndPersistNetworkParameters(latestNetworkParameters.toNetworkParameters())
|
||||
} else {
|
||||
logger.debug("Network parameters are already signed")
|
||||
logger.debug { "No need to sign any network parameters as they're up-to-date" }
|
||||
}
|
||||
|
||||
val nodeInfoHashes = networkMapStorage.getActiveNodeInfoHashes()
|
||||
logger.debug { "Active node-info hashes:\n${nodeInfoHashes.joinToString("\n")}" }
|
||||
|
||||
val currentNetworkMap = networkMapStorage.getActiveNetworkMap()?.toNetworkMap()
|
||||
if (currentNetworkMap != null) {
|
||||
logger.debug { "Current network map: $currentNetworkMap" }
|
||||
} else {
|
||||
logger.info("There is currently no network map")
|
||||
}
|
||||
|
||||
val newNetworkMap = NetworkMap(nodeInfoHashes, SecureHash.parse(latestNetParamsEntity.parametersHash), null)
|
||||
val parametersToNetworkMap = if (parametersUpdate?.flagDay == true || currentNetworkParameters == null) {
|
||||
networkMapStorage.clearParametersUpdates()
|
||||
latestNetworkParameters
|
||||
} else currentNetworkParameters
|
||||
val newNetworkMap = NetworkMap(nodeInfoHashes, SecureHash.parse(parametersToNetworkMap.parametersHash), parametersUpdate?.toNetMapUpdate())
|
||||
logger.debug { "Potential new network map: $newNetworkMap" }
|
||||
|
||||
if (currentNetworkMap != newNetworkMap) {
|
||||
if (currentNetworkMap?.toNetworkMap() != newNetworkMap) {
|
||||
val netNetworkMapAndSigned = NetworkMapAndSigned(newNetworkMap) { signer.signBytes(it.bytes) }
|
||||
networkMapStorage.saveNewActiveNetworkMap(netNetworkMapAndSigned)
|
||||
logger.info("Signed new network map: $newNetworkMap")
|
||||
|
@ -1,15 +1,18 @@
|
||||
package com.r3.corda.networkmanage.doorman
|
||||
|
||||
import com.google.common.primitives.Booleans
|
||||
import com.r3.corda.networkmanage.common.utils.ShowHelpException
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import com.typesafe.config.ConfigParseOptions
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import joptsimple.OptionParser
|
||||
import joptsimple.util.EnumConverter
|
||||
import joptsimple.util.PathConverter
|
||||
import joptsimple.util.PathProperties
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.NotaryInfo
|
||||
import net.corda.nodeapi.internal.config.parseAs
|
||||
import java.nio.file.Path
|
||||
import java.time.Instant
|
||||
|
||||
class DoormanArgsParser {
|
||||
private val optionParser = OptionParser()
|
||||
@ -24,12 +27,14 @@ class DoormanArgsParser {
|
||||
.withRequiredArg()
|
||||
.withValuesConvertedBy(object : EnumConverter<Mode>(Mode::class.java) {})
|
||||
.defaultsTo(Mode.DOORMAN)
|
||||
private val updateNetworkParametersArg = optionParser
|
||||
.accepts("update-network-parameters", "Update network parameters file. Currently only network parameters initialisation is supported.")
|
||||
private val setNetworkParametersArg = optionParser
|
||||
.accepts("set-network-parameters", "Set the network parameters using the given file. This can be for either the initial set or to schedule an update.")
|
||||
.withRequiredArg()
|
||||
.withValuesConvertedBy(PathConverter(PathProperties.FILE_EXISTING))
|
||||
private val flagDayArg = optionParser.accepts("flag-day", "Roll over the scheduled network parameters to be the current.")
|
||||
private val cancelUpdateArg = optionParser.accepts("cancel-network-parameters-update", "Cancel the scheduled update of the network parameters.")
|
||||
private val trustStorePasswordArg = optionParser
|
||||
.accepts("trust-store-password", "Password for the generated network root trust store. Only required when operating in ${Mode.ROOT_KEYGEN} mode.")
|
||||
.accepts("trust-store-password", "Password for the generated network root trust store. Only applicable when operating in ${Mode.ROOT_KEYGEN} mode.")
|
||||
.withRequiredArg()
|
||||
|
||||
fun parse(vararg args: String): DoormanCmdLineOptions {
|
||||
@ -39,13 +44,27 @@ class DoormanArgsParser {
|
||||
}
|
||||
val configFile = optionSet.valueOf(configFileArg)
|
||||
val mode = optionSet.valueOf(modeArg)
|
||||
val networkParametersFile = optionSet.valueOf(updateNetworkParametersArg)
|
||||
val setNetworkParametersFile = optionSet.valueOf(setNetworkParametersArg)
|
||||
val flagDay = optionSet.has(flagDayArg)
|
||||
val cancelUpdate = optionSet.has(cancelUpdateArg)
|
||||
require(Booleans.countTrue(setNetworkParametersFile != null, flagDay, cancelUpdate) <= 1) {
|
||||
"Only one of $setNetworkParametersArg, $flagDay and $cancelUpdate can be specified"
|
||||
}
|
||||
val networkParametersOption = when {
|
||||
setNetworkParametersFile != null -> NetworkParametersCmd.Set.fromFile(setNetworkParametersFile)
|
||||
flagDay -> NetworkParametersCmd.FlagDay
|
||||
cancelUpdate -> NetworkParametersCmd.CancelUpdate
|
||||
else -> null
|
||||
}
|
||||
val trustStorePassword = optionSet.valueOf(trustStorePasswordArg)
|
||||
return DoormanCmdLineOptions(configFile, mode, networkParametersFile, trustStorePassword)
|
||||
return DoormanCmdLineOptions(configFile, mode, networkParametersOption, trustStorePassword)
|
||||
}
|
||||
}
|
||||
|
||||
data class DoormanCmdLineOptions(val configFile: Path, val mode: Mode, val networkParametersFile: Path?, val trustStorePassword: String?) {
|
||||
data class DoormanCmdLineOptions(val configFile: Path,
|
||||
val mode: Mode,
|
||||
val networkParametersCmd: NetworkParametersCmd?,
|
||||
val trustStorePassword: String?) {
|
||||
init {
|
||||
// Make sure trust store password is only specified in root keygen mode.
|
||||
if (mode != Mode.ROOT_KEYGEN) {
|
||||
@ -53,3 +72,58 @@ data class DoormanCmdLineOptions(val configFile: Path, val mode: Mode, val netwo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class NetworkParametersCmd {
|
||||
/**
|
||||
* This is the same as [NetworkParametersConfig] but instead of using [NotaryConfig] it uses the simpler to test with
|
||||
* [NotaryInfo].
|
||||
*/
|
||||
data class Set(val minimumPlatformVersion: Int,
|
||||
val notaries: List<NotaryInfo>,
|
||||
val maxMessageSize: Int,
|
||||
val maxTransactionSize: Int,
|
||||
val parametersUpdate: ParametersUpdateConfig?) : NetworkParametersCmd() {
|
||||
companion object {
|
||||
fun fromFile(file: Path): Set {
|
||||
return ConfigFactory.parseFile(file.toFile(), ConfigParseOptions.defaults())
|
||||
.parseAs<NetworkParametersConfig>()
|
||||
.let {
|
||||
Set(
|
||||
it.minimumPlatformVersion,
|
||||
it.notaries.map { it.toNotaryInfo() },
|
||||
it.maxMessageSize,
|
||||
it.maxTransactionSize,
|
||||
it.parametersUpdate
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun checkCompatibility(currentNetParams: NetworkParameters) {
|
||||
// TODO Comment it out when maxMessageSize is properly wired
|
||||
// require(previousParameters.maxMessageSize <= newParameters.maxMessageSize) { "maxMessageSize can only increase" }
|
||||
require(maxTransactionSize >= currentNetParams.maxTransactionSize) { "maxTransactionSize can only increase" }
|
||||
val removedNames = currentNetParams.notaries.map { it.identity.name } - notaries.map { it.identity.name }
|
||||
require(removedNames.isEmpty()) { "notaries cannot be removed: $removedNames" }
|
||||
val removedKeys = currentNetParams.notaries.map { it.identity.owningKey } - notaries.map { it.identity.owningKey }
|
||||
require(removedKeys.isEmpty()) { "notaries cannot be removed: $removedKeys" }
|
||||
}
|
||||
|
||||
fun toNetworkParameters(modifiedTime: Instant, epoch: Int): NetworkParameters {
|
||||
return NetworkParameters(
|
||||
minimumPlatformVersion,
|
||||
notaries,
|
||||
maxMessageSize,
|
||||
maxTransactionSize,
|
||||
modifiedTime,
|
||||
epoch,
|
||||
// TODO: Tudor, Michal - pass the actual network parameters where we figure out how
|
||||
emptyMap()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object FlagDay : NetworkParametersCmd()
|
||||
|
||||
object CancelUpdate : NetworkParametersCmd()
|
||||
}
|
||||
|
@ -11,15 +11,12 @@
|
||||
package com.r3.corda.networkmanage.doorman
|
||||
|
||||
import com.jcabi.manifests.Manifests
|
||||
import com.r3.corda.networkmanage.common.persistence.configureDatabase
|
||||
import com.r3.corda.networkmanage.common.utils.*
|
||||
import com.r3.corda.networkmanage.doorman.signer.LocalSigner
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.time.Instant
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
private val logger = LoggerFactory.getLogger("com.r3.corda.networkmanage.doorman")
|
||||
@ -47,7 +44,7 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
}
|
||||
|
||||
data class NetworkMapStartParams(val signer: LocalSigner?, val updateNetworkParameters: NetworkParameters?, val config: NetworkMapConfig)
|
||||
data class NetworkMapStartParams(val signer: LocalSigner?, val config: NetworkMapConfig)
|
||||
|
||||
data class NetworkManagementServerStatus(var serverStartTime: Instant = Instant.now(), var lastRequestCheckTime: Instant? = null)
|
||||
|
||||
@ -85,28 +82,34 @@ private fun caKeyGenMode(config: NetworkManagementServerConfig) {
|
||||
|
||||
private fun doormanMode(cmdLineOptions: DoormanCmdLineOptions, config: NetworkManagementServerConfig) {
|
||||
initialiseSerialization()
|
||||
val persistence = configureDatabase(config.dataSourceProperties, config.database)
|
||||
// TODO: move signing to signing server.
|
||||
val csrAndNetworkMap = processKeyStore(config)
|
||||
|
||||
if (csrAndNetworkMap != null) {
|
||||
logger.info("Starting network management services with local signing")
|
||||
val networkManagementServer = NetworkManagementServer(config.dataSourceProperties, config.database)
|
||||
|
||||
if (cmdLineOptions.networkParametersCmd == null) {
|
||||
// TODO: move signing to signing server.
|
||||
val csrAndNetworkMap = processKeyStore(config)
|
||||
if (csrAndNetworkMap != null) {
|
||||
logger.info("Starting network management services with local signing")
|
||||
}
|
||||
|
||||
val networkMapStartParams = config.networkMap?.let {
|
||||
NetworkMapStartParams(csrAndNetworkMap?.second, it)
|
||||
}
|
||||
|
||||
networkManagementServer.start(
|
||||
config.address,
|
||||
csrAndNetworkMap?.first,
|
||||
config.doorman,
|
||||
networkMapStartParams)
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(object : Thread("ShutdownHook") {
|
||||
override fun run() {
|
||||
networkManagementServer.close()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
networkManagementServer.use {
|
||||
it.processNetworkParameters(cmdLineOptions.networkParametersCmd)
|
||||
}
|
||||
}
|
||||
|
||||
val networkManagementServer = NetworkManagementServer()
|
||||
val networkParameters = cmdLineOptions.networkParametersFile?.let {
|
||||
// TODO This check shouldn't be needed. Fix up the config design.
|
||||
requireNotNull(config.networkMap) { "'networkMap' config is required for applying network parameters" }
|
||||
logger.info("Parsing network parameters from '${it.toAbsolutePath()}'...")
|
||||
parseNetworkParametersConfig(it).toNetworkParameters(modifiedTime = Instant.now(), epoch = 1)
|
||||
}
|
||||
val networkMapStartParams = config.networkMap?.let {
|
||||
NetworkMapStartParams(csrAndNetworkMap?.second, networkParameters, it)
|
||||
}
|
||||
|
||||
networkManagementServer.start(config.address, persistence, csrAndNetworkMap?.first, config.doorman, networkMapStartParams)
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(thread(start = false) {
|
||||
networkManagementServer.close()
|
||||
})
|
||||
}
|
||||
|
@ -11,10 +11,7 @@
|
||||
package com.r3.corda.networkmanage.doorman
|
||||
|
||||
import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory
|
||||
import com.r3.corda.networkmanage.common.persistence.ApproveAllCertificateSigningRequestStorage
|
||||
import com.r3.corda.networkmanage.common.persistence.PersistentCertificateSigningRequestStorage
|
||||
import com.r3.corda.networkmanage.common.persistence.PersistentNetworkMapStorage
|
||||
import com.r3.corda.networkmanage.common.persistence.PersistentNodeInfoStorage
|
||||
import com.r3.corda.networkmanage.common.persistence.*
|
||||
import com.r3.corda.networkmanage.common.signer.NetworkMapSigner
|
||||
import com.r3.corda.networkmanage.common.utils.CertPathAndKey
|
||||
import com.r3.corda.networkmanage.doorman.signer.DefaultCsrHandler
|
||||
@ -23,23 +20,31 @@ import com.r3.corda.networkmanage.doorman.signer.LocalSigner
|
||||
import com.r3.corda.networkmanage.doorman.webservice.MonitoringWebService
|
||||
import com.r3.corda.networkmanage.doorman.webservice.NetworkMapWebService
|
||||
import com.r3.corda.networkmanage.doorman.webservice.RegistrationWebService
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import java.io.Closeable
|
||||
import java.net.URI
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class NetworkManagementServer : Closeable {
|
||||
class NetworkManagementServer(dataSourceProperties: Properties, databaseConfig: DatabaseConfig) : Closeable {
|
||||
companion object {
|
||||
private val logger = contextLogger()
|
||||
}
|
||||
|
||||
private val closeActions = mutableListOf<() -> Unit>()
|
||||
private val database = configureDatabase(dataSourceProperties, databaseConfig).also { closeActions += it::close }
|
||||
private val networkMapStorage = PersistentNetworkMapStorage(database)
|
||||
private val nodeInfoStorage = PersistentNodeInfoStorage(database)
|
||||
|
||||
lateinit var hostAndPort: NetworkHostAndPort
|
||||
|
||||
override fun close() {
|
||||
@ -53,23 +58,12 @@ class NetworkManagementServer : Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNetworkMapService(config: NetworkMapConfig, database: CordaPersistence, signer: LocalSigner?, newNetworkParameters: NetworkParameters?): NetworkMapWebService {
|
||||
val networkMapStorage = PersistentNetworkMapStorage(database)
|
||||
val nodeInfoStorage = PersistentNodeInfoStorage(database)
|
||||
private fun getNetworkMapService(config: NetworkMapConfig, signer: LocalSigner?): NetworkMapWebService {
|
||||
val localNetworkMapSigner = signer?.let { NetworkMapSigner(networkMapStorage, it) }
|
||||
|
||||
newNetworkParameters?.let {
|
||||
val netParamsOfNetworkMap = networkMapStorage.getActiveNetworkMap()?.networkParameters
|
||||
if (netParamsOfNetworkMap == null) {
|
||||
localNetworkMapSigner?.signAndPersistNetworkParameters(it) ?: networkMapStorage.saveNetworkParameters(it, null)
|
||||
} else {
|
||||
throw UnsupportedOperationException("Network parameters already exist. Updating them is not supported yet.")
|
||||
}
|
||||
}
|
||||
|
||||
val latestParameters = networkMapStorage.getLatestNetworkParameters() ?:
|
||||
val latestParameters = networkMapStorage.getLatestNetworkParameters()?.toNetworkParameters() ?:
|
||||
throw IllegalStateException("No network parameters were found. Please upload new network parameters before starting network map service")
|
||||
logger.info("Starting network map service with network parameters: $latestParameters")
|
||||
localNetworkMapSigner?.signAndPersistNetworkParameters(latestParameters)
|
||||
|
||||
if (localNetworkMapSigner != null) {
|
||||
logger.info("Starting background worker for signing the network map using the local key store")
|
||||
@ -88,7 +82,6 @@ class NetworkManagementServer : Closeable {
|
||||
return NetworkMapWebService(nodeInfoStorage, networkMapStorage, config)
|
||||
}
|
||||
|
||||
|
||||
private fun getDoormanService(config: DoormanConfig,
|
||||
database: CordaPersistence,
|
||||
csrCertPathAndKey: CertPathAndKey?,
|
||||
@ -128,16 +121,15 @@ class NetworkManagementServer : Closeable {
|
||||
}
|
||||
|
||||
fun start(hostAndPort: NetworkHostAndPort,
|
||||
database: CordaPersistence,
|
||||
csrCertPathAndKey: CertPathAndKey?,
|
||||
doormanServiceParameter: DoormanConfig?, // TODO Doorman config shouldn't be optional as the doorman is always required to run
|
||||
doormanConfig: DoormanConfig?, // TODO Doorman config shouldn't be optional as the doorman is always required to run
|
||||
startNetworkMap: NetworkMapStartParams?
|
||||
) {
|
||||
val services = mutableListOf<Any>()
|
||||
val serverStatus = NetworkManagementServerStatus()
|
||||
|
||||
startNetworkMap?.let { services += getNetworkMapService(it.config, database, it.signer, it.updateNetworkParameters) }
|
||||
doormanServiceParameter?.let { services += getDoormanService(it, database, csrCertPathAndKey, serverStatus) }
|
||||
startNetworkMap?.let { services += getNetworkMapService(it.config, it.signer) }
|
||||
doormanConfig?.let { services += getDoormanService(it, database, csrCertPathAndKey, serverStatus) }
|
||||
|
||||
require(services.isNotEmpty()) { "No service created, please provide at least one service config." }
|
||||
|
||||
@ -150,4 +142,85 @@ class NetworkManagementServer : Closeable {
|
||||
closeActions += webServer::close
|
||||
this.hostAndPort = webServer.hostAndPort
|
||||
}
|
||||
}
|
||||
|
||||
fun processNetworkParameters(networkParametersCmd: NetworkParametersCmd) {
|
||||
when (networkParametersCmd) {
|
||||
is NetworkParametersCmd.Set -> handleSetNetworkParameters(networkParametersCmd)
|
||||
NetworkParametersCmd.FlagDay -> handleFlagDay()
|
||||
NetworkParametersCmd.CancelUpdate -> handleCancelUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSetNetworkParameters(setNetParams: NetworkParametersCmd.Set) {
|
||||
logger.info("maxMessageSize is not currently wired in the nodes")
|
||||
val activeNetParams = networkMapStorage.getActiveNetworkMap()?.networkParameters?.toNetworkParameters()
|
||||
if (activeNetParams == null) {
|
||||
require(setNetParams.parametersUpdate == null) {
|
||||
"'parametersUpdate' specified in network parameters file but there are no network parameters to update"
|
||||
}
|
||||
val initialNetParams = setNetParams.toNetworkParameters(modifiedTime = Instant.now(), epoch = 1)
|
||||
logger.info("Saving initial network parameters to be signed:\n$initialNetParams")
|
||||
networkMapStorage.saveNetworkParameters(initialNetParams, null)
|
||||
} else {
|
||||
val parametersUpdate = requireNotNull(setNetParams.parametersUpdate) {
|
||||
"'parametersUpdate' not specified in network parameters file but there is already an active set of network parameters."
|
||||
}
|
||||
|
||||
setNetParams.checkCompatibility(activeNetParams)
|
||||
|
||||
val latestNetParams = checkNotNull(networkMapStorage.getLatestNetworkParameters()?.toNetworkParameters()) {
|
||||
"Something has gone wrong! We have an active set of network parameters ($activeNetParams) but apparently no latest network parameters!"
|
||||
}
|
||||
|
||||
// It's not necessary that latestNetParams is the current active network parameters. It can be the network
|
||||
// parameters from a previous update attempt which has't activated yet. We still take the epoch value for this
|
||||
// new set from latestNetParams to make sure the advertised update attempts have incrementing epochs.
|
||||
// This has the implication that active network parameters may have gaps in their epochs.
|
||||
val newNetParams = setNetParams.toNetworkParameters(modifiedTime = Instant.now(), epoch = latestNetParams.epoch + 1)
|
||||
|
||||
logger.info("Enabling update to network parameters:\n$newNetParams\n$parametersUpdate")
|
||||
|
||||
require(!sameNetworkParameters(latestNetParams, newNetParams)) { "New network parameters are the same as the latest ones" }
|
||||
|
||||
networkMapStorage.saveNewParametersUpdate(newNetParams, parametersUpdate.description, parametersUpdate.updateDeadline)
|
||||
|
||||
logger.info("Update enabled")
|
||||
}
|
||||
}
|
||||
|
||||
private fun sameNetworkParameters(params1: NetworkParameters, params2: NetworkParameters): Boolean {
|
||||
return params1.copy(epoch = 1, modifiedTime = Instant.MAX) == params2.copy(epoch = 1, modifiedTime = Instant.MAX)
|
||||
}
|
||||
|
||||
private fun handleFlagDay() {
|
||||
val parametersUpdate = checkNotNull(networkMapStorage.getParametersUpdate()) {
|
||||
"No network parameters updates are scheduled"
|
||||
}
|
||||
check(Instant.now() >= parametersUpdate.updateDeadline) {
|
||||
"Update deadline of ${parametersUpdate.updateDeadline} hasn't passed yet"
|
||||
}
|
||||
val activeNetParams = networkMapStorage.getActiveNetworkMap()?.networkParameters
|
||||
val latestNetParamsEntity = networkMapStorage.getLatestNetworkParameters()!!
|
||||
check(latestNetParamsEntity.isSigned) {
|
||||
"Parameters we are trying to switch to haven't been signed yet"
|
||||
}
|
||||
// TODO This check is stil not good enough as when it comes to signing, the NetworkMapSigner will just accept
|
||||
check(latestNetParamsEntity.parametersHash == parametersUpdate.networkParameters.parametersHash) {
|
||||
"The latest network parameters is not the scheduled one:\n${latestNetParamsEntity.toNetworkParameters()}\n${parametersUpdate.toParametersUpdate()}"
|
||||
}
|
||||
logger.info("Flag day has occurred, however the new network parameters won't be active until the new network map is signed.\n" +
|
||||
"Switching from: $activeNetParams\nTo: ${latestNetParamsEntity.toNetworkParameters()}")
|
||||
networkMapStorage.setFlagDay(SecureHash.parse(parametersUpdate.networkParameters.parametersHash))
|
||||
}
|
||||
|
||||
private fun handleCancelUpdate() {
|
||||
val parametersUpdate = networkMapStorage.getParametersUpdate()
|
||||
if (parametersUpdate == null) {
|
||||
logger.info("Trying to cancel parameters update but no update is scheduled")
|
||||
} else {
|
||||
logger.info("Cancelling parameters update: ${parametersUpdate.toParametersUpdate()}")
|
||||
// We leave parameters from that update in the database, for auditing reasons
|
||||
networkMapStorage.clearParametersUpdates()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,13 +11,10 @@
|
||||
package com.r3.corda.networkmanage.doorman
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import com.typesafe.config.ConfigParseOptions
|
||||
import net.corda.core.internal.exists
|
||||
import net.corda.core.internal.readObject
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.NotaryInfo
|
||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||
import net.corda.nodeapi.internal.config.parseAs
|
||||
import java.nio.file.Path
|
||||
import java.time.Instant
|
||||
|
||||
@ -28,6 +25,7 @@ import java.time.Instant
|
||||
*/
|
||||
data class NotaryConfig(private val notaryNodeInfoFile: Path,
|
||||
private val validating: Boolean) {
|
||||
// TODO ENT-1608 - Check that the identity belongs to us
|
||||
fun toNotaryInfo(): NotaryInfo {
|
||||
val nodeInfo = notaryNodeInfoFile.readObject<SignedNodeInfo>().verified()
|
||||
// It is always the last identity (in the list of identities) that corresponds to the notary identity.
|
||||
@ -37,32 +35,15 @@ data class NotaryConfig(private val notaryNodeInfoFile: Path,
|
||||
}
|
||||
}
|
||||
|
||||
data class ParametersUpdateConfig(val description: String, val updateDeadline: Instant)
|
||||
|
||||
/**
|
||||
* Data class containing the fields from [NetworkParameters] which can be read at start-up time from doorman.
|
||||
* It is a proper subset of [NetworkParameters] except for the [notaries] field which is replaced by a list of
|
||||
* [NotaryConfig] which is parsable.
|
||||
*
|
||||
* This is public only because [parseAs] needs to be able to call its constructor.
|
||||
*/
|
||||
data class NetworkParametersConfig(val minimumPlatformVersion: Int,
|
||||
val notaries: List<NotaryConfig>,
|
||||
val maxMessageSize: Int,
|
||||
val maxTransactionSize: Int) {
|
||||
fun toNetworkParameters(modifiedTime: Instant, epoch: Int): NetworkParameters {
|
||||
return NetworkParameters(
|
||||
minimumPlatformVersion,
|
||||
notaries.map { it.toNotaryInfo() },
|
||||
maxMessageSize,
|
||||
maxTransactionSize,
|
||||
modifiedTime,
|
||||
epoch,
|
||||
// TODO: Tudor, Michal - pass the actual network parameters where we figure out how
|
||||
emptyMap()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun parseNetworkParametersConfig(configFile: Path): NetworkParametersConfig {
|
||||
check(configFile.exists()) { "File $configFile does not exist" }
|
||||
return ConfigFactory.parseFile(configFile.toFile(), ConfigParseOptions.defaults()).parseAs()
|
||||
}
|
||||
val maxTransactionSize: Int,
|
||||
val parametersUpdate: ParametersUpdateConfig?)
|
||||
|
@ -17,6 +17,8 @@ import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage
|
||||
import com.r3.corda.networkmanage.doorman.NetworkMapConfig
|
||||
import com.r3.corda.networkmanage.doorman.webservice.NetworkMapWebService.Companion.NETWORK_MAP_PATH
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.deserialize
|
||||
@ -96,6 +98,25 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage,
|
||||
}.build()
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("ack-parameters")
|
||||
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
|
||||
fun ackNetworkParameters(input: InputStream): Response {
|
||||
return try {
|
||||
val signedParametersHash = input.readBytes().deserialize<SignedData<SecureHash>>()
|
||||
val hash = signedParametersHash.verified()
|
||||
networkMapStorage.getSignedNetworkParameters(hash) ?: throw IllegalArgumentException("No network parameters with hash $hash")
|
||||
logger.debug { "Received ack-parameters with $hash from ${signedParametersHash.sig.by}" }
|
||||
nodeInfoStorage.ackNodeInfoParametersUpdate(signedParametersHash.sig.by.encoded.sha256(), hash)
|
||||
ok()
|
||||
} catch (e: Exception) {
|
||||
when (e) {
|
||||
is SignatureException -> status(Response.Status.FORBIDDEN).entity(e.message)
|
||||
else -> status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
@GET
|
||||
fun getNetworkMap(): Response = createResponse(currentSignedNetworkMap, addCacheTimeout = true)
|
||||
|
||||
|
@ -97,8 +97,8 @@
|
||||
<column name="hash" type="NVARCHAR(64)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="certificate" type="BLOB"/>
|
||||
<column name="created" type="TIMESTAMP"/>
|
||||
<column name="certificate" type="BLOB"/>
|
||||
<column name="parameters_bytes" type="BLOB">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
@ -110,6 +110,7 @@
|
||||
<column name="node_info_hash" type="NVARCHAR(64)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="public_key_hash" type="NVARCHAR(64)"/>
|
||||
<column name="signed_node_info_bytes" type="BLOB">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
@ -122,6 +123,7 @@
|
||||
<column name="published_at" type="TIMESTAMP">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="accepted_parameters_hash" type="NVARCHAR(64)"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet author="R3.Corda" id="1520338500424-8">
|
||||
@ -313,4 +315,21 @@
|
||||
constraintName="FK_NM_NP"
|
||||
referencedColumnNames="hash" referencedTableName="network_parameters"/>
|
||||
</changeSet>
|
||||
<changeSet author="R3.Corda" id="update-net-params">
|
||||
<createTable tableName="parameters_update">
|
||||
<column name="description" type="VARCHAR(255)"/>
|
||||
<column name="update_deadline" type="TIMESTAMP"/>
|
||||
<column name="id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="network_parameters" type="NVARCHAR(64)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="flag_day" type="BOOLEAN"/>
|
||||
</createTable>
|
||||
<addPrimaryKey columnNames="id" constraintName="CONSTRAINT_PARAMUPKEY" tableName="parameters_update"/>
|
||||
<addForeignKeyConstraint baseTableName="parameters_update" baseColumnNames="network_parameters"
|
||||
constraintName="FK_PU_NP"
|
||||
referencedTableName="network_parameters" referencedColumnNames="hash"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
|
@ -1,76 +0,0 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package com.r3.corda.networkmanage
|
||||
|
||||
import com.google.common.jimfs.Jimfs
|
||||
import com.r3.corda.networkmanage.doorman.NetworkParametersConfig
|
||||
import com.r3.corda.networkmanage.doorman.NotaryConfig
|
||||
import net.corda.core.internal.copyTo
|
||||
import net.corda.core.node.NotaryInfo
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.internal.createNodeInfoAndSigned
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.nio.file.Path
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
class NetworkParametersConfigTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
private val fs = Jimfs.newFileSystem()
|
||||
|
||||
@After
|
||||
fun cleanUp() {
|
||||
fs.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun toNetworkParameters() {
|
||||
val (aliceNodeInfo, aliceSignedNodeInfo) = createNodeInfoAndSigned(ALICE_NAME)
|
||||
val (bobNodeInfo, bobSignedNodeInfo) = createNodeInfoAndSigned(BOB_NAME)
|
||||
val networkParametersConfig = NetworkParametersConfig(
|
||||
notaries = listOf(
|
||||
NotaryConfig(aliceSignedNodeInfo.writeToFile(), true),
|
||||
NotaryConfig(bobSignedNodeInfo.writeToFile(), false)
|
||||
),
|
||||
maxMessageSize = 100,
|
||||
maxTransactionSize = 100,
|
||||
minimumPlatformVersion = 3
|
||||
)
|
||||
|
||||
val modifiedTime = Instant.now()
|
||||
val networkParameters = networkParametersConfig.toNetworkParameters(modifiedTime = modifiedTime, epoch = 2)
|
||||
assertThat(networkParameters.modifiedTime).isEqualTo(modifiedTime)
|
||||
assertThat(networkParameters.epoch).isEqualTo(2)
|
||||
assertThat(networkParameters.notaries).containsExactly(
|
||||
NotaryInfo(aliceNodeInfo.legalIdentities[0], true),
|
||||
NotaryInfo(bobNodeInfo.legalIdentities[0], false)
|
||||
)
|
||||
assertThat(networkParameters.maxMessageSize).isEqualTo(100)
|
||||
assertThat(networkParameters.maxTransactionSize).isEqualTo(100)
|
||||
assertThat(networkParameters.minimumPlatformVersion).isEqualTo(3)
|
||||
}
|
||||
|
||||
private fun SignedNodeInfo.writeToFile(): Path {
|
||||
val path = fs.getPath(UUID.randomUUID().toString())
|
||||
serialize().open().copyTo(path)
|
||||
return path
|
||||
}
|
||||
}
|
@ -4,9 +4,11 @@ import com.r3.corda.networkmanage.common.persistence.entity.NetworkMapEntity
|
||||
import com.r3.corda.networkmanage.common.persistence.entity.NetworkParametersEntity
|
||||
import net.corda.core.crypto.SecureHash
|
||||
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
|
||||
import net.corda.nodeapi.internal.network.NetworkMap
|
||||
import net.corda.nodeapi.internal.network.ParametersUpdate
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
|
||||
fun createNetworkParametersEntity(signingCertAndKeyPair: CertificateAndKeyPair = createDevNetworkMapCa(),
|
||||
@ -20,10 +22,21 @@ fun createNetworkParametersEntity(signingCertAndKeyPair: CertificateAndKeyPair =
|
||||
)
|
||||
}
|
||||
|
||||
fun createNetworkParametersEntityUnsigned(networkParameters: NetworkParameters = testNetworkParameters()): NetworkParametersEntity {
|
||||
val serialised = networkParameters.serialize()
|
||||
return NetworkParametersEntity(
|
||||
parametersHash = serialised.hash.toString(),
|
||||
parametersBytes = serialised.bytes,
|
||||
signature = null,
|
||||
certificate = null
|
||||
)
|
||||
}
|
||||
|
||||
fun createNetworkMapEntity(signingCertAndKeyPair: CertificateAndKeyPair = createDevNetworkMapCa(),
|
||||
netParamsEntity: NetworkParametersEntity,
|
||||
nodeInfoHashes: List<SecureHash> = emptyList()): NetworkMapEntity {
|
||||
val signedNetMap = signingCertAndKeyPair.sign(NetworkMap(nodeInfoHashes, SecureHash.parse(netParamsEntity.parametersHash), null))
|
||||
nodeInfoHashes: List<SecureHash> = emptyList(),
|
||||
parametersUpdate: ParametersUpdate? = null): NetworkMapEntity {
|
||||
val signedNetMap = signingCertAndKeyPair.sign(NetworkMap(nodeInfoHashes, SecureHash.parse(netParamsEntity.parametersHash), parametersUpdate))
|
||||
return NetworkMapEntity(
|
||||
networkMapBytes = signedNetMap.raw.bytes,
|
||||
signature = signedNetMap.sig.bytes,
|
||||
|
@ -13,10 +13,13 @@ package com.r3.corda.networkmanage.common.persistence
|
||||
import com.r3.corda.networkmanage.TestBase
|
||||
import com.r3.corda.networkmanage.common.persistence.entity.NodeInfoEntity
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.nodeapi.internal.createDevNetworkMapCa
|
||||
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||
import net.corda.nodeapi.internal.network.NetworkMap
|
||||
import net.corda.nodeapi.internal.network.NetworkMapAndSigned
|
||||
import net.corda.nodeapi.internal.network.ParametersUpdate
|
||||
import net.corda.nodeapi.internal.network.verifiedNetworkMapCert
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
@ -28,6 +31,7 @@ import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Instant
|
||||
|
||||
class PersistentNetworkMapStorageTest : TestBase() {
|
||||
private lateinit var persistence: CordaPersistence
|
||||
@ -121,4 +125,29 @@ class PersistentNetworkMapStorageTest : TestBase() {
|
||||
// then
|
||||
assertThat(validNodeInfoHashes).containsOnly(nodeInfoHashB)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `saveNewParametersUpdate clears the previous updates from database`() {
|
||||
val testParameters1 = testNetworkParameters(epoch = 1)
|
||||
val testParameters2 = testNetworkParameters(epoch = 2)
|
||||
val hash1 = testParameters1.serialize().hash
|
||||
val hash2 = testParameters2.serialize().hash
|
||||
val updateDeadline1 = Instant.ofEpochMilli(random63BitValue())
|
||||
val updateDeadline2 = Instant.ofEpochMilli(random63BitValue())
|
||||
networkMapStorage.saveNewParametersUpdate(testParameters1, "Update 1", updateDeadline1)
|
||||
networkMapStorage.saveNewParametersUpdate(testParameters1, "Update of update", updateDeadline1)
|
||||
assertThat(networkMapStorage.getParametersUpdate()?.toParametersUpdate()).isEqualTo(ParametersUpdate(hash1, "Update of update", updateDeadline1))
|
||||
networkMapStorage.saveNewParametersUpdate(testParameters2, "Update 3", updateDeadline2)
|
||||
assertThat(networkMapStorage.getParametersUpdate()?.toParametersUpdate()).isEqualTo(ParametersUpdate(hash2, "Update 3", updateDeadline2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clear parameters update removes all parameters updates`() {
|
||||
val params1 = testNetworkParameters(minimumPlatformVersion = 1)
|
||||
val params2 = testNetworkParameters(minimumPlatformVersion = 2)
|
||||
networkMapStorage.saveNewParametersUpdate(params1, "Update 1", Instant.ofEpochMilli(random63BitValue()))
|
||||
networkMapStorage.saveNewParametersUpdate(params2, "Update 2", Instant.ofEpochMilli(random63BitValue()))
|
||||
networkMapStorage.clearParametersUpdates()
|
||||
assertThat(networkMapStorage.getParametersUpdate()).isNull()
|
||||
}
|
||||
}
|
||||
|
@ -146,6 +146,21 @@ class PersistentNodeInfoStorageTest : TestBase() {
|
||||
val persistedSignedNodeInfo = nodeInfoStorage.getNodeInfo(nodeInfoHash)
|
||||
assertThat(persistedSignedNodeInfo?.signatures).isEqualTo(nodeInfoWithSigned.signed.signatures)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `accept parameters updates node info correctly`() {
|
||||
// given
|
||||
val (nodeInfoWithSigned) = createValidSignedNodeInfo("Test", requestStorage)
|
||||
|
||||
// when
|
||||
val paramsHash = SecureHash.randomSHA256()
|
||||
val nodeInfoHash = nodeInfoStorage.putNodeInfo(nodeInfoWithSigned)
|
||||
nodeInfoStorage.ackNodeInfoParametersUpdate(SecureHash.parse(nodeInfoWithSigned.nodeInfo.legalIdentities.first().owningKey.hashString()), paramsHash)
|
||||
|
||||
// then
|
||||
val persistedParametersHash = nodeInfoStorage.getAcceptedParametersUpdateHash(nodeInfoHash)
|
||||
assertThat(persistedParametersHash).isEqualTo(paramsHash)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun createValidSignedNodeInfo(organisation: String,
|
||||
|
@ -13,11 +13,17 @@ package com.r3.corda.networkmanage.common.signer
|
||||
import com.nhaarman.mockito_kotlin.*
|
||||
import com.r3.corda.networkmanage.TestBase
|
||||
import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage
|
||||
import com.r3.corda.networkmanage.common.persistence.entity.ParametersUpdateEntity
|
||||
import com.r3.corda.networkmanage.createNetworkMapEntity
|
||||
import com.r3.corda.networkmanage.createNetworkParametersEntity
|
||||
import com.r3.corda.networkmanage.createNetworkParametersEntityUnsigned
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.internal.DigitalSignatureWithCert
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.nodeapi.internal.createDevNetworkMapCa
|
||||
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||
@ -29,6 +35,7 @@ import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Instant
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class NetworkMapSignerTest : TestBase() {
|
||||
@ -47,27 +54,25 @@ class NetworkMapSignerTest : TestBase() {
|
||||
signer = mock()
|
||||
networkMapStorage = mock()
|
||||
networkMapSigner = NetworkMapSigner(networkMapStorage, signer)
|
||||
whenever(signer.signBytes(any())).then {
|
||||
DigitalSignatureWithCert(signingCertAndKeyPair.certificate, Crypto.doSign(signingCertAndKeyPair.keyPair.private, it.arguments[0] as ByteArray))
|
||||
}
|
||||
whenever(signer.signObject(any<NetworkParameters>())).then {
|
||||
val serialised: SerializedBytes<NetworkParameters> = uncheckedCast(it.arguments[0].serialize())
|
||||
SignedNetworkParameters(serialised, signer.signBytes(serialised.bytes))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `signNetworkMap builds and signs network map and network parameters`() {
|
||||
// given
|
||||
val nodeInfoHashes = listOf(SecureHash.randomSHA256(), SecureHash.randomSHA256())
|
||||
val activeNetParams = testNetworkParameters(minimumPlatformVersion = 1)
|
||||
val latestNetParams = testNetworkParameters(minimumPlatformVersion = 2)
|
||||
val activeNetParamsEntity = createNetworkParametersEntity(signingCertAndKeyPair, activeNetParams)
|
||||
val latestNetParamsEntity = createNetworkParametersEntity(signingCertAndKeyPair, latestNetParams)
|
||||
val netMapEntity = createNetworkMapEntity(signingCertAndKeyPair, activeNetParamsEntity, nodeInfoHashes)
|
||||
val latestNetParams = testNetworkParameters(epoch = 3)
|
||||
val latestNetParamsEntity = createNetworkParametersEntityUnsigned(latestNetParams)
|
||||
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(latestNetParamsEntity)
|
||||
whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(nodeInfoHashes)
|
||||
whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(netMapEntity)
|
||||
whenever(signer.signBytes(any())).then {
|
||||
DigitalSignatureWithCert(signingCertAndKeyPair.certificate, Crypto.doSign(signingCertAndKeyPair.keyPair.private, it.arguments[0] as ByteArray))
|
||||
}
|
||||
whenever(signer.signObject(latestNetParams)).then {
|
||||
val serialised = latestNetParams.serialize()
|
||||
SignedNetworkParameters(serialised, signer.signBytes(serialised.bytes))
|
||||
}
|
||||
whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(null)
|
||||
whenever(networkMapStorage.getParametersUpdate()).thenReturn(null)
|
||||
|
||||
// when
|
||||
networkMapSigner.signNetworkMap()
|
||||
@ -75,13 +80,21 @@ class NetworkMapSignerTest : TestBase() {
|
||||
// then
|
||||
// Verify networkMapStorage calls
|
||||
verify(networkMapStorage).getActiveNodeInfoHashes()
|
||||
verify(networkMapStorage).getActiveNetworkMap()
|
||||
verify(networkMapStorage).getParametersUpdate()
|
||||
verify(networkMapStorage).getLatestNetworkParameters()
|
||||
argumentCaptor<NetworkMapAndSigned>().apply {
|
||||
verify(networkMapStorage).saveNewActiveNetworkMap(capture())
|
||||
val capturedNetworkMap = firstValue.networkMap
|
||||
// Parameters in network map got swapped for latest ones.
|
||||
assertEquals(latestNetParams.serialize().hash, capturedNetworkMap.networkParameterHash)
|
||||
assertThat(capturedNetworkMap.nodeInfoHashes).isEqualTo(nodeInfoHashes)
|
||||
}
|
||||
val paramsCaptor = argumentCaptor<NetworkParameters>()
|
||||
val signatureCaptor = argumentCaptor<DigitalSignatureWithCert>()
|
||||
verify(networkMapStorage).saveNetworkParameters(paramsCaptor.capture(), signatureCaptor.capture())
|
||||
assertEquals(paramsCaptor.firstValue, latestNetParams)
|
||||
assertThat(signatureCaptor.firstValue.verify(latestNetParams.serialize()))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -99,32 +112,128 @@ class NetworkMapSignerTest : TestBase() {
|
||||
// then
|
||||
// Verify networkMapStorage is not called
|
||||
verify(networkMapStorage, never()).saveNewActiveNetworkMap(any())
|
||||
verify(networkMapStorage, never()).saveNetworkParameters(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `signNetworkMap creates a new network map if there is no current network map`() {
|
||||
// given
|
||||
val netParams = testNetworkParameters()
|
||||
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(createNetworkParametersEntity(signingCertAndKeyPair, netParams))
|
||||
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(createNetworkParametersEntityUnsigned(netParams))
|
||||
whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList())
|
||||
whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(null)
|
||||
whenever(signer.signBytes(any())).then {
|
||||
DigitalSignatureWithCert(signingCertAndKeyPair.certificate, Crypto.doSign(signingCertAndKeyPair.keyPair.private, it.arguments[0] as ByteArray))
|
||||
}
|
||||
whenever(signer.signObject(netParams)).then {
|
||||
val serialised = netParams.serialize()
|
||||
SignedNetworkParameters(serialised, signer.signBytes(serialised.bytes))
|
||||
}
|
||||
whenever(networkMapStorage.getParametersUpdate()).thenReturn(null)
|
||||
|
||||
// when
|
||||
networkMapSigner.signNetworkMap()
|
||||
|
||||
// then
|
||||
// Verify networkMapStorage calls
|
||||
verify(networkMapStorage).getActiveNodeInfoHashes()
|
||||
verify(networkMapStorage).getActiveNetworkMap()
|
||||
verify(networkMapStorage).getLatestNetworkParameters()
|
||||
verify(networkMapStorage).getParametersUpdate()
|
||||
argumentCaptor<NetworkMapAndSigned>().apply {
|
||||
verify(networkMapStorage).saveNewActiveNetworkMap(capture())
|
||||
assertEquals(netParams.serialize().hash, firstValue.networkMap.networkParameterHash)
|
||||
}
|
||||
val paramsCaptor = argumentCaptor<NetworkParameters>()
|
||||
val signatureCaptor = argumentCaptor<DigitalSignatureWithCert>()
|
||||
verify(networkMapStorage).saveNetworkParameters(paramsCaptor.capture(), signatureCaptor.capture())
|
||||
assertEquals(paramsCaptor.firstValue, netParams)
|
||||
assertThat(signatureCaptor.firstValue.verify(netParams.serialize()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `signNetworkMap signs new network parameters`() {
|
||||
// given
|
||||
val currentNetworkParameters = createNetworkParametersEntity(signingCertAndKeyPair)
|
||||
val updateNetworkParameters = createNetworkParametersEntityUnsigned(testNetworkParameters(epoch = 2))
|
||||
val updateParametersHash = SecureHash.parse(updateNetworkParameters.parametersHash)
|
||||
val parametersUpdate = ParametersUpdateEntity(0, updateNetworkParameters,"Update time", Instant.ofEpochMilli(random63BitValue()))
|
||||
val netMapEntity = createNetworkMapEntity(signingCertAndKeyPair, currentNetworkParameters, emptyList(), null)
|
||||
whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(netMapEntity)
|
||||
whenever(networkMapStorage.getParametersUpdate()).thenReturn(parametersUpdate)
|
||||
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(updateNetworkParameters)
|
||||
whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList())
|
||||
|
||||
// when
|
||||
networkMapSigner.signNetworkMap()
|
||||
|
||||
// then
|
||||
// Verify networkMapStorage calls
|
||||
verify(networkMapStorage).getActiveNetworkMap()
|
||||
verify(networkMapStorage).getActiveNodeInfoHashes()
|
||||
verify(networkMapStorage).getLatestNetworkParameters()
|
||||
verify(networkMapStorage).getParametersUpdate()
|
||||
|
||||
val paramsCaptor = argumentCaptor<NetworkParameters>()
|
||||
val signatureCaptor = argumentCaptor<DigitalSignatureWithCert>()
|
||||
verify(networkMapStorage, times(1)).saveNetworkParameters(paramsCaptor.capture(), signatureCaptor.capture())
|
||||
assertEquals(paramsCaptor.firstValue, updateNetworkParameters.toNetworkParameters())
|
||||
assertThat(signatureCaptor.firstValue.verify(updateNetworkParameters.parametersBytes))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `signNetworkMap fails if there is parameter update without relevant parameters stored`() {
|
||||
val updateNetworkParameters = createNetworkParametersEntityUnsigned(testNetworkParameters(epoch = 2))
|
||||
val parametersUpdate = ParametersUpdateEntity(0, updateNetworkParameters,"Update time", Instant.ofEpochMilli(random63BitValue()))
|
||||
whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(null)
|
||||
whenever(networkMapStorage.getParametersUpdate()).thenReturn(parametersUpdate)
|
||||
whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList())
|
||||
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(createNetworkParametersEntity())
|
||||
|
||||
verify(networkMapStorage, never()).saveNetworkParameters(any(), any())
|
||||
verify(networkMapStorage, never()).saveNewActiveNetworkMap(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setting flag day on parameters update changes parameters inside network map`() {
|
||||
val activeNetworkParameters = createNetworkParametersEntity(signingCertAndKeyPair, testNetworkParameters(epoch = 1))
|
||||
val updateNetworkParameters = createNetworkParametersEntity(signingCertAndKeyPair, testNetworkParameters(epoch = 2))
|
||||
val parametersUpdate = ParametersUpdateEntity(0, updateNetworkParameters,"Update time", Instant.ofEpochMilli(random63BitValue()))
|
||||
val activeNetworkMap = createNetworkMapEntity(signingCertAndKeyPair, activeNetworkParameters, emptyList(), parametersUpdate.toParametersUpdate())
|
||||
|
||||
whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(activeNetworkMap)
|
||||
whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList())
|
||||
whenever(networkMapStorage.getParametersUpdate()).thenReturn(parametersUpdate.copy(flagDay = true))
|
||||
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(updateNetworkParameters)
|
||||
|
||||
// when
|
||||
networkMapSigner.signNetworkMap()
|
||||
|
||||
//then
|
||||
argumentCaptor<NetworkMapAndSigned>().apply {
|
||||
verify(networkMapStorage).saveNewActiveNetworkMap(capture())
|
||||
val netMap = firstValue.networkMap
|
||||
assertEquals(SecureHash.parse(updateNetworkParameters.parametersHash), netMap.networkParameterHash)
|
||||
assertEquals(emptyList(), netMap.nodeInfoHashes)
|
||||
assertEquals(null, netMap.parametersUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cancel update test`() {
|
||||
val activeNetworkParameters = createNetworkParametersEntity(signingCertAndKeyPair, testNetworkParameters(epoch = 1))
|
||||
val updateNetworkParameters = createNetworkParametersEntityUnsigned(testNetworkParameters(epoch = 2))
|
||||
val parametersUpdate = ParametersUpdateEntity(0, updateNetworkParameters,"Update time", Instant.ofEpochMilli(random63BitValue()))
|
||||
val activeNetworkMap = createNetworkMapEntity(signingCertAndKeyPair, activeNetworkParameters, emptyList(), parametersUpdate.toParametersUpdate())
|
||||
|
||||
whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(activeNetworkMap)
|
||||
whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList())
|
||||
whenever(networkMapStorage.getParametersUpdate()).thenReturn(null)
|
||||
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(createNetworkParametersEntity())
|
||||
|
||||
// when
|
||||
networkMapSigner.signNetworkMap()
|
||||
|
||||
//then
|
||||
argumentCaptor<NetworkMapAndSigned>().apply {
|
||||
verify(networkMapStorage).saveNewActiveNetworkMap(capture())
|
||||
val netMap = firstValue.networkMap
|
||||
assertEquals(SecureHash.parse(activeNetworkParameters.parametersHash), netMap.networkParameterHash)
|
||||
assertEquals(emptyList(), netMap.nodeInfoHashes)
|
||||
assertEquals(null, netMap.parametersUpdate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class DoormanArgsParserTest {
|
||||
@Test
|
||||
fun `should fail when network parameters file is missing`() {
|
||||
assertThatThrownBy {
|
||||
argsParser.parse("--config-file", validConfigPath, "--update-network-parameters", "not-here")
|
||||
argsParser.parse("--config-file", validConfigPath, "--set-network-parameters", "not-here")
|
||||
}.hasMessageContaining("not-here")
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,70 @@
|
||||
package com.r3.corda.networkmanage.doorman
|
||||
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.NotaryInfo
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.junit.Test
|
||||
|
||||
class NetworkParametersCmdTest {
|
||||
@Test
|
||||
fun `maxTransactionSize cannot decrease`() {
|
||||
val netParams = testNetworkParameters(maxTransactionSize = 100)
|
||||
val netParamsCmd = netParams.toCmd()
|
||||
assertThatExceptionOfType(IllegalArgumentException::class.java)
|
||||
.isThrownBy { netParamsCmd.copy(maxTransactionSize = 99).checkCompatibility(netParams) }
|
||||
.withMessageContaining("maxTransactionSize")
|
||||
netParamsCmd.copy(maxTransactionSize = 100).checkCompatibility(netParams)
|
||||
netParamsCmd.copy(maxTransactionSize = 101).checkCompatibility(netParams)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `notary cannot be removed`() {
|
||||
val alice = NotaryInfo(TestIdentity(ALICE_NAME).party, true)
|
||||
val bob = NotaryInfo(TestIdentity(BOB_NAME).party, true)
|
||||
val netParams = testNetworkParameters(notaries = listOf(alice, bob))
|
||||
val netParamsCmd = netParams.toCmd()
|
||||
assertThatExceptionOfType(IllegalArgumentException::class.java)
|
||||
.isThrownBy { netParamsCmd.copy(notaries = listOf(alice)).checkCompatibility(netParams) }
|
||||
.withMessageContaining("notaries")
|
||||
assertThatExceptionOfType(IllegalArgumentException::class.java)
|
||||
.isThrownBy { netParamsCmd.copy(notaries = emptyList()).checkCompatibility(netParams) }
|
||||
.withMessageContaining("notaries")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `notary identity key cannot change`() {
|
||||
val netParams = testNetworkParameters(notaries = listOf(NotaryInfo(freshParty(ALICE_NAME), true)))
|
||||
val notaryKeyChanged = netParams.toCmd().copy(notaries = listOf(NotaryInfo(freshParty(ALICE_NAME), true)))
|
||||
assertThatExceptionOfType(IllegalArgumentException::class.java)
|
||||
.isThrownBy { notaryKeyChanged.checkCompatibility(netParams) }
|
||||
.withMessageContaining("notaries")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `notary name cannot change`() {
|
||||
val identity = freshParty(ALICE_NAME)
|
||||
val netParams = testNetworkParameters(notaries = listOf(NotaryInfo(identity, true)))
|
||||
val notaryNameChanged = netParams.toCmd().copy(notaries = listOf(NotaryInfo(Party(BOB_NAME, identity.owningKey), true)))
|
||||
assertThatExceptionOfType(IllegalArgumentException::class.java)
|
||||
.isThrownBy { notaryNameChanged.checkCompatibility(netParams) }
|
||||
.withMessageContaining("notaries")
|
||||
}
|
||||
|
||||
private fun NetworkParameters.toCmd(): NetworkParametersCmd.Set {
|
||||
return NetworkParametersCmd.Set(
|
||||
minimumPlatformVersion = minimumPlatformVersion,
|
||||
notaries = notaries,
|
||||
maxMessageSize = maxMessageSize,
|
||||
maxTransactionSize = maxTransactionSize,
|
||||
parametersUpdate = null
|
||||
)
|
||||
}
|
||||
|
||||
private fun freshParty(name: CordaX500Name) = TestIdentity(name).party
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package com.r3.corda.networkmanage.doorman
|
||||
|
||||
import com.google.common.jimfs.Jimfs
|
||||
import net.corda.core.internal.copyTo
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.internal.createNodeInfoAndSigned
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.util.*
|
||||
|
||||
class NotaryConfigTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
private val fs = Jimfs.newFileSystem()
|
||||
|
||||
@After
|
||||
fun cleanUp() {
|
||||
fs.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun toNotaryInfo() {
|
||||
val (nodeInfo, signedNodeInfo) = createNodeInfoAndSigned(ALICE_NAME)
|
||||
|
||||
val file = fs.getPath(UUID.randomUUID().toString())
|
||||
signedNodeInfo.serialize().open().copyTo(file)
|
||||
|
||||
val notaryInfo = NotaryConfig(file, true).toNotaryInfo()
|
||||
assertThat(notaryInfo.identity).isEqualTo(nodeInfo.legalIdentities[0])
|
||||
assertThat(notaryInfo.validating).isTrue()
|
||||
}
|
||||
}
|
@ -10,21 +10,19 @@
|
||||
|
||||
package com.r3.corda.networkmanage.doorman.webservice
|
||||
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import com.nhaarman.mockito_kotlin.times
|
||||
import com.nhaarman.mockito_kotlin.verify
|
||||
import com.nhaarman.mockito_kotlin.*
|
||||
import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage
|
||||
import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage
|
||||
import com.r3.corda.networkmanage.createNetworkMapEntity
|
||||
import com.r3.corda.networkmanage.doorman.NetworkManagementWebServer
|
||||
import com.r3.corda.networkmanage.doorman.NetworkMapConfig
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.SecureHash.Companion.randomSHA256
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.checkOkResponse
|
||||
import net.corda.core.internal.openHttpConnection
|
||||
import net.corda.core.internal.responseAs
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||
import net.corda.nodeapi.internal.createDevNetworkMapCa
|
||||
@ -43,7 +41,6 @@ import org.junit.Test
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.ws.rs.core.MediaType
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class NetworkMapWebServiceTest {
|
||||
@ -68,15 +65,13 @@ class NetworkMapWebServiceTest {
|
||||
val networkMapStorage: NetworkMapStorage = mock {
|
||||
on { getActiveNetworkMap() }.thenReturn(createNetworkMapEntity())
|
||||
}
|
||||
|
||||
// Create node info.
|
||||
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"))
|
||||
|
||||
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
|
||||
it.start()
|
||||
val nodeInfoAndSignature = signedNodeInfo.serialize().bytes
|
||||
// Post node info and signature to doorman, this should pass without any exception.
|
||||
it.doPost("publish", nodeInfoAndSignature)
|
||||
it.doPost("publish", signedNodeInfo.serialize())
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,8 +85,7 @@ class NetworkMapWebServiceTest {
|
||||
|
||||
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
|
||||
it.start()
|
||||
val nodeInfoAndSignature = signedNodeInfo.serialize().bytes
|
||||
assertThatThrownBy { it.doPost("publish", nodeInfoAndSignature) }
|
||||
assertThatThrownBy { it.doPost("publish", signedNodeInfo.serialize()) }
|
||||
.hasMessageStartingWith("Response Code 400: Minimum platform version is 2")
|
||||
}
|
||||
}
|
||||
@ -106,8 +100,7 @@ class NetworkMapWebServiceTest {
|
||||
|
||||
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
|
||||
it.start()
|
||||
val nodeInfoAndSignature = signedNodeInfo.serialize().bytes
|
||||
assertThatThrownBy { it.doPost("publish", nodeInfoAndSignature) }
|
||||
assertThatThrownBy { it.doPost("publish", signedNodeInfo.serialize()) }
|
||||
.hasMessageStartingWith("Response Code 503: Network parameters have not been initialised")
|
||||
}
|
||||
}
|
||||
@ -158,7 +151,7 @@ class NetworkMapWebServiceTest {
|
||||
|
||||
@Test
|
||||
fun `get network parameters`() {
|
||||
val networkParameters = testNetworkParameters(emptyList())
|
||||
val networkParameters = testNetworkParameters()
|
||||
val signedNetworkParameters = signingCertAndKeyPair.sign(networkParameters)
|
||||
val networkParametersHash = signedNetworkParameters.raw.hash
|
||||
|
||||
@ -178,15 +171,33 @@ class NetworkMapWebServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
private fun NetworkManagementWebServer.doPost(path: String, payload: ByteArray) {
|
||||
val url = URL("http://$hostAndPort/network-map/$path")
|
||||
url.openHttpConnection().apply {
|
||||
doOutput = true
|
||||
requestMethod = "POST"
|
||||
setRequestProperty("Content-Type", MediaType.APPLICATION_OCTET_STREAM)
|
||||
outputStream.write(payload)
|
||||
checkOkResponse()
|
||||
@Test
|
||||
fun `ack network parameters update`() {
|
||||
val netParams = testNetworkParameters()
|
||||
val hash = netParams.serialize().hash
|
||||
val nodeInfoStorage: NodeInfoStorage = mock {
|
||||
on { ackNodeInfoParametersUpdate(any(), eq(hash)) }.then { Unit }
|
||||
}
|
||||
val networkMapStorage: NetworkMapStorage = mock {
|
||||
on { getSignedNetworkParameters(hash) }.thenReturn(signingCertAndKeyPair.sign(netParams))
|
||||
}
|
||||
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(nodeInfoStorage, networkMapStorage, testNetworkMapConfig)).use {
|
||||
it.start()
|
||||
val keyPair = Crypto.generateKeyPair()
|
||||
val signedHash = hash.serialize().sign { keyPair.sign(it) }
|
||||
it.doPost("ack-parameters", signedHash.serialize())
|
||||
verify(nodeInfoStorage).ackNodeInfoParametersUpdate(keyPair.public.encoded.sha256(), hash)
|
||||
val randomSigned = SecureHash.randomSHA256().serialize().sign { keyPair.sign(it) }
|
||||
assertThatThrownBy { it.doPost("ack-parameters", randomSigned.serialize()) }
|
||||
.hasMessageContaining("HTTP ERROR 500")
|
||||
val badSigned = SignedData(signedHash.raw, randomSigned.sig)
|
||||
assertThatThrownBy { it.doPost("ack-parameters", badSigned.serialize()) }
|
||||
.hasMessageStartingWith("Response Code 403: Signature Verification failed!")
|
||||
}
|
||||
}
|
||||
|
||||
private fun NetworkManagementWebServer.doPost(path: String, payload: OpaqueBytes) {
|
||||
URL("http://$hostAndPort/network-map/$path").post(payload)
|
||||
}
|
||||
|
||||
private inline fun <reified T : Any> NetworkManagementWebServer.doGet(path: String): T {
|
||||
|
@ -12,22 +12,20 @@ package net.corda.node.services.network
|
||||
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.exists
|
||||
import net.corda.core.internal.list
|
||||
import net.corda.core.internal.readObject
|
||||
import net.corda.core.messaging.ParametersUpdateInfo
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.core.serialization.serialize
|
||||
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.testNetworkParameters
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.driver.NodeHandle
|
||||
import net.corda.testing.driver.internal.NodeHandleInternal
|
||||
import net.corda.testing.driver.internal.RandomFree
|
||||
import net.corda.testing.internal.IntegrationTest
|
||||
import net.corda.testing.internal.IntegrationTestSchemas
|
||||
@ -35,6 +33,7 @@ import net.corda.testing.internal.toDatabaseSchemaName
|
||||
import net.corda.testing.node.internal.CompatibilityZoneParams
|
||||
import net.corda.testing.node.internal.internalDriver
|
||||
import net.corda.testing.node.internal.network.NetworkMapServer
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.*
|
||||
import java.net.URL
|
||||
@ -73,6 +72,53 @@ class NetworkMapTest : IntegrationTest() {
|
||||
networkMapServer.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parameters update test`() {
|
||||
internalDriver(
|
||||
portAllocation = portAllocation,
|
||||
compatibilityZone = compatibilityZone,
|
||||
initialiseSerialization = false,
|
||||
notarySpecs = emptyList()
|
||||
) {
|
||||
val alice = startNode(providedName = ALICE_NAME).getOrThrow() as NodeHandleInternal
|
||||
val nextParams = networkMapServer.networkParameters.copy(epoch = 3, modifiedTime = Instant.ofEpochMilli(random63BitValue()))
|
||||
val nextHash = nextParams.serialize().hash
|
||||
val snapshot = alice.rpc.networkParametersFeed().snapshot
|
||||
val updates = alice.rpc.networkParametersFeed().updates.bufferUntilSubscribed()
|
||||
assertEquals(null, snapshot)
|
||||
assertThat(updates.isEmpty)
|
||||
networkMapServer.scheduleParametersUpdate(nextParams, "Next parameters", Instant.ofEpochMilli(random63BitValue()))
|
||||
// Wait for network map client to poll for the next update.
|
||||
Thread.sleep(cacheTimeout.toMillis() * 2)
|
||||
val laterParams = networkMapServer.networkParameters.copy(epoch = 4, modifiedTime = Instant.ofEpochMilli(random63BitValue()))
|
||||
val laterHash = laterParams.serialize().hash
|
||||
networkMapServer.scheduleParametersUpdate(laterParams, "Another update", Instant.ofEpochMilli(random63BitValue()))
|
||||
Thread.sleep(cacheTimeout.toMillis() * 2)
|
||||
updates.expectEvents(isStrict = false) {
|
||||
sequence(
|
||||
expect { update: ParametersUpdateInfo ->
|
||||
assertEquals(update.description, "Next parameters")
|
||||
assertEquals(update.hash, nextHash)
|
||||
assertEquals(update.parameters, nextParams)
|
||||
},
|
||||
expect { update: ParametersUpdateInfo ->
|
||||
assertEquals(update.description, "Another update")
|
||||
assertEquals(update.hash, laterHash)
|
||||
assertEquals(update.parameters, laterParams)
|
||||
}
|
||||
)
|
||||
}
|
||||
// This should throw, because the nextHash has been replaced by laterHash
|
||||
Assertions.assertThatThrownBy { alice.rpc.acceptNewNetworkParameters(nextHash) }.hasMessageContaining("Refused to accept parameters with hash")
|
||||
alice.rpc.acceptNewNetworkParameters(laterHash)
|
||||
assertEquals(laterHash, networkMapServer.latestParametersAccepted(alice.nodeInfo.legalIdentities.first().owningKey))
|
||||
networkMapServer.advertiseNewParameters()
|
||||
val networkParameters = (alice.configuration.baseDirectory / NETWORK_PARAMS_UPDATE_FILE_NAME)
|
||||
.readObject<SignedNetworkParameters>().verified()
|
||||
Assert.assertEquals(networkParameters, laterParams)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `node correctly downloads and saves network parameters file on startup`() {
|
||||
internalDriver(
|
||||
|
@ -215,10 +215,10 @@ class NetworkMapUpdaterTest {
|
||||
updates.expectEvents(isStrict = false) {
|
||||
sequence(
|
||||
expect { update: ParametersUpdateInfo ->
|
||||
assertThat(update.updateDeadline == updateDeadline)
|
||||
assertThat(update.description == "Test update")
|
||||
assertThat(update.hash == newParameters.serialize().hash)
|
||||
assertThat(update.parameters == newParameters)
|
||||
assertEquals(update.updateDeadline, updateDeadline)
|
||||
assertEquals(update.description,"Test update")
|
||||
assertEquals(update.hash, newParameters.serialize().hash)
|
||||
assertEquals(update.parameters, newParameters)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -54,10 +54,6 @@ class NetworkMapServer(private val cacheTimeout: Duration,
|
||||
|
||||
private val server: Server
|
||||
var networkParameters: NetworkParameters = stubNetworkParameters
|
||||
set(networkParameters) {
|
||||
check(field == stubNetworkParameters) { "Network parameters can be set only once" }
|
||||
field = networkParameters
|
||||
}
|
||||
private val service = InMemoryNetworkMapService()
|
||||
private var parametersUpdate: ParametersUpdate? = null
|
||||
private var nextNetworkParameters: NetworkParameters? = null
|
||||
|
Loading…
Reference in New Issue
Block a user