mirror of
https://github.com/corda/corda.git
synced 2024-12-28 00:38:55 +00:00
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:
parent
341e060424
commit
a435c23e19
@ -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'
|
||||
```
|
||||
|
||||
|
@ -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?
|
||||
|
||||
/**
|
||||
|
@ -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()
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"/>
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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)
|
||||
|
BIN
network-management/src/test/resources/oldNodeInfoDB.mv.db
Normal file
BIN
network-management/src/test/resources/oldNodeInfoDB.mv.db
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user