mirror of
https://github.com/corda/corda.git
synced 2025-06-18 15:18:16 +00:00
Use NetworkMap and SignedNetworkMap in NetworkMapClient, and enable signature verification. (#2054)
* new network map object for network map, and verify signature and root in Signed network map and node info * fixup after rebase * * added certificate and key to network map server * move DigitalSignature.WithCert back to NetworkMap.kt, as its breaking API test, will raise another PR to move it back. * Make DigitalSignature.WithCert not extend WithKey, as per PR discussion. * various fixes after rebase. * move Network map back to core/node, as its breaking API test * revert unintended changes * move network map objects to node-api
This commit is contained in:
@ -1678,27 +1678,6 @@ public @interface net.corda.core.messaging.RPCReturnsObservables
|
|||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowHandle startFlow(net.corda.core.flows.FlowLogic)
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowHandle startFlow(net.corda.core.flows.FlowLogic)
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowProgressHandle startTrackedFlow(net.corda.core.flows.FlowLogic)
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowProgressHandle startTrackedFlow(net.corda.core.flows.FlowLogic)
|
||||||
##
|
##
|
||||||
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NetworkParameters extends java.lang.Object
|
|
||||||
public <init>(int, List, java.time.Duration, int, int, java.time.Instant, int)
|
|
||||||
public final int component1()
|
|
||||||
@org.jetbrains.annotations.NotNull public final List component2()
|
|
||||||
@org.jetbrains.annotations.NotNull public final java.time.Duration component3()
|
|
||||||
public final int component4()
|
|
||||||
public final int component5()
|
|
||||||
@org.jetbrains.annotations.NotNull public final java.time.Instant component6()
|
|
||||||
public final int component7()
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters copy(int, List, java.time.Duration, int, int, java.time.Instant, int)
|
|
||||||
public boolean equals(Object)
|
|
||||||
public final int getEpoch()
|
|
||||||
@org.jetbrains.annotations.NotNull public final java.time.Duration getEventHorizon()
|
|
||||||
public final int getMaxMessageSize()
|
|
||||||
public final int getMaxTransactionSize()
|
|
||||||
public final int getMinimumPlatformVersion()
|
|
||||||
@org.jetbrains.annotations.NotNull public final java.time.Instant getModifiedTime()
|
|
||||||
@org.jetbrains.annotations.NotNull public final List getNotaries()
|
|
||||||
public int hashCode()
|
|
||||||
public String toString()
|
|
||||||
##
|
|
||||||
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NodeInfo extends java.lang.Object
|
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NodeInfo extends java.lang.Object
|
||||||
public <init>(List, List, int, long)
|
public <init>(List, List, int, long)
|
||||||
@org.jetbrains.annotations.NotNull public final List component1()
|
@org.jetbrains.annotations.NotNull public final List component1()
|
||||||
@ -1717,17 +1696,6 @@ public @interface net.corda.core.messaging.RPCReturnsObservables
|
|||||||
public final boolean isLegalIdentity(net.corda.core.identity.Party)
|
public final boolean isLegalIdentity(net.corda.core.identity.Party)
|
||||||
public String toString()
|
public String toString()
|
||||||
##
|
##
|
||||||
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NotaryInfo extends java.lang.Object
|
|
||||||
public <init>(net.corda.core.identity.Party, boolean)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component1()
|
|
||||||
public final boolean component2()
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NotaryInfo copy(net.corda.core.identity.Party, boolean)
|
|
||||||
public boolean equals(Object)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party getIdentity()
|
|
||||||
public final boolean getValidating()
|
|
||||||
public int hashCode()
|
|
||||||
public String toString()
|
|
||||||
##
|
|
||||||
@net.corda.core.DoNotImplement public interface net.corda.core.node.ServiceHub extends net.corda.core.node.ServicesForResolution
|
@net.corda.core.DoNotImplement public interface net.corda.core.node.ServiceHub extends net.corda.core.node.ServicesForResolution
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction)
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction)
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey)
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey)
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
package net.corda.core.node
|
|
||||||
|
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.serialization.CordaSerializable
|
|
||||||
import java.time.Duration
|
|
||||||
import java.time.Instant
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @property minimumPlatformVersion
|
|
||||||
* @property notaries
|
|
||||||
* @property eventHorizon
|
|
||||||
* @property maxMessageSize Maximum P2P message sent over the wire in bytes.
|
|
||||||
* @property maxTransactionSize Maximum permitted transaction size in bytes.
|
|
||||||
* @property modifiedTime
|
|
||||||
* @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set
|
|
||||||
* of parameters.
|
|
||||||
*/
|
|
||||||
// TODO Wire up the parameters
|
|
||||||
@CordaSerializable
|
|
||||||
data class NetworkParameters(
|
|
||||||
val minimumPlatformVersion: Int,
|
|
||||||
val notaries: List<NotaryInfo>,
|
|
||||||
val eventHorizon: Duration,
|
|
||||||
val maxMessageSize: Int,
|
|
||||||
val maxTransactionSize: Int,
|
|
||||||
val modifiedTime: Instant,
|
|
||||||
val epoch: Int
|
|
||||||
) {
|
|
||||||
init {
|
|
||||||
require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" }
|
|
||||||
require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" }
|
|
||||||
require(epoch > 0) { "epoch must be at least 1" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@CordaSerializable
|
|
||||||
data class NotaryInfo(val identity: Party, val validating: Boolean)
|
|
@ -0,0 +1,75 @@
|
|||||||
|
package net.corda.nodeapi.internal
|
||||||
|
|
||||||
|
import net.corda.core.crypto.DigitalSignature
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.crypto.verify
|
||||||
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.core.serialization.SerializedBytes
|
||||||
|
import net.corda.core.serialization.deserialize
|
||||||
|
import java.security.SignatureException
|
||||||
|
import java.security.cert.CertPathValidatorException
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
// TODO: Need more discussion on rather we should move this class out of internal.
|
||||||
|
/**
|
||||||
|
* Data class containing hash of [NetworkParameters] and network participant's [NodeInfo] hashes.
|
||||||
|
*/
|
||||||
|
@CordaSerializable
|
||||||
|
data class NetworkMap(val nodeInfoHashes: List<SecureHash>, val networkParameterHash: SecureHash)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property minimumPlatformVersion
|
||||||
|
* @property notaries
|
||||||
|
* @property eventHorizon
|
||||||
|
* @property maxMessageSize Maximum P2P message sent over the wire in bytes.
|
||||||
|
* @property maxTransactionSize Maximum permitted transaction size in bytes.
|
||||||
|
* @property modifiedTime
|
||||||
|
* @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set
|
||||||
|
* of parameters.
|
||||||
|
*/
|
||||||
|
// TODO Wire up the parameters
|
||||||
|
@CordaSerializable
|
||||||
|
data class NetworkParameters(
|
||||||
|
val minimumPlatformVersion: Int,
|
||||||
|
val notaries: List<NotaryInfo>,
|
||||||
|
val eventHorizon: Duration,
|
||||||
|
val maxMessageSize: Int,
|
||||||
|
val maxTransactionSize: Int,
|
||||||
|
val modifiedTime: Instant,
|
||||||
|
val epoch: Int
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" }
|
||||||
|
require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" }
|
||||||
|
require(epoch > 0) { "epoch must be at least 1" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CordaSerializable
|
||||||
|
data class NotaryInfo(val identity: Party, val validating: Boolean)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A serialized [NetworkMap] and its signature and certificate. Enforces signature validity in order to deserialize the data
|
||||||
|
* contained within.
|
||||||
|
*/
|
||||||
|
@CordaSerializable
|
||||||
|
class SignedNetworkMap(val raw: SerializedBytes<NetworkMap>, val sig: DigitalSignatureWithCert) {
|
||||||
|
/**
|
||||||
|
* Return the deserialized NetworkMap if the signature and certificate can be verified.
|
||||||
|
*
|
||||||
|
* @throws CertPathValidatorException if the certificate path is invalid.
|
||||||
|
* @throws SignatureException if the signature is invalid.
|
||||||
|
*/
|
||||||
|
@Throws(SignatureException::class)
|
||||||
|
fun verified(): NetworkMap {
|
||||||
|
sig.by.publicKey.verify(raw.bytes, sig)
|
||||||
|
return raw.deserialize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This class should reside in the [DigitalSignature] class.
|
||||||
|
/** A digital signature that identifies who the public key is owned by, and the certificate which provides prove of the identity */
|
||||||
|
class DigitalSignatureWithCert(val by: X509Certificate, val signatureBytes: ByteArray) : DigitalSignature(signatureBytes)
|
@ -6,6 +6,7 @@ import net.corda.core.utilities.NetworkHostAndPort
|
|||||||
import org.apache.activemq.artemis.api.core.SimpleString
|
import org.apache.activemq.artemis.api.core.SimpleString
|
||||||
import rx.Notification
|
import rx.Notification
|
||||||
import rx.exceptions.OnErrorNotImplementedException
|
import rx.exceptions.OnErrorNotImplementedException
|
||||||
|
import sun.security.x509.X509CertImpl
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,6 +59,9 @@ object DefaultWhitelist : SerializationWhitelist {
|
|||||||
java.util.LinkedHashMap::class.java,
|
java.util.LinkedHashMap::class.java,
|
||||||
BitSet::class.java,
|
BitSet::class.java,
|
||||||
OnErrorNotImplementedException::class.java,
|
OnErrorNotImplementedException::class.java,
|
||||||
StackTraceElement::class.java
|
StackTraceElement::class.java,
|
||||||
|
|
||||||
|
// Implementation of X509Certificate.
|
||||||
|
X509CertImpl::class.java
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ import org.objenesis.strategy.StdInstantiatorStrategy
|
|||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import sun.security.ec.ECPublicKeyImpl
|
import sun.security.ec.ECPublicKeyImpl
|
||||||
import sun.security.provider.certpath.X509CertPath
|
import sun.security.provider.certpath.X509CertPath
|
||||||
|
import sun.security.x509.X509CertImpl
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
@ -75,6 +76,7 @@ object DefaultKryoCustomizer {
|
|||||||
addDefaultSerializer(InputStream::class.java, InputStreamSerializer)
|
addDefaultSerializer(InputStream::class.java, InputStreamSerializer)
|
||||||
addDefaultSerializer(SerializeAsToken::class.java, SerializeAsTokenSerializer<SerializeAsToken>())
|
addDefaultSerializer(SerializeAsToken::class.java, SerializeAsTokenSerializer<SerializeAsToken>())
|
||||||
addDefaultSerializer(Logger::class.java, LoggerSerializer)
|
addDefaultSerializer(Logger::class.java, LoggerSerializer)
|
||||||
|
addDefaultSerializer(X509Certificate::class.java, X509CertificateSerializer)
|
||||||
|
|
||||||
// WARNING: reordering the registrations here will cause a change in the serialized form, since classes
|
// WARNING: reordering the registrations here will cause a change in the serialized form, since classes
|
||||||
// with custom serializers get written as registration ids. This will break backwards-compatibility.
|
// with custom serializers get written as registration ids. This will break backwards-compatibility.
|
||||||
@ -108,7 +110,6 @@ object DefaultKryoCustomizer {
|
|||||||
register(FileInputStream::class.java, InputStreamSerializer)
|
register(FileInputStream::class.java, InputStreamSerializer)
|
||||||
register(CertPath::class.java, CertPathSerializer)
|
register(CertPath::class.java, CertPathSerializer)
|
||||||
register(X509CertPath::class.java, CertPathSerializer)
|
register(X509CertPath::class.java, CertPathSerializer)
|
||||||
register(X509Certificate::class.java, X509CertificateSerializer)
|
|
||||||
register(BCECPrivateKey::class.java, PrivateKeySerializer)
|
register(BCECPrivateKey::class.java, PrivateKeySerializer)
|
||||||
register(BCECPublicKey::class.java, publicKeySerializer)
|
register(BCECPublicKey::class.java, publicKeySerializer)
|
||||||
register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer)
|
register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer)
|
||||||
|
@ -13,7 +13,6 @@ import net.corda.core.identity.CordaX500Name
|
|||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.deleteIfExists
|
import net.corda.core.internal.deleteIfExists
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.node.NotaryInfo
|
|
||||||
import net.corda.core.node.services.NotaryService
|
import net.corda.core.node.services.NotaryService
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
@ -26,6 +25,7 @@ import net.corda.node.services.config.NotaryConfig
|
|||||||
import net.corda.node.services.transactions.minClusterSize
|
import net.corda.node.services.transactions.minClusterSize
|
||||||
import net.corda.node.services.transactions.minCorrectReplicas
|
import net.corda.node.services.transactions.minCorrectReplicas
|
||||||
import net.corda.node.utilities.ServiceIdentityGenerator
|
import net.corda.node.utilities.ServiceIdentityGenerator
|
||||||
|
import net.corda.nodeapi.internal.NotaryInfo
|
||||||
import net.corda.testing.chooseIdentity
|
import net.corda.testing.chooseIdentity
|
||||||
import net.corda.testing.common.internal.NetworkParametersCopier
|
import net.corda.testing.common.internal.NetworkParametersCopier
|
||||||
import net.corda.testing.common.internal.testNetworkParameters
|
import net.corda.testing.common.internal.testNetworkParameters
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package net.corda.node.services.network
|
package net.corda.node.services.network
|
||||||
|
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.utilities.minutes
|
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.testing.ALICE
|
import net.corda.testing.ALICE
|
||||||
import net.corda.testing.BOB
|
import net.corda.testing.BOB
|
||||||
@ -18,7 +17,7 @@ class NetworkMapClientTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `nodes can see each other using the http network map`() {
|
fun `nodes can see each other using the http network map`() {
|
||||||
NetworkMapServer(1.minutes, portAllocation.nextHostAndPort()).use {
|
NetworkMapServer(1.seconds, portAllocation.nextHostAndPort()).use {
|
||||||
val (host, port) = it.start()
|
val (host, port) = it.start()
|
||||||
driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) {
|
driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) {
|
||||||
val alice = startNode(providedName = ALICE.name)
|
val alice = startNode(providedName = ALICE.name)
|
||||||
|
@ -44,15 +44,8 @@ import net.corda.node.services.events.ScheduledActivityObserver
|
|||||||
import net.corda.node.services.identity.PersistentIdentityService
|
import net.corda.node.services.identity.PersistentIdentityService
|
||||||
import net.corda.node.services.keys.PersistentKeyManagementService
|
import net.corda.node.services.keys.PersistentKeyManagementService
|
||||||
import net.corda.node.services.messaging.MessagingService
|
import net.corda.node.services.messaging.MessagingService
|
||||||
import net.corda.node.services.network.NetworkMapCacheImpl
|
|
||||||
import net.corda.node.services.network.NodeInfoWatcher
|
|
||||||
import net.corda.node.services.network.PersistentNetworkMapCache
|
|
||||||
import net.corda.node.services.persistence.*
|
|
||||||
import net.corda.node.services.network.*
|
import net.corda.node.services.network.*
|
||||||
import net.corda.node.services.persistence.DBCheckpointStorage
|
import net.corda.node.services.persistence.*
|
||||||
import net.corda.node.services.persistence.DBTransactionMappingStorage
|
|
||||||
import net.corda.node.services.persistence.DBTransactionStorage
|
|
||||||
import net.corda.node.services.persistence.NodeAttachmentService
|
|
||||||
import net.corda.node.services.schema.HibernateObserver
|
import net.corda.node.services.schema.HibernateObserver
|
||||||
import net.corda.node.services.schema.NodeSchemaService
|
import net.corda.node.services.schema.NodeSchemaService
|
||||||
import net.corda.node.services.statemachine.*
|
import net.corda.node.services.statemachine.*
|
||||||
@ -64,6 +57,7 @@ import net.corda.node.shell.InteractiveShell
|
|||||||
import net.corda.node.utilities.AffinityExecutor
|
import net.corda.node.utilities.AffinityExecutor
|
||||||
import net.corda.node.utilities.CordaPersistence
|
import net.corda.node.utilities.CordaPersistence
|
||||||
import net.corda.node.utilities.configureDatabase
|
import net.corda.node.utilities.configureDatabase
|
||||||
|
import net.corda.nodeapi.internal.NetworkParameters
|
||||||
import net.corda.nodeapi.internal.crypto.*
|
import net.corda.nodeapi.internal.crypto.*
|
||||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
@ -137,7 +131,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
protected lateinit var network: MessagingService
|
protected lateinit var network: MessagingService
|
||||||
protected val runOnStop = ArrayList<() -> Any?>()
|
protected val runOnStop = ArrayList<() -> Any?>()
|
||||||
protected val _nodeReadyFuture = openFuture<Unit>()
|
protected val _nodeReadyFuture = openFuture<Unit>()
|
||||||
protected val networkMapClient: NetworkMapClient? by lazy { configuration.compatibilityZoneURL?.let(::NetworkMapClient) }
|
protected val networkMapClient: NetworkMapClient? by lazy {
|
||||||
|
configuration.compatibilityZoneURL?.let {
|
||||||
|
NetworkMapClient(it, services.identityService.trustRoot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lateinit var userService: RPCUserService get
|
lateinit var userService: RPCUserService get
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.corda.node.services.network
|
package net.corda.node.services.network
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import com.google.common.util.concurrent.MoreExecutors
|
import com.google.common.util.concurrent.MoreExecutors
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.SignedData
|
import net.corda.core.crypto.SignedData
|
||||||
@ -13,6 +12,10 @@ import net.corda.core.utilities.minutes
|
|||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.node.services.api.NetworkMapCacheInternal
|
import net.corda.node.services.api.NetworkMapCacheInternal
|
||||||
import net.corda.node.utilities.NamedThreadFactory
|
import net.corda.node.utilities.NamedThreadFactory
|
||||||
|
import net.corda.nodeapi.internal.NetworkMap
|
||||||
|
import net.corda.nodeapi.internal.NetworkParameters
|
||||||
|
import net.corda.nodeapi.internal.SignedNetworkMap
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
import okhttp3.CacheControl
|
import okhttp3.CacheControl
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
@ -20,11 +23,12 @@ import java.io.BufferedReader
|
|||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class NetworkMapClient(compatibilityZoneURL: URL) {
|
class NetworkMapClient(compatibilityZoneURL: URL, private val trustedRoot: X509Certificate) {
|
||||||
private val networkMapUrl = URL("$compatibilityZoneURL/network-map")
|
private val networkMapUrl = URL("$compatibilityZoneURL/network-map")
|
||||||
|
|
||||||
fun publish(signedNodeInfo: SignedData<NodeInfo>) {
|
fun publish(signedNodeInfo: SignedData<NodeInfo>) {
|
||||||
@ -42,14 +46,30 @@ class NetworkMapClient(compatibilityZoneURL: URL) {
|
|||||||
|
|
||||||
fun getNetworkMap(): NetworkMapResponse {
|
fun getNetworkMap(): NetworkMapResponse {
|
||||||
val conn = networkMapUrl.openHttpConnection()
|
val conn = networkMapUrl.openHttpConnection()
|
||||||
val response = conn.inputStream.bufferedReader().use(BufferedReader::readLine)
|
val signedNetworkMap = conn.inputStream.use { it.readBytes() }.deserialize<SignedNetworkMap>()
|
||||||
val networkMap = ObjectMapper().readValue(response, List::class.java).map { SecureHash.parse(it.toString()) }
|
val networkMap = signedNetworkMap.verified()
|
||||||
|
// Assume network map cert is issued by the root.
|
||||||
|
X509Utilities.validateCertificateChain(trustedRoot, signedNetworkMap.sig.by, trustedRoot)
|
||||||
val timeout = CacheControl.parse(Headers.of(conn.headerFields.filterKeys { it != null }.mapValues { it.value.first() })).maxAgeSeconds().seconds
|
val timeout = CacheControl.parse(Headers.of(conn.headerFields.filterKeys { it != null }.mapValues { it.value.first() })).maxAgeSeconds().seconds
|
||||||
return NetworkMapResponse(networkMap, timeout)
|
return NetworkMapResponse(networkMap, timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo? {
|
fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo? {
|
||||||
val conn = URL("$networkMapUrl/$nodeInfoHash").openHttpConnection()
|
val conn = URL("$networkMapUrl/node-info/$nodeInfoHash").openHttpConnection()
|
||||||
|
return if (conn.responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
val signedNodeInfo = conn.inputStream.use { it.readBytes() }.deserialize<SignedData<NodeInfo>>()
|
||||||
|
val nodeInfo = signedNodeInfo.verified()
|
||||||
|
// Verify node info is signed by node identity
|
||||||
|
// TODO : Validate multiple signatures when NodeInfo supports multiple identities.
|
||||||
|
require(nodeInfo.legalIdentities.any { it.owningKey == signedNodeInfo.sig.by }) { "NodeInfo must be signed by the node owning key." }
|
||||||
|
nodeInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNetworkParameter(networkParameterHash: SecureHash): NetworkParameters? {
|
||||||
|
val conn = URL("$networkMapUrl/network-parameter/$networkParameterHash").openHttpConnection()
|
||||||
return if (conn.responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
|
return if (conn.responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
@ -63,7 +83,7 @@ class NetworkMapClient(compatibilityZoneURL: URL) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class NetworkMapResponse(val networkMap: List<SecureHash>, val cacheMaxAge: Duration)
|
data class NetworkMapResponse(val networkMap: NetworkMap, val cacheMaxAge: Duration)
|
||||||
|
|
||||||
class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
||||||
private val fileWatcher: NodeInfoWatcher,
|
private val fileWatcher: NodeInfoWatcher,
|
||||||
@ -107,21 +127,28 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
|||||||
val nextScheduleDelay = try {
|
val nextScheduleDelay = try {
|
||||||
val (networkMap, cacheTimeout) = networkMapClient.getNetworkMap()
|
val (networkMap, cacheTimeout) = networkMapClient.getNetworkMap()
|
||||||
val currentNodeHashes = networkMapCache.allNodeHashes
|
val currentNodeHashes = networkMapCache.allNodeHashes
|
||||||
(networkMap - currentNodeHashes).mapNotNull {
|
val hashesFromNetworkMap = networkMap.nodeInfoHashes
|
||||||
|
(hashesFromNetworkMap - currentNodeHashes).mapNotNull {
|
||||||
// Download new node info from network map
|
// Download new node info from network map
|
||||||
|
try {
|
||||||
networkMapClient.getNodeInfo(it)
|
networkMapClient.getNodeInfo(it)
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
// Failure to retrieve one node info shouldn't stop the whole update, log and return null instead.
|
||||||
|
logger.warn("Error encountered when downloading node info '$it', skipping...", t)
|
||||||
|
null
|
||||||
|
}
|
||||||
}.forEach {
|
}.forEach {
|
||||||
// Add new node info to the network map cache, these could be new node info or modification of node info for existing nodes.
|
// Add new node info to the network map cache, these could be new node info or modification of node info for existing nodes.
|
||||||
networkMapCache.addNode(it)
|
networkMapCache.addNode(it)
|
||||||
}
|
}
|
||||||
// Remove node info from network map.
|
// Remove node info from network map.
|
||||||
(currentNodeHashes - networkMap - fileWatcher.processedNodeInfoHashes)
|
(currentNodeHashes - hashesFromNetworkMap - fileWatcher.processedNodeInfoHashes)
|
||||||
.mapNotNull(networkMapCache::getNodeByHash)
|
.mapNotNull(networkMapCache::getNodeByHash)
|
||||||
.forEach(networkMapCache::removeNode)
|
.forEach(networkMapCache::removeNode)
|
||||||
|
// TODO: Check NetworkParameter.
|
||||||
cacheTimeout
|
cacheTimeout
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
logger.warn("Error encountered while updating network map, will retry in $retryInterval", t)
|
logger.warn("Error encountered while updating network map, will retry in ${retryInterval.seconds} seconds", t)
|
||||||
retryInterval
|
retryInterval
|
||||||
}
|
}
|
||||||
// Schedule the next update.
|
// Schedule the next update.
|
||||||
@ -137,7 +164,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
|||||||
try {
|
try {
|
||||||
networkMapClient.publish(signedNodeInfo)
|
networkMapClient.publish(signedNodeInfo)
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
logger.warn("Error encountered while publishing node info, will retry in $retryInterval.", t)
|
logger.warn("Error encountered while publishing node info, will retry in ${retryInterval.seconds} seconds.", t)
|
||||||
// TODO: Exponential backoff?
|
// TODO: Exponential backoff?
|
||||||
executor.schedule(this, retryInterval.toMillis(), TimeUnit.MILLISECONDS)
|
executor.schedule(this, retryInterval.toMillis(), TimeUnit.MILLISECONDS)
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import net.corda.core.internal.concurrent.openFuture
|
|||||||
import net.corda.core.internal.schemas.NodeInfoSchemaV1
|
import net.corda.core.internal.schemas.NodeInfoSchemaV1
|
||||||
import net.corda.core.messaging.DataFeed
|
import net.corda.core.messaging.DataFeed
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.NotaryInfo
|
|
||||||
import net.corda.core.node.services.IdentityService
|
import net.corda.core.node.services.IdentityService
|
||||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
import net.corda.core.node.services.NetworkMapCache.MapChange
|
||||||
import net.corda.core.node.services.PartyInfo
|
import net.corda.core.node.services.PartyInfo
|
||||||
@ -25,6 +24,7 @@ import net.corda.node.services.api.NetworkMapCacheInternal
|
|||||||
import net.corda.node.utilities.CordaPersistence
|
import net.corda.node.utilities.CordaPersistence
|
||||||
import net.corda.node.utilities.bufferUntilDatabaseCommit
|
import net.corda.node.utilities.bufferUntilDatabaseCommit
|
||||||
import net.corda.node.utilities.wrapWithDatabaseTransaction
|
import net.corda.node.utilities.wrapWithDatabaseTransaction
|
||||||
|
import net.corda.nodeapi.internal.NotaryInfo
|
||||||
import org.hibernate.Session
|
import org.hibernate.Session
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package net.corda.node.services.network
|
package net.corda.node.services.network
|
||||||
|
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.sha256
|
import net.corda.core.crypto.sha256
|
||||||
|
import net.corda.core.internal.cert
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.node.services.network.TestNodeInfoFactory.createNodeInfo
|
import net.corda.node.services.network.TestNodeInfoFactory.createNodeInfo
|
||||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
import net.corda.testing.DEV_CA
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
import net.corda.testing.DEV_TRUST_ROOT
|
||||||
|
import net.corda.testing.ROOT_CA
|
||||||
import net.corda.testing.SerializationEnvironmentRule
|
import net.corda.testing.SerializationEnvironmentRule
|
||||||
import net.corda.testing.driver.PortAllocation
|
import net.corda.testing.driver.PortAllocation
|
||||||
import net.corda.testing.node.network.NetworkMapServer
|
import net.corda.testing.node.network.NetworkMapServer
|
||||||
@ -16,6 +19,7 @@ import org.junit.Rule
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
|
||||||
class NetworkMapClientTest {
|
class NetworkMapClientTest {
|
||||||
@Rule
|
@Rule
|
||||||
@ -32,7 +36,7 @@ class NetworkMapClientTest {
|
|||||||
fun setUp() {
|
fun setUp() {
|
||||||
server = NetworkMapServer(cacheTimeout, PortAllocation.Incremental(10000).nextHostAndPort())
|
server = NetworkMapServer(cacheTimeout, PortAllocation.Incremental(10000).nextHostAndPort())
|
||||||
val hostAndPort = server.start()
|
val hostAndPort = server.start()
|
||||||
networkMapClient = NetworkMapClient(URL("http://${hostAndPort.host}:${hostAndPort.port}"))
|
networkMapClient = NetworkMapClient(URL("http://${hostAndPort.host}:${hostAndPort.port}"), DEV_TRUST_ROOT.cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -50,7 +54,7 @@ class NetworkMapClientTest {
|
|||||||
|
|
||||||
val nodeInfoHash = nodeInfo.serialize().sha256()
|
val nodeInfoHash = nodeInfo.serialize().sha256()
|
||||||
|
|
||||||
assertThat(networkMapClient.getNetworkMap().networkMap).containsExactly(nodeInfoHash)
|
assertThat(networkMapClient.getNetworkMap().networkMap.nodeInfoHashes).containsExactly(nodeInfoHash)
|
||||||
assertEquals(nodeInfo, networkMapClient.getNodeInfo(nodeInfoHash))
|
assertEquals(nodeInfo, networkMapClient.getNodeInfo(nodeInfoHash))
|
||||||
|
|
||||||
val signedNodeInfo2 = createNodeInfo("Test2")
|
val signedNodeInfo2 = createNodeInfo("Test2")
|
||||||
@ -58,11 +62,20 @@ class NetworkMapClientTest {
|
|||||||
networkMapClient.publish(signedNodeInfo2)
|
networkMapClient.publish(signedNodeInfo2)
|
||||||
|
|
||||||
val nodeInfoHash2 = nodeInfo2.serialize().sha256()
|
val nodeInfoHash2 = nodeInfo2.serialize().sha256()
|
||||||
assertThat(networkMapClient.getNetworkMap().networkMap).containsExactly(nodeInfoHash, nodeInfoHash2)
|
assertThat(networkMapClient.getNetworkMap().networkMap.nodeInfoHashes).containsExactly(nodeInfoHash, nodeInfoHash2)
|
||||||
assertEquals(cacheTimeout, networkMapClient.getNetworkMap().cacheMaxAge)
|
assertEquals(cacheTimeout, networkMapClient.getNetworkMap().cacheMaxAge)
|
||||||
assertEquals(nodeInfo2, networkMapClient.getNodeInfo(nodeInfoHash2))
|
assertEquals(nodeInfo2, networkMapClient.getNodeInfo(nodeInfoHash2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `download NetworkParameter correctly`() {
|
||||||
|
// The test server returns same network parameter for any hash.
|
||||||
|
val networkParameter = networkMapClient.getNetworkParameter(SecureHash.randomSHA256())
|
||||||
|
assertNotNull(networkParameter)
|
||||||
|
assertEquals(NetworkMapServer.stubNetworkParameter, networkParameter)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `get hostname string from http response correctly`() {
|
fun `get hostname string from http response correctly`() {
|
||||||
assertEquals("test.host.name", networkMapClient.myPublicHostname())
|
assertEquals("test.host.name", networkMapClient.myPublicHostname())
|
||||||
|
@ -13,6 +13,7 @@ import net.corda.core.crypto.SignedData
|
|||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
|
import net.corda.nodeapi.internal.NetworkMap
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
@ -95,7 +96,7 @@ class NetworkMapUpdaterTest {
|
|||||||
val signedNodeInfo: SignedData<NodeInfo> = uncheckedCast(it.arguments.first())
|
val signedNodeInfo: SignedData<NodeInfo> = uncheckedCast(it.arguments.first())
|
||||||
nodeInfoMap.put(signedNodeInfo.verified().serialize().hash, signedNodeInfo)
|
nodeInfoMap.put(signedNodeInfo.verified().serialize().hash, signedNodeInfo)
|
||||||
}
|
}
|
||||||
on { getNetworkMap() }.then { NetworkMapResponse(nodeInfoMap.keys.toList(), 100.millis) }
|
on { getNetworkMap() }.then { NetworkMapResponse(NetworkMap(nodeInfoMap.keys.toList(), SecureHash.randomSHA256()), 100.millis) }
|
||||||
on { getNodeInfo(any()) }.then { nodeInfoMap[it.arguments.first()]?.verified() }
|
on { getNodeInfo(any()) }.then { nodeInfoMap[it.arguments.first()]?.verified() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +150,7 @@ class NetworkMapUpdaterTest {
|
|||||||
val signedNodeInfo: SignedData<NodeInfo> = uncheckedCast(it.arguments.first())
|
val signedNodeInfo: SignedData<NodeInfo> = uncheckedCast(it.arguments.first())
|
||||||
nodeInfoMap.put(signedNodeInfo.verified().serialize().hash, signedNodeInfo)
|
nodeInfoMap.put(signedNodeInfo.verified().serialize().hash, signedNodeInfo)
|
||||||
}
|
}
|
||||||
on { getNetworkMap() }.then { NetworkMapResponse(nodeInfoMap.keys.toList(), 100.millis) }
|
on { getNetworkMap() }.then { NetworkMapResponse(NetworkMap(nodeInfoMap.keys.toList(), SecureHash.randomSHA256()), 100.millis) }
|
||||||
on { getNodeInfo(any()) }.then { nodeInfoMap[it.arguments.first()]?.verified() }
|
on { getNodeInfo(any()) }.then { nodeInfoMap[it.arguments.first()]?.verified() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import net.corda.core.internal.list
|
|||||||
import net.corda.core.internal.readLines
|
import net.corda.core.internal.readLines
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.core.utilities.minutes
|
import net.corda.core.utilities.minutes
|
||||||
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.node.internal.NodeStartup
|
import net.corda.node.internal.NodeStartup
|
||||||
import net.corda.testing.DUMMY_BANK_A
|
import net.corda.testing.DUMMY_BANK_A
|
||||||
import net.corda.testing.DUMMY_NOTARY
|
import net.corda.testing.DUMMY_NOTARY
|
||||||
@ -63,7 +64,7 @@ class DriverTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `node registration`() {
|
fun `node registration`() {
|
||||||
val handler = RegistrationHandler()
|
val handler = RegistrationHandler()
|
||||||
NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), handler).use {
|
NetworkMapServer(1.seconds, portAllocation.nextHostAndPort(), handler).use {
|
||||||
val (host, port) = it.start()
|
val (host, port) = it.start()
|
||||||
driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) {
|
driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) {
|
||||||
// Wait for the node to have started.
|
// Wait for the node to have started.
|
||||||
|
@ -17,7 +17,6 @@ import net.corda.core.internal.*
|
|||||||
import net.corda.core.internal.concurrent.*
|
import net.corda.core.internal.concurrent.*
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.NotaryInfo
|
|
||||||
import net.corda.core.node.services.NetworkMapCache
|
import net.corda.core.node.services.NetworkMapCache
|
||||||
import net.corda.core.node.services.NotaryService
|
import net.corda.core.node.services.NotaryService
|
||||||
import net.corda.core.toFuture
|
import net.corda.core.toFuture
|
||||||
@ -34,6 +33,7 @@ import net.corda.node.utilities.registration.NetworkRegistrationHelper
|
|||||||
import net.corda.nodeapi.NodeInfoFilesCopier
|
import net.corda.nodeapi.NodeInfoFilesCopier
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import net.corda.nodeapi.config.toConfig
|
import net.corda.nodeapi.config.toConfig
|
||||||
|
import net.corda.nodeapi.internal.NotaryInfo
|
||||||
import net.corda.nodeapi.internal.addShutdownHook
|
import net.corda.nodeapi.internal.addShutdownHook
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
import net.corda.testing.common.internal.NetworkParametersCopier
|
import net.corda.testing.common.internal.NetworkParametersCopier
|
||||||
|
@ -17,7 +17,6 @@ import net.corda.core.messaging.MessageRecipients
|
|||||||
import net.corda.core.messaging.RPCOps
|
import net.corda.core.messaging.RPCOps
|
||||||
import net.corda.core.messaging.SingleMessageRecipient
|
import net.corda.core.messaging.SingleMessageRecipient
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.NotaryInfo
|
|
||||||
import net.corda.core.node.services.IdentityService
|
import net.corda.core.node.services.IdentityService
|
||||||
import net.corda.core.node.services.KeyManagementService
|
import net.corda.core.node.services.KeyManagementService
|
||||||
import net.corda.core.serialization.SerializationWhitelist
|
import net.corda.core.serialization.SerializationWhitelist
|
||||||
@ -39,12 +38,13 @@ import net.corda.node.utilities.AffinityExecutor
|
|||||||
import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
|
import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
|
||||||
import net.corda.node.utilities.CordaPersistence
|
import net.corda.node.utilities.CordaPersistence
|
||||||
import net.corda.node.utilities.ServiceIdentityGenerator
|
import net.corda.node.utilities.ServiceIdentityGenerator
|
||||||
|
import net.corda.nodeapi.internal.NotaryInfo
|
||||||
import net.corda.testing.DUMMY_NOTARY
|
import net.corda.testing.DUMMY_NOTARY
|
||||||
import net.corda.testing.common.internal.NetworkParametersCopier
|
import net.corda.testing.common.internal.NetworkParametersCopier
|
||||||
import net.corda.testing.common.internal.testNetworkParameters
|
import net.corda.testing.common.internal.testNetworkParameters
|
||||||
import net.corda.testing.setGlobalSerialization
|
|
||||||
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
|
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
|
||||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||||
|
import net.corda.testing.setGlobalSerialization
|
||||||
import net.corda.testing.testNodeConfiguration
|
import net.corda.testing.testNodeConfiguration
|
||||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||||
import org.apache.sshd.common.util.security.SecurityUtils
|
import org.apache.sshd.common.util.security.SecurityUtils
|
||||||
@ -159,7 +159,8 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
|
|||||||
* Returns the single notary node on the network. Throws if there are none or more than one.
|
* Returns the single notary node on the network. Throws if there are none or more than one.
|
||||||
* @see notaryNodes
|
* @see notaryNodes
|
||||||
*/
|
*/
|
||||||
val defaultNotaryNode: StartedNode<MockNode> get() {
|
val defaultNotaryNode: StartedNode<MockNode>
|
||||||
|
get() {
|
||||||
return when (notaryNodes.size) {
|
return when (notaryNodes.size) {
|
||||||
0 -> throw IllegalStateException("There are no notaries defined on the network")
|
0 -> throw IllegalStateException("There are no notaries defined on the network")
|
||||||
1 -> notaryNodes[0]
|
1 -> notaryNodes[0]
|
||||||
@ -171,7 +172,8 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
|
|||||||
* Return the identity of the default notary node.
|
* Return the identity of the default notary node.
|
||||||
* @see defaultNotaryNode
|
* @see defaultNotaryNode
|
||||||
*/
|
*/
|
||||||
val defaultNotaryIdentity: Party get() {
|
val defaultNotaryIdentity: Party
|
||||||
|
get() {
|
||||||
return defaultNotaryNode.info.legalIdentities.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities")
|
return defaultNotaryNode.info.legalIdentities.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +181,8 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
|
|||||||
* Return the identity of the default notary node.
|
* Return the identity of the default notary node.
|
||||||
* @see defaultNotaryNode
|
* @see defaultNotaryNode
|
||||||
*/
|
*/
|
||||||
val defaultNotaryIdentityAndCert: PartyAndCertificate get() {
|
val defaultNotaryIdentityAndCert: PartyAndCertificate
|
||||||
|
get() {
|
||||||
return defaultNotaryNode.info.legalIdentitiesAndCerts.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities")
|
return defaultNotaryNode.info.legalIdentitiesAndCerts.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,25 @@
|
|||||||
package net.corda.testing.node.network
|
package net.corda.testing.node.network
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.SignedData
|
import net.corda.core.crypto.SignedData
|
||||||
import net.corda.core.crypto.sha256
|
import net.corda.core.crypto.sha256
|
||||||
|
import net.corda.core.internal.cert
|
||||||
|
import net.corda.core.internal.toX509CertHolder
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
|
import net.corda.core.utilities.hours
|
||||||
|
import net.corda.nodeapi.internal.DigitalSignatureWithCert
|
||||||
|
import net.corda.nodeapi.internal.NetworkMap
|
||||||
|
import net.corda.nodeapi.internal.NetworkParameters
|
||||||
|
import net.corda.nodeapi.internal.SignedNetworkMap
|
||||||
|
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||||
|
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
|
import net.corda.testing.ROOT_CA
|
||||||
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
import org.eclipse.jetty.server.Server
|
import org.eclipse.jetty.server.Server
|
||||||
import org.eclipse.jetty.server.ServerConnector
|
import org.eclipse.jetty.server.ServerConnector
|
||||||
import org.eclipse.jetty.server.handler.HandlerCollection
|
import org.eclipse.jetty.server.handler.HandlerCollection
|
||||||
@ -19,15 +31,34 @@ import java.io.Closeable
|
|||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
import java.time.Instant
|
||||||
import javax.ws.rs.*
|
import javax.ws.rs.*
|
||||||
import javax.ws.rs.core.MediaType
|
import javax.ws.rs.core.MediaType
|
||||||
import javax.ws.rs.core.Response
|
import javax.ws.rs.core.Response
|
||||||
import javax.ws.rs.core.Response.ok
|
import javax.ws.rs.core.Response.ok
|
||||||
|
|
||||||
class NetworkMapServer(cacheTimeout: Duration, hostAndPort: NetworkHostAndPort, vararg additionalServices: Any) : Closeable {
|
class NetworkMapServer(cacheTimeout: Duration,
|
||||||
private val server: Server
|
hostAndPort: NetworkHostAndPort,
|
||||||
|
vararg additionalServices: Any) : Closeable {
|
||||||
|
companion object {
|
||||||
|
val stubNetworkParameter = NetworkParameters(1, emptyList(), 1.hours, 10, 10, Instant.now(), 10)
|
||||||
|
|
||||||
private val service = InMemoryNetworkMapService(cacheTimeout)
|
private fun networkMapKeyAndCert(rootCAKeyAndCert: CertificateAndKeyPair): CertificateAndKeyPair {
|
||||||
|
val networkMapKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||||
|
val networkMapCert = X509Utilities.createCertificate(
|
||||||
|
CertificateType.IDENTITY,
|
||||||
|
rootCAKeyAndCert.certificate,
|
||||||
|
rootCAKeyAndCert.keyPair,
|
||||||
|
X500Name("CN=Corda Network Map,L=London"),
|
||||||
|
networkMapKey.public).cert
|
||||||
|
return CertificateAndKeyPair(networkMapCert.toX509CertHolder(), networkMapKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val server: Server
|
||||||
|
// Default to ROOT_CA for testing.
|
||||||
|
// TODO: make this configurable?
|
||||||
|
private val service = InMemoryNetworkMapService(cacheTimeout, networkMapKeyAndCert(ROOT_CA))
|
||||||
|
|
||||||
init {
|
init {
|
||||||
server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply {
|
server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply {
|
||||||
@ -68,8 +99,9 @@ class NetworkMapServer(cacheTimeout: Duration, hostAndPort: NetworkHostAndPort,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Path("network-map")
|
@Path("network-map")
|
||||||
class InMemoryNetworkMapService(private val cacheTimeout: Duration) {
|
class InMemoryNetworkMapService(private val cacheTimeout: Duration, private val networkMapKeyAndCert: CertificateAndKeyPair) {
|
||||||
private val nodeInfoMap = mutableMapOf<SecureHash, NodeInfo>()
|
private val nodeInfoMap = mutableMapOf<SecureHash, SignedData<NodeInfo>>()
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("publish")
|
@Path("publish")
|
||||||
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
|
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
@ -77,39 +109,48 @@ class NetworkMapServer(cacheTimeout: Duration, hostAndPort: NetworkHostAndPort,
|
|||||||
val registrationData = input.readBytes().deserialize<SignedData<NodeInfo>>()
|
val registrationData = input.readBytes().deserialize<SignedData<NodeInfo>>()
|
||||||
val nodeInfo = registrationData.verified()
|
val nodeInfo = registrationData.verified()
|
||||||
val nodeInfoHash = nodeInfo.serialize().sha256()
|
val nodeInfoHash = nodeInfo.serialize().sha256()
|
||||||
nodeInfoMap.put(nodeInfoHash, nodeInfo)
|
nodeInfoMap.put(nodeInfoHash, registrationData)
|
||||||
return ok().build()
|
return ok().build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
fun getNetworkMap(): Response {
|
fun getNetworkMap(): Response {
|
||||||
return Response.ok(ObjectMapper().writeValueAsString(nodeInfoMap.keys.map { it.toString() }))
|
val networkMap = NetworkMap(nodeInfoMap.keys.map { it }, SecureHash.randomSHA256())
|
||||||
.header("Cache-Control", "max-age=${cacheTimeout.seconds}")
|
val serializedNetworkMap = networkMap.serialize()
|
||||||
.build()
|
val signature = Crypto.doSign(networkMapKeyAndCert.keyPair.private, serializedNetworkMap.bytes)
|
||||||
|
val signedNetworkMap = SignedNetworkMap(networkMap.serialize(), DigitalSignatureWithCert(networkMapKeyAndCert.certificate.cert, signature))
|
||||||
|
return Response.ok(signedNetworkMap.serialize().bytes).header("Cache-Control", "max-age=${cacheTimeout.seconds}").build()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove nodeInfo for testing.
|
||||||
|
fun removeNodeInfo(nodeInfo: NodeInfo) {
|
||||||
|
nodeInfoMap.remove(nodeInfo.serialize().hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("{var}")
|
@Path("node-info/{var}")
|
||||||
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
fun getNodeInfo(@PathParam("var") nodeInfoHash: String): Response {
|
fun getNodeInfo(@PathParam("var") nodeInfoHash: String): Response {
|
||||||
val nodeInfo = nodeInfoMap[SecureHash.parse(nodeInfoHash)]
|
val signedNodeInfo = nodeInfoMap[SecureHash.parse(nodeInfoHash)]
|
||||||
return if (nodeInfo != null) {
|
return if (signedNodeInfo != null) {
|
||||||
Response.ok(nodeInfo.serialize().bytes)
|
Response.ok(signedNodeInfo.serialize().bytes)
|
||||||
} else {
|
} else {
|
||||||
Response.status(Response.Status.NOT_FOUND)
|
Response.status(Response.Status.NOT_FOUND)
|
||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("network-parameter/{var}")
|
||||||
|
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
|
fun getNetworkParameter(@PathParam("var") networkParameterHash: String): Response {
|
||||||
|
return Response.ok(stubNetworkParameter.serialize().bytes).build()
|
||||||
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("my-hostname")
|
@Path("my-hostname")
|
||||||
fun getHostName(): Response {
|
fun getHostName(): Response {
|
||||||
return Response.ok("test.host.name").build()
|
return Response.ok("test.host.name").build()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove nodeInfo for testing.
|
|
||||||
fun removeNodeInfo(nodeInfo: NodeInfo) {
|
|
||||||
nodeInfoMap.remove(nodeInfo.serialize().hash)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ apply plugin: 'com.jfrog.artifactory'
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
|
compile project(':node-api')
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
|
@ -5,8 +5,8 @@ import net.corda.core.crypto.entropyToKeyPair
|
|||||||
import net.corda.core.crypto.sign
|
import net.corda.core.crypto.sign
|
||||||
import net.corda.core.internal.copyTo
|
import net.corda.core.internal.copyTo
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.node.NetworkParameters
|
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
|
import net.corda.nodeapi.internal.NetworkParameters
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.nio.file.FileAlreadyExistsException
|
import java.nio.file.FileAlreadyExistsException
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package net.corda.testing.common.internal
|
package net.corda.testing.common.internal
|
||||||
|
|
||||||
import net.corda.core.node.NetworkParameters
|
|
||||||
import net.corda.core.node.NotaryInfo
|
|
||||||
import net.corda.core.utilities.days
|
import net.corda.core.utilities.days
|
||||||
|
import net.corda.nodeapi.internal.NetworkParameters
|
||||||
|
import net.corda.nodeapi.internal.NotaryInfo
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
fun testNetworkParameters(notaries: List<NotaryInfo>): NetworkParameters {
|
fun testNetworkParameters(notaries: List<NotaryInfo>): NetworkParameters {
|
||||||
|
@ -70,6 +70,12 @@ val DEV_CA: CertificateAndKeyPair by lazy {
|
|||||||
val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass")
|
val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||||
caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
|
caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val ROOT_CA: CertificateAndKeyPair by lazy {
|
||||||
|
// TODO: Should be identity scheme
|
||||||
|
val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||||
|
caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_ROOT_CA, "cordacadevkeypass")
|
||||||
|
}
|
||||||
val DEV_TRUST_ROOT: X509CertificateHolder by lazy {
|
val DEV_TRUST_ROOT: X509CertificateHolder by lazy {
|
||||||
// TODO: Should be identity scheme
|
// TODO: Should be identity scheme
|
||||||
val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass")
|
val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||||
|
Reference in New Issue
Block a user