ENT-1557 - Network Map returns 404 not found for current node info advertised in the network map (#511)

* * added is current and timestamp to the node info table
* getNodeInfoHashes returns all "current" node info hashes
* TODO: network map should return 404 if receive old node info request
* TODO: database migration integration test

* fix compilation error

* * removed unnecessary unique constraint

* rebase and tidy up liquid base xml

* address PR issues

* address PR issues

* address PR issues
This commit is contained in:
Patrick Kuo 2018-03-14 11:53:55 +00:00 committed by GitHub
parent 341e060424
commit a435c23e19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 162 additions and 84 deletions

View File

@ -48,6 +48,14 @@ The built file will appear in
network-management/capsule-hsm-cert-generator/build/libs/hsm-cert-generator-<VERSION>.jar
```
# Logs
In order to set the desired logging level the system properties need to be used.
Appropriate system properties can be set at the execution time.
Example:
```
java -DdefaultLogLevel=TRACE -DconsoleLogLevel=TRACE -jar doorman-<version>.jar --config-file <config file>
```
#Configuring network management service
### Local signing
@ -98,7 +106,8 @@ The doorman service can use JIRA to manage the certificate signing request appro
signInterval = 10000
}
```
`cacheTimeout`(ms) indicates how often the network map should poll the database for a newly signed network map. This is also added to the HTTP response header to set the node's network map update frequency.
`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
@ -212,10 +221,12 @@ We can now restart the network management server with both doorman and network m
java -jar doorman-<version>.jar --config-file <config file> --update-network-parameters network-parameters.conf
```
### 7. Logs
In order to set the desired logging level the system properties need to be used.
Appropriate system properties can be set at the execution time.
Example:
### 7. Archive policy
The ``node_info`` and ``network_map`` table are designed to retain all historical data for auditing purposes and will grow over time.
**It is recommended to monitor the space usage and archive these tables according to the data retention policy.**
Run the following SQL script to archive the node info table (change the timestamp according to the archive policy):
```
java -DdefaultLogLevel=TRACE -DconsoleLogLevel=TRACE -jar doorman-<version>.jar --config-file <config file>
```
delect from node_info where is_current = false and published_at < '2018-03-12'
```

View File

@ -27,13 +27,11 @@ interface NetworkMapStorage {
fun getCurrentNetworkMap(): SignedNetworkMap?
/**
* Retrieves node info hashes where the certificate status matches [certificateStatus].
* Retrieves node info hashes where [isCurrent] is true and the certificate status is [CertificateStatus.VALID]
*
* @param certificateStatus certificate status to be used in the node info filtering. Node info hash is returned
* in the result collection if its certificate status matches [certificateStatus].
* @return list of node info hashes satisfying the filtering criteria given by [certificateStatus].
* @return list of current and valid node info hashes.
*/
fun getNodeInfoHashes(certificateStatus: CertificateStatus): List<SecureHash>
fun getActiveNodeInfoHashes(): List<SecureHash>
/**
* Persists a new instance of the signed network map.
@ -51,6 +49,7 @@ interface NetworkMapStorage {
/**
* Retrieve the network parameters of the current network map, or null if there's no network map.
*/
// TODO: Remove this method. We should get the "current" network parameter by using the the hash in the network map and use the [getSignedNetworkParameters] method.
fun getNetworkParametersOfNetworkMap(): SignedNetworkParameters?
/**

View File

@ -24,19 +24,23 @@ import javax.persistence.criteria.CriteriaBuilder
import javax.persistence.criteria.Path
import javax.persistence.criteria.Predicate
fun <T> DatabaseTransaction.singleRequestWhere(clazz: Class<T>, predicate: (CriteriaBuilder, Path<T>) -> Predicate): T? {
val builder = session.criteriaBuilder
val criteriaQuery = builder.createQuery(clazz)
val query = criteriaQuery.from(clazz).run {
criteriaQuery.where(predicate(builder, this))
}
return session.createQuery(query).setLockMode(LockModeType.PESSIMISTIC_WRITE).resultList.firstOrNull()
inline fun <reified T> DatabaseTransaction.singleEntityWhere(predicate: (CriteriaBuilder, Path<T>) -> Predicate): T? {
return getEntitiesWhere(predicate).firstOrNull()
}
fun <T> DatabaseTransaction.deleteRequest(clazz: Class<T>, predicate: (CriteriaBuilder, Path<T>) -> Predicate): Int {
inline fun <reified T> DatabaseTransaction.getEntitiesWhere(predicate: (CriteriaBuilder, Path<T>) -> Predicate): List<T> {
val builder = session.criteriaBuilder
val criteriaDelete = builder.createCriteriaDelete(clazz)
val delete = criteriaDelete.from(clazz).run {
val criteriaQuery = builder.createQuery(T::class.java)
val query = criteriaQuery.from(T::class.java).run {
criteriaQuery.where(predicate(builder, this))
}
return session.createQuery(query).setLockMode(LockModeType.PESSIMISTIC_WRITE).resultList
}
inline fun <reified T> DatabaseTransaction.deleteEntity(predicate: (CriteriaBuilder, Path<T>) -> Predicate): Int {
val builder = session.criteriaBuilder
val criteriaDelete = builder.createCriteriaDelete(T::class.java)
val delete = criteriaDelete.from(T::class.java).run {
criteriaDelete.where(predicate(builder, this))
}
return session.createQuery(delete).executeUpdate()

View File

@ -40,7 +40,7 @@ class PersistentCertificateRevocationListStorage(private val database: CordaPers
}
private fun revokeCertificate(certificateSerialNumber: BigInteger, time: Instant, transaction: DatabaseTransaction) {
val revocation = transaction.singleRequestWhere(CertificateRevocationRequestEntity::class.java) { builder, path ->
val revocation = transaction.singleEntityWhere<CertificateRevocationRequestEntity> { builder, path ->
builder.equal(path.get<BigInteger>(CertificateRevocationRequestEntity::certificateSerialNumber.name), certificateSerialNumber)
}
revocation ?: throw IllegalStateException("The certificate revocation request for $certificateSerialNumber does not exist")

View File

@ -13,7 +13,7 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP
override fun saveRevocationRequest(certificateSerialNumber: BigInteger, reason: CRLReason, reporter: String): String {
return database.transaction(TransactionIsolationLevel.SERIALIZABLE) {
// Check if there is an entry for the given certificate serial number
val revocation = singleRequestWhere(CertificateRevocationRequestEntity::class.java) { builder, path ->
val revocation = singleEntityWhere<CertificateRevocationRequestEntity> { builder, path ->
val serialNumberEqual = builder.equal(path.get<BigInteger>(CertificateRevocationRequestEntity::certificateSerialNumber.name), certificateSerialNumber)
val statusNotEqualRejected = builder.notEqual(path.get<RequestStatus>(CertificateRevocationRequestEntity::status.name), RequestStatus.REJECTED)
builder.and(serialNumberEqual, statusNotEqualRejected)
@ -21,7 +21,7 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP
if (revocation != null) {
revocation.requestId
} else {
val certificateData = singleRequestWhere(CertificateDataEntity::class.java) { builder, path ->
val certificateData = singleEntityWhere<CertificateDataEntity> { builder, path ->
val serialNumberEqual = builder.equal(path.get<BigInteger>(CertificateDataEntity::certificateSerialNumber.name), certificateSerialNumber)
val statusEqualValid = builder.equal(path.get<CertificateStatus>(CertificateDataEntity::certificateStatus.name), CertificateStatus.VALID)
builder.and(serialNumberEqual, statusEqualValid)
@ -90,7 +90,7 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP
}
private fun getRevocationRequestEntity(requestId: String): CertificateRevocationRequestEntity? = database.transaction {
singleRequestWhere(CertificateRevocationRequestEntity::class.java) { builder, path ->
singleEntityWhere { builder, path ->
builder.equal(path.get<String>(CertificateRevocationRequestEntity::requestId.name), requestId)
}
}

View File

@ -38,7 +38,7 @@ class PersistentCertificateSigningRequestStorage(private val database: CordaPers
override fun putCertificatePath(requestId: String, certPath: CertPath, signedBy: String) {
return database.transaction(TransactionIsolationLevel.SERIALIZABLE) {
val request = singleRequestWhere(CertificateSigningRequestEntity::class.java) { builder, path ->
val request = singleEntityWhere<CertificateSigningRequestEntity> { builder, path ->
val requestIdEq = builder.equal(path.get<String>(CertificateSigningRequestEntity::requestId.name), requestId)
val statusEq = builder.equal(path.get<String>(CertificateSigningRequestEntity::status.name), RequestStatus.APPROVED)
builder.and(requestIdEq, statusEq)
@ -89,7 +89,7 @@ class PersistentCertificateSigningRequestStorage(private val database: CordaPers
private fun DatabaseTransaction.findRequest(requestId: String,
requestStatus: RequestStatus? = null): CertificateSigningRequestEntity? {
return singleRequestWhere(CertificateSigningRequestEntity::class.java) { builder, path ->
return singleEntityWhere { builder, path ->
val idClause = builder.equal(path.get<String>(CertificateSigningRequestEntity::requestId.name), requestId)
if (requestStatus == null) {
idClause

View File

@ -57,15 +57,17 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw
}
}
override fun getNodeInfoHashes(certificateStatus: CertificateStatus): List<SecureHash> {
override fun getActiveNodeInfoHashes(): List<SecureHash> {
return database.transaction {
val builder = session.criteriaBuilder
val query = builder.createQuery(String::class.java).run {
from(NodeInfoEntity::class.java).run {
select(get<String>(NodeInfoEntity::nodeInfoHash.name))
.where(builder.equal(get<CertificateSigningRequestEntity>(NodeInfoEntity::certificateSigningRequest.name)
.get<CertificateDataEntity>(CertificateSigningRequestEntity::certificateData.name)
.get<CertificateStatus>(CertificateDataEntity::certificateStatus.name), certificateStatus))
val certStatusExpression = get<CertificateSigningRequestEntity>(NodeInfoEntity::certificateSigningRequest.name)
.get<CertificateDataEntity>(CertificateSigningRequestEntity::certificateData.name)
.get<CertificateStatus>(CertificateDataEntity::certificateStatus.name)
val certStatusEq = builder.equal(certStatusExpression, CertificateStatus.VALID)
val isCurrentNodeInfo = builder.isTrue(get<Boolean>(NodeInfoEntity::isCurrent.name))
select(get<String>(NodeInfoEntity::nodeInfoHash.name)).where(builder.and(certStatusEq, isCurrentNodeInfo))
}
}
session.createQuery(query).resultList.map { SecureHash.parse(it) }
@ -117,7 +119,7 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw
private fun getNetworkParametersEntity(parameterHash: String): NetworkParametersEntity? {
return database.transaction {
singleRequestWhere(NetworkParametersEntity::class.java) { builder, path ->
singleEntityWhere { builder, path ->
builder.equal(path.get<String>(NetworkParametersEntity::parametersHash.name), parameterHash)
}
}

View File

@ -35,29 +35,27 @@ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeIn
nodeCaCert ?: throw IllegalArgumentException("Missing Node CA")
return database.transaction {
// TODO Move these checks out of data access layer
val request = requireNotNull(getSignedRequestByPublicHash(nodeCaCert.publicKey.encoded.sha256(), this)) {
val request = requireNotNull(getSignedRequestByPublicHash(nodeCaCert.publicKey.encoded.sha256())) {
"Node-info not registered with us"
}
request.certificateData?.certificateStatus.let {
require(it == CertificateStatus.VALID) { "Certificate is no longer valid: $it" }
}
/*
* Delete any previous [NodeInfoEntity] instance for this CSR
* Possibly it should be moved at the network signing process at the network signing process
* as for a while the network map will have invalid entries (i.e. hashes for node info which have been
* removed). Either way, there will be a period of time when the network map data will be invalid
* but it has been confirmed that this fact has been acknowledged at the design time and we are fine with it.
*/
deleteRequest(NodeInfoEntity::class.java) { builder, path ->
builder.equal(path.get<CertificateSigningRequestEntity>(NodeInfoEntity::certificateSigningRequest.name), request)
// Update any [NodeInfoEntity] instance for this CSR as not current.
val existingNodeInfo = getEntitiesWhere<NodeInfoEntity> { builder, path ->
val requestEq = builder.equal(path.get<CertificateSigningRequestEntity>(NodeInfoEntity::certificateSigningRequest.name), request)
val isCurrent = builder.isTrue(path.get<Boolean>(NodeInfoEntity::isCurrent.name))
builder.and(requestEq, isCurrent)
}
val hash = signedNodeInfo.raw.hash
existingNodeInfo.forEach { session.merge(it.copy(isCurrent = false)) }
val hash = signedNodeInfo.raw.hash
val hashedNodeInfo = NodeInfoEntity(
nodeInfoHash = hash.toString(),
certificateSigningRequest = request,
signedNodeInfoBytes = signedNodeInfo.serialize().bytes)
signedNodeInfoBytes = signedNodeInfo.serialize().bytes,
isCurrent = true)
session.save(hashedNodeInfo)
hash
}
@ -71,13 +69,13 @@ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeIn
override fun getCertificatePath(publicKeyHash: SecureHash): CertPath? {
return database.transaction {
val request = getSignedRequestByPublicHash(publicKeyHash, this)
val request = getSignedRequestByPublicHash(publicKeyHash)
request?.let { buildCertPath(it.certificateData!!.certificatePathBytes) }
}
}
private fun getSignedRequestByPublicHash(publicKeyHash: SecureHash, transaction: DatabaseTransaction): CertificateSigningRequestEntity? {
return transaction.singleRequestWhere(CertificateSigningRequestEntity::class.java) { builder, path ->
private fun DatabaseTransaction.getSignedRequestByPublicHash(publicKeyHash: SecureHash): CertificateSigningRequestEntity? {
return singleEntityWhere { builder, path ->
val publicKeyEq = builder.equal(path.get<String>(CertificateSigningRequestEntity::publicKeyHash.name), publicKeyHash.toString())
val statusEq = builder.equal(path.get<RequestStatus>(CertificateSigningRequestEntity::status.name), RequestStatus.DONE)
builder.and(publicKeyEq, statusEq)

View File

@ -12,6 +12,8 @@ package com.r3.corda.networkmanage.common.persistence.entity
import net.corda.core.serialization.deserialize
import net.corda.nodeapi.internal.SignedNodeInfo
import org.hibernate.annotations.CreationTimestamp
import java.time.Instant
import javax.persistence.*
@Entity
@ -22,13 +24,19 @@ class NodeInfoEntity(
@Column(name = "node_info_hash", length = 64)
val nodeInfoHash: String = "",
@OneToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "certificate_signing_request", nullable = true)
val certificateSigningRequest: CertificateSigningRequestEntity? = null,
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "certificate_signing_request")
val certificateSigningRequest: CertificateSigningRequestEntity,
@Lob
@Column(name = "signed_node_info_bytes")
val signedNodeInfoBytes: ByteArray
val signedNodeInfoBytes: ByteArray,
@Column(name="is_current")
val isCurrent: Boolean,
@Column(name = "published_at")
val publishedAt: Instant = Instant.now()
) {
/**
* Deserializes NodeInfoEntity.soignedNodeInfoBytes into the [SignedNodeInfo] instance
@ -36,13 +44,17 @@ class NodeInfoEntity(
fun signedNodeInfo() = signedNodeInfoBytes.deserialize<SignedNodeInfo>()
fun copy(nodeInfoHash: String = this.nodeInfoHash,
certificateSigningRequest: CertificateSigningRequestEntity? = this.certificateSigningRequest,
signedNodeInfoBytes: ByteArray = this.signedNodeInfoBytes
certificateSigningRequest: CertificateSigningRequestEntity = this.certificateSigningRequest,
signedNodeInfoBytes: ByteArray = this.signedNodeInfoBytes,
isCurrent: Boolean = this.isCurrent,
publishedAt: Instant = this.publishedAt
): NodeInfoEntity {
return NodeInfoEntity(
nodeInfoHash = nodeInfoHash,
certificateSigningRequest = certificateSigningRequest,
signedNodeInfoBytes = signedNodeInfoBytes
signedNodeInfoBytes = signedNodeInfoBytes,
isCurrent = isCurrent,
publishedAt = publishedAt
)
}
}

View File

@ -10,7 +10,6 @@
package com.r3.corda.networkmanage.common.signer
import com.r3.corda.networkmanage.common.persistence.CertificateStatus
import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage
import net.corda.core.internal.SignedDataWithCert
import net.corda.core.node.NetworkParameters
@ -45,7 +44,7 @@ class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private
logger.debug("Fetching current network map...")
val currentSignedNetworkMap = networkMapStorage.getCurrentNetworkMap()
logger.debug("Fetching node info hashes with VALID certificates...")
val nodeInfoHashes = networkMapStorage.getNodeInfoHashes(CertificateStatus.VALID)
val nodeInfoHashes = networkMapStorage.getActiveNodeInfoHashes()
logger.debug("Retrieved node info hashes: $nodeInfoHashes")
val newNetworkMap = NetworkMap(nodeInfoHashes, latestNetworkParameters.serialize().hash, null)
val serialisedNetworkMap = newNetworkMap.serialize()

View File

@ -51,11 +51,23 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage,
const val NETWORK_MAP_PATH = "network-map"
}
private val networkMapCache: LoadingCache<Boolean, Pair<SignedNetworkMap?, NetworkParameters?>> = CacheBuilder.newBuilder()
private val networkMapCache: LoadingCache<Boolean, CachedData> = CacheBuilder.newBuilder()
.expireAfterWrite(config.cacheTimeout, TimeUnit.MILLISECONDS)
.build(CacheLoader.from { _ ->
Pair(networkMapStorage.getCurrentNetworkMap(), networkMapStorage.getNetworkParametersOfNetworkMap()?.verified()) }
)
.build(CacheLoader.from { _ -> networkMapStorage.getCurrentNetworkMap()?.let {
val networkMap = it.verified()
CachedData(it, networkMap.nodeInfoHashes.toSet(), networkMapStorage.getSignedNetworkParameters(networkMap.networkParameterHash)?.verified()) }
})
private val nodeInfoCache: LoadingCache<SecureHash, SignedNodeInfo> = CacheBuilder.newBuilder()
// TODO: Define cache retention policy.
.softValues()
.build(CacheLoader.from { key ->
key?.let { nodeInfoStorage.getNodeInfo(it) }
})
private val currentSignedNetworkMap: SignedNetworkMap? get() = networkMapCache.getOrNull(true)?.signedNetworkMap
private val currentNodeInfoHashes: Set<SecureHash> get() = networkMapCache.getOrNull(true)?.nodeInfoHashes ?: emptySet()
private val currentNetworkParameters: NetworkParameters? get() = networkMapCache.getOrNull(true)?.currentNetworkParameter
@POST
@Path("publish")
@ -84,13 +96,19 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage,
}
@GET
fun getNetworkMap(): Response = createResponse(networkMapCache.get(true).first, addCacheTimeout = true)
fun getNetworkMap(): Response = createResponse(currentSignedNetworkMap, addCacheTimeout = true)
@GET
@Path("node-info/{nodeInfoHash}")
fun getNodeInfo(@PathParam("nodeInfoHash") nodeInfoHash: String): Response {
val signedNodeInfo = nodeInfoStorage.getNodeInfo(SecureHash.parse(nodeInfoHash))
logger.trace { "Precessed node info request for hash: '$nodeInfoHash'" }
// Only serve node info if its in the current network map, otherwise return 404.
logger.trace { "Processing node info request for hash: '$nodeInfoHash'" }
val signedNodeInfo = if (SecureHash.parse(nodeInfoHash) in currentNodeInfoHashes) {
nodeInfoCache.getOrNull(SecureHash.parse(nodeInfoHash))
} else {
logger.trace { "Requested node info is not current, returning null." }
null
}
logger.trace { "Node Info: ${signedNodeInfo?.verified()}" }
return createResponse(signedNodeInfo)
}
@ -113,7 +131,7 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage,
}
private fun verifyNodeInfo(nodeInfo: NodeInfo) {
val minimumPlatformVersion = networkMapCache.get(true).second?.minimumPlatformVersion
val minimumPlatformVersion = currentNetworkParameters?.minimumPlatformVersion
?: throw NetworkMapNotInitialisedException("Network parameters have not been initialised")
if (nodeInfo.platformVersion < minimumPlatformVersion) {
throw InvalidPlatformVersionException("Minimum platform version is $minimumPlatformVersion")
@ -134,4 +152,16 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage,
class NetworkMapNotInitialisedException(message: String?) : Exception(message)
class InvalidPlatformVersionException(message: String?) : Exception(message)
private data class CachedData(val signedNetworkMap: SignedNetworkMap, val nodeInfoHashes: Set<SecureHash>, val currentNetworkParameter: NetworkParameters?)
// Guava loading cache will throw if value is null, this helper method returns null instead.
// The loading cache will load the data from persistence again ignoring timeout if previous value was null.
private fun <K : Any, V : Any> LoadingCache<K, V>.getOrNull(key: K): V? {
return try {
get(key)
} catch (e: CacheLoader.InvalidCacheLoadException) {
null
}
}
}

View File

@ -100,10 +100,18 @@
<column name="node_info_hash" type="NVARCHAR(64)">
<constraints nullable="false"/>
</column>
<column name="signed_node_info_bytes" type="BLOB"/>
<column name="signed_node_info_bytes" type="BLOB">
<constraints nullable="false"/>
</column>
<column name="certificate_signing_request" type="NVARCHAR(64)">
<constraints nullable="false"/>
</column>
<column name="is_current" type="BOOLEAN">
<constraints nullable="false"/>
</column>
<column name="published_at" type="TIMESTAMP">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet author="R3.Corda" id="1520338500424-8">
@ -135,9 +143,6 @@
<changeSet author="R3.Corda" id="1520338500424-15">
<addUniqueConstraint columnNames="certificate_signing_request" constraintName="UK_CD_CSR" tableName="certificate_data"/>
</changeSet>
<changeSet author="R3.Corda" id="1520338500424-16">
<addUniqueConstraint columnNames="certificate_signing_request" constraintName="UK_NI_CSR" tableName="node_info"/>
</changeSet>
<changeSet author="R3.Corda" id="1520338500424-19">
<createIndex indexName="IDX_CSRA_REV" tableName="certificate_signing_request_AUD">
<column name="REV"/>

View File

@ -141,7 +141,7 @@ class PersistentNetworkMapStorageTest : TestBase() {
networkMapStorage.saveNetworkMap(signedNetworkMap)
// when
val validNodeInfoHash = networkMapStorage.getNodeInfoHashes(CertificateStatus.VALID)
val validNodeInfoHash = networkMapStorage.getActiveNodeInfoHashes()
// then
assertThat(validNodeInfoHash).containsOnly(nodeInfoHashA, nodeInfoHashB)

View File

@ -37,10 +37,12 @@ import javax.security.auth.x500.X500Principal
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
class PersistentNodeInfoStorageTest : TestBase() {
private lateinit var requestStorage: CertificateSigningRequestStorage
private lateinit var nodeInfoStorage: PersistentNodeInfoStorage
private lateinit var networkMapStorage: PersistentNetworkMapStorage
private lateinit var persistence: CordaPersistence
private lateinit var rootCaCert: X509Certificate
private lateinit var intermediateCa: CertificateAndKeyPair
@ -53,6 +55,7 @@ class PersistentNodeInfoStorageTest : TestBase() {
persistence = configureDatabase(MockServices.makeTestDataSourceProperties(), DatabaseConfig(runMigration = true))
nodeInfoStorage = PersistentNodeInfoStorage(persistence)
requestStorage = PersistentCertificateSigningRequestStorage(persistence)
networkMapStorage = PersistentNetworkMapStorage(persistence)
}
@After
@ -119,12 +122,15 @@ class PersistentNodeInfoStorageTest : TestBase() {
val nodeInfo1Hash = nodeInfoStorage.putNodeInfo(node1)
assertEquals(node1.nodeInfo, nodeInfoStorage.getNodeInfo(nodeInfo1Hash)?.verified())
assertTrue(networkMapStorage.getActiveNodeInfoHashes().contains(nodeInfo1Hash))
// This should replace the node info.
nodeInfoStorage.putNodeInfo(node2)
val nodeInfo2Hash = nodeInfoStorage.putNodeInfo(node2)
// Old node info should be removed.
assertNull(nodeInfoStorage.getNodeInfo(nodeInfo1Hash))
// Old node info should be removed from list of current node info hashes, but still accessible if required.
assertThat(networkMapStorage.getActiveNodeInfoHashes()).doesNotContain(nodeInfo1Hash)
assertThat(networkMapStorage.getActiveNodeInfoHashes()).contains(nodeInfo2Hash)
assertNotNull(nodeInfoStorage.getNodeInfo(nodeInfo1Hash))
assertEquals(nodeInfo2, nodeInfoStorage.getNodeInfo(nodeInfo2.serialize().hash)?.verified())
}

View File

@ -27,10 +27,10 @@ import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.verifiedNetworkMapCert
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.internal.createDevIntermediateCaCertPath
import java.security.cert.X509Certificate
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import java.security.cert.X509Certificate
import kotlin.test.assertEquals
class NetworkMapSignerTest : TestBase() {
@ -60,7 +60,7 @@ class NetworkMapSignerTest : TestBase() {
val networkMap = NetworkMap(signedNodeInfoHashes, currentParameters.serialize().hash, null)
val signedNetworkMap = networkMap.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate)
whenever(networkMapStorage.getCurrentNetworkMap()).thenReturn(signedNetworkMap)
whenever(networkMapStorage.getNodeInfoHashes(any())).thenReturn(signedNodeInfoHashes)
whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(signedNodeInfoHashes)
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(latestNetworkParameters)
whenever(networkMapStorage.getNetworkParametersOfNetworkMap()).thenReturn(currentParameters.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate))
whenever(signer.signBytes(any())).then {
@ -76,7 +76,7 @@ class NetworkMapSignerTest : TestBase() {
// then
// Verify networkMapStorage calls
verify(networkMapStorage).getNodeInfoHashes(any())
verify(networkMapStorage).getActiveNodeInfoHashes()
verify(networkMapStorage).getLatestNetworkParameters()
verify(networkMapStorage).getNetworkParametersOfNetworkMap()
argumentCaptor<SignedNetworkMap>().apply {
@ -96,7 +96,7 @@ class NetworkMapSignerTest : TestBase() {
val networkMap = NetworkMap(emptyList(), networkMapParametersHash, null)
val signedNetworkMap = networkMap.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate)
whenever(networkMapStorage.getCurrentNetworkMap()).thenReturn(signedNetworkMap)
whenever(networkMapStorage.getNodeInfoHashes(any())).thenReturn(emptyList())
whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList())
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkParameters)
whenever(networkMapStorage.getNetworkParametersOfNetworkMap()).thenReturn(networkParameters.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate))
@ -113,7 +113,7 @@ class NetworkMapSignerTest : TestBase() {
// given
val networkParameters = testNetworkParameters(emptyList())
whenever(networkMapStorage.getCurrentNetworkMap()).thenReturn(null)
whenever(networkMapStorage.getNodeInfoHashes(any())).thenReturn(emptyList())
whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList())
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkParameters)
whenever(signer.signBytes(any())).then {
DigitalSignatureWithCert(networkMapCa.certificate, Crypto.doSign(networkMapCa.keyPair.private, it.arguments[0] as ByteArray))
@ -127,7 +127,7 @@ class NetworkMapSignerTest : TestBase() {
// then
// Verify networkMapStorage calls
verify(networkMapStorage).getNodeInfoHashes(any())
verify(networkMapStorage).getActiveNodeInfoHashes()
verify(networkMapStorage).getLatestNetworkParameters()
argumentCaptor<SignedNetworkMap>().apply {
verify(networkMapStorage).saveNetworkMap(capture())

View File

@ -10,6 +10,7 @@
package com.r3.corda.networkmanage.doorman.webservice
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.times
import com.nhaarman.mockito_kotlin.verify
@ -67,7 +68,9 @@ class NetworkMapWebServiceTest {
@Test
fun `submit nodeInfo`() {
val networkMapStorage: NetworkMapStorage = mock {
on { getNetworkParametersOfNetworkMap() }.thenReturn(testNetworkParameters(emptyList()).signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate))
val networkParameter = testNetworkParameters(emptyList()).signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate)
on { getSignedNetworkParameters(any()) }.thenReturn(networkParameter)
on { getCurrentNetworkMap() }.thenReturn(NetworkMap(emptyList(), networkParameter.raw.hash, null).signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate))
}
// Create node info.
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"))
@ -83,7 +86,9 @@ class NetworkMapWebServiceTest {
@Test
fun `submit old nodeInfo`() {
val networkMapStorage: NetworkMapStorage = mock {
on { getNetworkParametersOfNetworkMap() }.thenReturn(testNetworkParameters(emptyList(), minimumPlatformVersion = 2).signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate))
val networkParameter = testNetworkParameters(emptyList(), minimumPlatformVersion = 2).signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate)
on { getSignedNetworkParameters(any()) }.thenReturn(networkParameter)
on { getCurrentNetworkMap() }.thenReturn(NetworkMap(emptyList(), networkParameter.raw.hash, null).signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate))
}
// Create node info.
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"), platformVersion = 1)
@ -131,14 +136,21 @@ class NetworkMapWebServiceTest {
@Test
fun `get node info`() {
// Mock node info storage
val (nodeInfo, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"))
val nodeInfoHash = nodeInfo.serialize().hash
val nodeInfoStorage: NodeInfoStorage = mock {
on { getNodeInfo(nodeInfoHash) }.thenReturn(signedNodeInfo)
}
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(nodeInfoStorage, mock(), testNetworkMapConfig)).use {
// Mock network map storage
val networkMap = NetworkMap(listOf(nodeInfoHash), randomSHA256(), null)
val signedNetworkMap = networkMap.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate)
val networkMapStorage: NetworkMapStorage = mock {
on { getCurrentNetworkMap() }.thenReturn(signedNetworkMap)
}
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(nodeInfoStorage, networkMapStorage, testNetworkMapConfig)).use {
it.start()
val nodeInfoResponse = it.doGet<SignedNodeInfo>("node-info/$nodeInfoHash")
verify(nodeInfoStorage, times(1)).getNodeInfo(nodeInfoHash)