Introducing network parameters.

network-parameters file read in by the node at startup, of which only the list of notaries is used. For now, the driver and MockNetwork have been updated to require notaries to be started first. This is so that the same set of network parameters can be defined for all the nodes.

CN in the legal name is not longer disallowed since it's no longer reserved for distributed notary names.

Single-node notaries now only have one identity, their main identity. Nodes part of a cluster continue to have two.

(Based off Kasia's work)
This commit is contained in:
Shams Asari 2017-11-02 20:45:27 +00:00
parent 5490465750
commit d04e48740b
57 changed files with 664 additions and 529 deletions

View File

@ -11,6 +11,7 @@ import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.bufferUntilSubscribed
import net.corda.core.internal.concurrent.transpose
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.StateMachineTransactionMapping
import net.corda.core.messaging.StateMachineUpdate
@ -35,20 +36,20 @@ import org.junit.Test
import rx.Observable
class NodeMonitorModelTest {
lateinit var aliceNode: NodeInfo
lateinit var bobNode: NodeInfo
lateinit var notaryParty: Party
private lateinit var aliceNode: NodeInfo
private lateinit var bobNode: NodeInfo
private lateinit var notaryParty: Party
lateinit var rpc: CordaRPCOps
lateinit var rpcBob: CordaRPCOps
lateinit var stateMachineTransactionMapping: Observable<StateMachineTransactionMapping>
lateinit var stateMachineUpdates: Observable<StateMachineUpdate>
lateinit var stateMachineUpdatesBob: Observable<StateMachineUpdate>
lateinit var progressTracking: Observable<ProgressTrackingEvent>
lateinit var transactions: Observable<SignedTransaction>
lateinit var vaultUpdates: Observable<Vault.Update<ContractState>>
lateinit var networkMapUpdates: Observable<NetworkMapCache.MapChange>
lateinit var newNode: (CordaX500Name) -> NodeInfo
private lateinit var rpc: CordaRPCOps
private lateinit var rpcBob: CordaRPCOps
private lateinit var stateMachineTransactionMapping: Observable<StateMachineTransactionMapping>
private lateinit var stateMachineUpdates: Observable<StateMachineUpdate>
private lateinit var stateMachineUpdatesBob: Observable<StateMachineUpdate>
private lateinit var progressTracking: Observable<ProgressTrackingEvent>
private lateinit var transactions: Observable<SignedTransaction>
private lateinit var vaultUpdates: Observable<Vault.Update<ContractState>>
private lateinit var networkMapUpdates: Observable<NetworkMapCache.MapChange>
private lateinit var newNode: (CordaX500Name) -> NodeInfo
private fun setup(runTest: () -> Unit) = driver(extraCordappPackagesToScan = listOf("net.corda.finance")) {
val cashUser = User("user1", "test", permissions = setOf(
startFlow<CashIssueFlow>(),
@ -62,9 +63,10 @@ class NodeMonitorModelTest {
invokeRpc(CordaRPCOps::stateMachinesFeed),
invokeRpc(CordaRPCOps::networkMapFeed))
)
val aliceNodeFuture = startNode(providedName = ALICE.name, rpcUsers = listOf(cashUser))
val notaryHandle = startNotaryNode(DUMMY_NOTARY.name, validating = false).getOrThrow()
val aliceNodeHandle = aliceNodeFuture.getOrThrow()
val (notaryHandle, aliceNodeHandle) = listOf(
startNotaryNode(DUMMY_NOTARY.name, validating = false),
startNode(providedName = ALICE.name, rpcUsers = listOf(cashUser))
).transpose().getOrThrow()
aliceNode = aliceNodeHandle.nodeInfo
newNode = { nodeName -> startNode(providedName = nodeName).getOrThrow().nodeInfo }
val monitor = NodeMonitorModel()
@ -77,7 +79,7 @@ class NodeMonitorModelTest {
monitor.register(aliceNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password)
rpc = monitor.proxyObservable.value!!
notaryParty = notaryHandle.nodeInfo.legalIdentities[1]
notaryParty = notaryHandle.nodeInfo.legalIdentities[0]
val bobNodeHandle = startNode(providedName = BOB.name, rpcUsers = listOf(cashUser)).getOrThrow()
bobNode = bobNodeHandle.nodeInfo

View File

@ -5,6 +5,7 @@ import com.google.common.cache.CacheLoader
import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import net.corda.client.jfx.utils.ChosenList
import net.corda.client.jfx.utils.filterNotNull
import net.corda.client.jfx.utils.fold
import net.corda.client.jfx.utils.map
@ -12,7 +13,6 @@ import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache.MapChange
import net.corda.nodeapi.internal.ServiceType
import java.security.PublicKey
class NetworkIdentityModel {
@ -36,16 +36,10 @@ class NetworkIdentityModel {
.build<PublicKey, ObservableValue<NodeInfo?>>(CacheLoader.from { publicKey ->
publicKey?.let { rpcProxy.map { it?.nodeInfoFromParty(AnonymousParty(publicKey)) } }
})
val notaries: ObservableList<Party> = networkIdentities.map {
it.legalIdentitiesAndCerts.find { it.name.commonName?.let { ServiceType.parse(it).isNotary() } == true }
}.map { it?.party }.filterNotNull()
val notaries = ChosenList(rpcProxy.map { FXCollections.observableList(it?.notaryIdentities() ?: emptyList()) })
val notaryNodes: ObservableList<NodeInfo> = notaries.map { rpcProxy.value?.nodeInfoFromParty(it) }.filterNotNull()
val parties: ObservableList<NodeInfo> = networkIdentities
.filtered { it.legalIdentities.all { it !in notaries } }
// TODO: REMOVE THIS HACK WHEN NETWORK MAP REDESIGN WORK IS COMPLETED.
.filtered { it.legalIdentities.all { it.name.organisation != "Network Map Service" } }
val myIdentity = rpcProxy.map { it?.nodeInfo()?.legalIdentitiesAndCerts?.first()?.party }
fun partyFromPublicKey(publicKey: PublicKey): ObservableValue<NodeInfo?> = identityCache[publicKey]

View File

@ -13,7 +13,7 @@ interface CordaFuture<V> : Future<V> {
* If the completion thread is problematic for you e.g. deadlock, you can submit to an executor manually.
* If callback fails, its throwable is logged.
*/
fun <W> then(callback: (CordaFuture<V>) -> W): Unit
fun <W> then(callback: (CordaFuture<V>) -> W)
/**
* @return a new [CompletableFuture] with the same outcome as this Future.

View File

@ -33,7 +33,9 @@ fun PrivateKey.sign(bytesToSign: ByteArray): DigitalSignature = DigitalSignature
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(InvalidKeyException::class, SignatureException::class)
fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey) = DigitalSignature.WithKey(publicKey, this.sign(bytesToSign).bytes)
fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
return DigitalSignature.WithKey(publicKey, this.sign(bytesToSign).bytes)
}
/**
* Helper function to sign with a key pair.
@ -44,11 +46,11 @@ fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey) = DigitalSigna
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(InvalidKeyException::class, SignatureException::class)
fun KeyPair.sign(bytesToSign: ByteArray) = private.sign(bytesToSign, public)
fun KeyPair.sign(bytesToSign: ByteArray): DigitalSignature.WithKey = private.sign(bytesToSign, public)
/** Helper function to sign the bytes of [bytesToSign] with a key pair. */
@Throws(InvalidKeyException::class, SignatureException::class)
fun KeyPair.sign(bytesToSign: OpaqueBytes) = sign(bytesToSign.bytes)
fun KeyPair.sign(bytesToSign: OpaqueBytes): DigitalSignature.WithKey = sign(bytesToSign.bytes)
/**
* Helper function for signing a [SignableData] object.

View File

@ -49,6 +49,7 @@ class NotaryFlow {
progressTracker.currentStep = REQUESTING
val notaryParty = stx.notary ?: throw IllegalStateException("Transaction does not specify a Notary")
check(serviceHub.networkMapCache.isNotary(notaryParty)) { "$notaryParty is not a notary on the network" }
check(stx.inputs.all { stateRef -> serviceHub.loadState(stateRef).notary == notaryParty }) {
"Input states must have the same Notary"
}
@ -115,6 +116,9 @@ class NotaryFlow {
@Suspendable
override fun call(): Void? {
check(serviceHub.myInfo.legalIdentities.any { serviceHub.networkMapCache.isNotary(it) }) {
"We are not a notary on the network"
}
val (id, inputs, timeWindow, notary) = receiveAndVerifyTx()
checkNotary(notary)
service.validateTimeWindow(timeWindow)
@ -135,7 +139,7 @@ class NotaryFlow {
protected fun checkNotary(notary: Party?) {
// TODO This check implies that it's OK to use the node's main identity. Shouldn't it be just limited to the
// notary identities?
if (notary !in serviceHub.myInfo.legalIdentities) {
if (notary == null || !serviceHub.myInfo.isLegalIdentity(notary)) {
throw NotaryException(NotaryError.WrongNotary)
}
}

View File

@ -0,0 +1,40 @@
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)

View File

@ -52,7 +52,6 @@ interface NetworkMapCacheBase {
*
* Note that the identities are sorted based on legal name, and the ordering might change once new notaries are introduced.
*/
// TODO this list will be taken from NetworkParameters distributed by NetworkMap.
val notaryIdentities: List<Party>
// DOCEND 1
@ -116,12 +115,15 @@ interface NetworkMapCacheBase {
fun getNotary(name: CordaX500Name): Party? = notaryIdentities.firstOrNull { it.name == name }
// DOCEND 2
/** Checks whether a given party is an advertised notary identity. */
/** Returns true if and only if the given [Party] is a notary, which is defined by the network parameters. */
fun isNotary(party: Party): Boolean = party in notaryIdentities
/** Checks whether a given party is an validating notary identity. */
// TODO This implementation will change after introducing of NetworkParameters.
fun isValidatingNotary(party: Party): Boolean = isNotary(party) && "validating" in party.name.commonName!!
/**
* Returns true if and only if the given [Party] is validating notary. For every party that is a validating notary,
* [isNotary] is only true.
* @see isNotary
*/
fun isValidatingNotary(party: Party): Boolean
/** Clear all network map data from local node cache. */
fun clearNetworkMapCache()

View File

@ -118,10 +118,6 @@ path to the node's base directory.
Only one of ``raft``, ``bftSMaRt`` or ``custom`` configuration values may be specified.
:minimumPlatformVersion: Used by the node if it's running the network map service to enforce a minimum version requirement
on registrations - any node on a Platform Version lower than this value will have their registration rejected.
Defaults to 1 if absent.
:useHTTPS: If false the node's web server will be plain HTTP. If true the node will use the same certificate and private
key from the ``<workspace>/certificates/sslkeystore.jks`` file as the ArtemisMQ port for HTTPS. If HTTPS is enabled
then unencrypted HTTP traffic to the node's **webAddress** port is not supported.

View File

@ -39,10 +39,10 @@ class IntegrationTestingTutorial {
invokeRpc("vaultTrackBy"),
invokeRpc(CordaRPCOps::networkMapFeed)
))
val (alice, bob) = listOf(
val (_, alice, bob) = listOf(
startNotaryNode(DUMMY_NOTARY.name),
startNode(providedName = ALICE.name, rpcUsers = listOf(aliceUser)),
startNode(providedName = BOB.name, rpcUsers = listOf(bobUser)),
startNotaryNode(DUMMY_NOTARY.name)
startNode(providedName = BOB.name, rpcUsers = listOf(bobUser))
).transpose().getOrThrow()
// END 1

View File

@ -1,27 +0,0 @@
package net.corda.nodeapi.internal
import net.corda.core.identity.CordaX500Name
import net.corda.core.serialization.CordaSerializable
/**
* A container for additional information for an advertised service.
*
* @param type the ServiceType identifier
* @param name the service name, used for differentiating multiple services of the same type. Can also be used as a
* grouping identifier for nodes collectively running a distributed service.
*/
@CordaSerializable
data class ServiceInfo(val type: ServiceType, val name: CordaX500Name? = null) {
companion object {
fun parse(encoded: String): ServiceInfo {
val parts = encoded.split("|")
require(parts.size in 1..2) { "Invalid number of elements found" }
val type = ServiceType.parse(parts[0])
val name = parts.getOrNull(1)
val principal = name?.let { CordaX500Name.parse(it) }
return ServiceInfo(type, principal)
}
}
override fun toString() = if (name != null) "$type|$name" else type.toString()
}

View File

@ -1,44 +0,0 @@
package net.corda.nodeapi.internal
import net.corda.core.serialization.CordaSerializable
/**
* Identifier for service types a node can expose over the network to other peers. These types are placed into network
* map advertisements. Services that are purely local and are not providing functionality to other parts of the network
* don't need a declared service type.
*/
@CordaSerializable
class ServiceType private constructor(val id: String) {
init {
// Enforce:
//
// * IDs must start with a lower case letter
// * IDs can only contain alphanumeric, full stop and underscore ASCII characters
require(id.matches(Regex("[a-z][a-zA-Z0-9._]+"))) { id }
}
companion object {
val corda: ServiceType
get() {
val stack = Throwable().stackTrace
val caller = stack.first().className
require(caller.startsWith("net.corda.")) { "Corda ServiceType namespace is reserved for Corda core components" }
return ServiceType("corda")
}
val notary: ServiceType = corda.getSubType("notary")
fun parse(id: String): ServiceType = ServiceType(id)
private fun baseWithSubType(baseId: String, subTypeId: String) = ServiceType("$baseId.$subTypeId")
}
fun getSubType(subTypeId: String): ServiceType = baseWithSubType(id, subTypeId)
fun isSubTypeOf(superType: ServiceType) = (id == superType.id) || id.startsWith(superType.id + ".")
fun isNotary() = isSubTypeOf(notary)
override fun equals(other: Any?): Boolean = other === this || other is ServiceType && other.id == this.id
override fun hashCode(): Int = id.hashCode()
override fun toString(): String = id
}

View File

@ -9,20 +9,15 @@ import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.NodeStartup
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.nodeapi.internal.ServiceType
import net.corda.testing.ALICE
import net.corda.testing.ProjectStructure.projectRootDir
import net.corda.testing.driver.ListenProcessDeathException
import net.corda.testing.driver.driver
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Ignore
import org.junit.Test
import java.io.*
import java.nio.file.Files
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class BootTests {
@ -53,16 +48,6 @@ class BootTests {
assertEquals(1, numberOfNodesThatLogged)
}
}
@Ignore("Need rewriting to produce too big network map registration (adverticed services trick doesn't work after services removal).")
@Test
fun `node quits on failure to register with network map`() {
val tooManyAdvertisedServices = (1..100).map { ServiceInfo(ServiceType.notary.getSubType("$it")) }.toSet()
driver {
val future = startNode(providedName = ALICE.name)
assertFailsWith(ListenProcessDeathException::class) { future.getOrThrow() }
}
}
}
@StartableByRPC

View File

@ -20,10 +20,13 @@ import net.corda.core.utilities.seconds
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.nodeapi.User
import net.corda.testing.*
import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.driver.DriverDSLExposedInterface
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver
import net.corda.testing.eventually
import net.corda.testing.node.MockServices
import org.junit.Assert.assertEquals
import org.junit.Before
@ -54,16 +57,16 @@ class AttachmentLoadingTests {
val bankAName = CordaX500Name("BankA", "Zurich", "CH")
val bankBName = CordaX500Name("BankB", "Zurich", "CH")
val notaryName = CordaX500Name("Notary", "Zurich", "CH")
val flowInitiatorClass =
val flowInitiatorClass: Class<out FlowLogic<*>> =
Class.forName("net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator", true, URLClassLoader(arrayOf(isolatedJAR)))
.asSubclass(FlowLogic::class.java)
private fun DriverDSLExposedInterface.createTwoNodesAndNotary(): List<NodeHandle> {
private fun DriverDSLExposedInterface.createNotaryAndTwoNodes(): List<NodeHandle> {
val adminUser = User("admin", "admin", permissions = setOf("ALL"))
val nodes = listOf(
startNotaryNode(providedName = notaryName, rpcUsers = listOf(adminUser), validating = false),
startNode(providedName = bankAName, rpcUsers = listOf(adminUser)),
startNode(providedName = bankBName, rpcUsers = listOf(adminUser)),
startNotaryNode(providedName = notaryName, rpcUsers = listOf(adminUser), validating = false)
startNode(providedName = bankBName, rpcUsers = listOf(adminUser))
).transpose().getOrThrow() // Wait for all nodes to start up.
nodes.forEach { it.rpc.waitUntilNetworkReady().getOrThrow() }
return nodes
@ -119,7 +122,7 @@ class AttachmentLoadingTests {
fun `test that attachments retrieved over the network are not used for code`() {
driver(initialiseSerialization = false) {
installIsolatedCordappTo(bankAName)
val (bankA, bankB, _) = createTwoNodesAndNotary()
val (_, bankA, bankB) = createNotaryAndTwoNodes()
eventuallyPassingTest {
assertFailsWith<UnexpectedFlowEndException>("Party C=CH,L=Zurich,O=BankB rejected session request: Don't know net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator") {
bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow()
@ -133,7 +136,7 @@ class AttachmentLoadingTests {
driver(initialiseSerialization = false) {
installIsolatedCordappTo(bankAName)
installIsolatedCordappTo(bankBName)
val (bankA, bankB, _) = createTwoNodesAndNotary()
val (_, bankA, bankB) = createNotaryAndTwoNodes()
eventuallyPassingTest {
bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow()
}

View File

@ -13,6 +13,8 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.deleteIfExists
import net.corda.core.internal.div
import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.NotaryService
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.NetworkHostAndPort
@ -21,15 +23,16 @@ import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.StartedNode
import net.corda.node.services.config.BFTSMaRtConfiguration
import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.transactions.BFTNonValidatingNotaryService
import net.corda.node.services.transactions.minClusterSize
import net.corda.node.services.transactions.minCorrectReplicas
import net.corda.node.utilities.ServiceIdentityGenerator
import net.corda.testing.chooseIdentity
import net.corda.testing.common.internal.NetworkParametersCopier
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract
import net.corda.testing.dummyCommand
import net.corda.testing.getDefaultNotary
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetwork.MockNode
import net.corda.testing.node.MockNodeParameters
import org.junit.After
import org.junit.Test
@ -38,39 +41,47 @@ import kotlin.test.assertEquals
import kotlin.test.assertTrue
class BFTNotaryServiceTests {
companion object {
private val clusterName = CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH")
}
private val mockNet = MockNetwork()
private val node = mockNet.createNode()
private lateinit var notary: Party
private lateinit var node: StartedNode<MockNode>
@After
fun stopNodes() {
mockNet.stopNodes()
}
private fun bftNotaryCluster(clusterSize: Int, exposeRaces: Boolean = false) {
private fun startBftClusterAndNode(clusterSize: Int, exposeRaces: Boolean = false) {
(Paths.get("config") / "currentView").deleteIfExists() // XXX: Make config object warn if this exists?
val replicaIds = (0 until clusterSize)
ServiceIdentityGenerator.generateToDisk(
notary = ServiceIdentityGenerator.generateToDisk(
replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) },
clusterName)
CordaX500Name("BFT", "Zurich", "CH"),
NotaryService.constructId(validating = false, bft = true))
val networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(notary, false))))
val clusterAddresses = replicaIds.map { NetworkHostAndPort("localhost", 11000 + it * 10) }
replicaIds.forEach { replicaId ->
mockNet.createNode(MockNodeParameters(configOverrides = {
val nodes = replicaIds.map { replicaId ->
mockNet.createUnstartedNode(MockNodeParameters(configOverrides = {
val notary = NotaryConfig(validating = false, bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses, exposeRaces = exposeRaces))
doReturn(notary).whenever(it).notary
}))
}
mockNet.runNetwork() // Exchange initial network map registration messages.
} + mockNet.createUnstartedNode()
// MockNetwork doesn't support BFT clusters, so we create all the nodes we need unstarted, and then install the
// network-parameters in their directories before they're started.
node = nodes.map { node ->
networkParameters.install(mockNet.baseDirectory(node.id))
node.start()
}.last()
}
/** Failure mode is the redundant replica gets stuck in startup, so we can't dispose it cleanly at the end. */
@Test
fun `all replicas start even if there is a new consensus during startup`() {
bftNotaryCluster(minClusterSize(1), true) // This true adds a sleep to expose the race.
val notary = node.services.getDefaultNotary()
startBftClusterAndNode(minClusterSize(1), exposeRaces = true) // This true adds a sleep to expose the race.
val f = node.run {
val trivialTx = signInitialTransaction(notary) {
addOutputState(DummyContract.SingleOwnerState(owner = info.chooseIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint)
@ -94,8 +105,7 @@ class BFTNotaryServiceTests {
private fun detectDoubleSpend(faultyReplicas: Int) {
val clusterSize = minClusterSize(faultyReplicas)
bftNotaryCluster(clusterSize)
val notary = node.services.getDefaultNotary()
startBftClusterAndNode(clusterSize)
node.run {
val issueTx = signInitialTransaction(notary) {
addOutputState(DummyContract.SingleOwnerState(owner = info.chooseIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint)
@ -138,15 +148,13 @@ class BFTNotaryServiceTests {
}
}
}
}
private fun StartedNode<*>.signInitialTransaction(
notary: Party,
block: TransactionBuilder.() -> Any?
): SignedTransaction {
return services.signInitialTransaction(
TransactionBuilder(notary).apply {
addCommand(dummyCommand(services.myInfo.chooseIdentity().owningKey))
block()
})
private fun StartedNode<*>.signInitialTransaction(notary: Party, block: TransactionBuilder.() -> Any?): SignedTransaction {
return services.signInitialTransaction(
TransactionBuilder(notary).apply {
addCommand(dummyCommand(services.myInfo.chooseIdentity().owningKey))
block()
}
)
}
}

View File

@ -38,12 +38,12 @@ class DistributedServiceTests {
invokeRpc(CordaRPCOps::nodeInfo),
invokeRpc(CordaRPCOps::stateMachinesFeed))
)
val aliceFuture = startNode(providedName = ALICE.name, rpcUsers = listOf(testUser))
val notariesFuture = startNotaryCluster(
DUMMY_NOTARY.name.copy(commonName = RaftValidatingNotaryService.id),
rpcUsers = listOf(testUser),
clusterSize = clusterSize
)
val aliceFuture = startNode(providedName = ALICE.name, rpcUsers = listOf(testUser))
alice = aliceFuture.get()
val (notaryIdentity, notaryNodes) = notariesFuture.get()

View File

@ -6,15 +6,11 @@ import net.corda.cordform.CordformNode
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.KeyManagementService
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.nodeapi.NodeInfoFilesCopier
import net.corda.testing.ALICE
import net.corda.testing.ALICE_KEY
import net.corda.testing.DEV_TRUST_ROOT
import net.corda.testing.getTestPartyAndCertificate
import net.corda.testing.internal.NodeBasedTest
import net.corda.testing.node.MockKeyManagementService
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.contentOf
import org.junit.Before
@ -31,7 +27,6 @@ class NodeInfoWatcherTest : NodeBasedTest() {
val nodeInfo = NodeInfo(listOf(), listOf(getTestPartyAndCertificate(ALICE)), 0, 0)
}
private lateinit var keyManagementService: KeyManagementService
private lateinit var nodeInfoPath: Path
private val scheduler = TestScheduler()
private val testSubscriber = TestSubscriber<NodeInfo>()
@ -41,8 +36,6 @@ class NodeInfoWatcherTest : NodeBasedTest() {
@Before
fun start() {
val identityService = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT)
keyManagementService = MockKeyManagementService(identityService, ALICE_KEY)
nodeInfoWatcher = NodeInfoWatcher(tempFolder.root.toPath(), scheduler = scheduler)
nodeInfoPath = tempFolder.root.toPath() / CordformNode.NODE_INFO_DIRECTORY
}
@ -51,7 +44,7 @@ class NodeInfoWatcherTest : NodeBasedTest() {
fun `save a NodeInfo`() {
assertEquals(0,
tempFolder.root.list().filter { it.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }.size)
NodeInfoWatcher.saveToFile(tempFolder.root.toPath(), nodeInfo, keyManagementService)
NodeInfoWatcher.saveToFile(tempFolder.root.toPath(), nodeInfo, ALICE_KEY)
val nodeInfoFiles = tempFolder.root.list().filter { it.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }
assertEquals(1, nodeInfoFiles.size)
@ -66,7 +59,7 @@ class NodeInfoWatcherTest : NodeBasedTest() {
fun `save a NodeInfo to JimFs`() {
val jimFs = Jimfs.newFileSystem(Configuration.unix())
val jimFolder = jimFs.getPath("/nodeInfo")
NodeInfoWatcher.saveToFile(jimFolder, nodeInfo, keyManagementService)
NodeInfoWatcher.saveToFile(jimFolder, nodeInfo, ALICE_KEY)
}
@Test
@ -135,6 +128,6 @@ class NodeInfoWatcherTest : NodeBasedTest() {
// Write a nodeInfo under the right path.
private fun createNodeInfoFileInPath(nodeInfo: NodeInfo) {
NodeInfoWatcher.saveToFile(nodeInfoPath, nodeInfo, keyManagementService)
NodeInfoWatcher.saveToFile(nodeInfoPath, nodeInfo, ALICE_KEY)
}
}

View File

@ -8,7 +8,7 @@ import net.corda.node.internal.Node
import net.corda.node.internal.StartedNode
import net.corda.testing.ALICE
import net.corda.testing.BOB
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.DUMMY_REGULATOR
import net.corda.testing.chooseIdentity
import net.corda.testing.internal.NodeBasedTest
import org.junit.Before
@ -16,9 +16,9 @@ import org.junit.Test
import kotlin.test.assertEquals
class PersistentNetworkMapCacheTest : NodeBasedTest() {
private val partiesList = listOf(DUMMY_NOTARY, ALICE, BOB)
private val partiesList = listOf(DUMMY_REGULATOR, ALICE, BOB)
private val addressesMap = HashMap<CordaX500Name, NetworkHostAndPort>()
private val infos: MutableSet<NodeInfo> = HashSet()
private val infos = HashSet<NodeInfo>()
@Before
fun start() {
@ -37,8 +37,8 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() {
alice.database.transaction {
val res = netCache.getNodeByLegalIdentity(alice.info.chooseIdentity())
assertEquals(alice.info, res)
val res2 = netCache.getNodeByLegalName(DUMMY_NOTARY.name)
assertEquals(infos.singleOrNull { DUMMY_NOTARY.name in it.legalIdentitiesAndCerts.map { it.name } }, res2)
val res2 = netCache.getNodeByLegalName(DUMMY_REGULATOR.name)
assertEquals(infos.singleOrNull { DUMMY_REGULATOR.name in it.legalIdentities.map { it.name } }, res2)
}
}

View File

@ -8,11 +8,11 @@ import net.corda.core.messaging.startFlow
import net.corda.core.transactions.TransactionBuilder
import net.corda.testing.BOB
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.aliceBobAndNotary
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyState
import net.corda.testing.driver.driver
import net.corda.testing.dummyCommand
import net.corda.testing.notaryAliceAndBob
import org.junit.Test
import kotlin.test.assertEquals
@ -65,7 +65,7 @@ class LargeTransactionsTest {
val bigFile3 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024 * 3, 2)
val bigFile4 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024 * 3, 3)
driver(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.testing.contracts")) {
val (alice, _, _) = aliceBobAndNotary()
val (_, alice) = notaryAliceAndBob()
alice.useRPC {
val hash1 = it.uploadAttachment(bigFile1.inputStream)
val hash2 = it.uploadAttachment(bigFile2.inputStream)

View File

@ -18,7 +18,6 @@ import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints
import org.bouncycastle.cert.X509CertificateHolder
import org.junit.Test
import java.nio.file.Files

View File

@ -23,7 +23,7 @@ import net.corda.nodeapi.User
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.chooseIdentity
import net.corda.testing.driver.driver
import org.junit.Assume
import org.junit.Assume.assumeFalse
import org.junit.Test
import java.lang.management.ManagementFactory
import javax.persistence.Column
@ -33,25 +33,24 @@ import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class NodeStatePersistenceTests {
@Test
fun `persistent state survives node restart`() {
// Temporary disable this test when executed on Windows. It is known to be sporadically failing.
// More investigation is needed to establish why.
Assume.assumeFalse(System.getProperty("os.name").toLowerCase().startsWith("win"))
assumeFalse(System.getProperty("os.name").toLowerCase().startsWith("win"))
val user = User("mark", "dadada", setOf(startFlow<SendMessageFlow>(), invokeRpc("vaultQuery")))
val message = Message("Hello world!")
driver(isDebug = true, startNodesInProcess = isQuasarAgentSpecified()) {
val (nodeName, notaryNodeHandle) = {
val notaryNodeHandle = startNotaryNode(DUMMY_NOTARY.name, validating = false).getOrThrow()
val nodeName = {
startNotaryNode(DUMMY_NOTARY.name, validating = false).getOrThrow()
val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow()
val nodeName = nodeHandle.nodeInfo.chooseIdentity().name
nodeHandle.rpcClientToNode().start(user.username, user.password).use {
it.proxy.startFlow(::SendMessageFlow, message).returnValue.getOrThrow()
}
nodeHandle.stop()
nodeName to notaryNodeHandle
nodeName
}()
val nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user)).getOrThrow()

View File

@ -7,6 +7,7 @@ import net.corda.confidential.SwapIdentitiesFlow
import net.corda.confidential.SwapIdentitiesHandler
import net.corda.core.CordaException
import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.SignedData
import net.corda.core.flows.*
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
@ -20,6 +21,7 @@ import net.corda.core.node.services.*
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.deserialize
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.debug
@ -42,7 +44,9 @@ import net.corda.node.services.events.ScheduledActivityObserver
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.PersistentKeyManagementService
import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.network.*
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.DBCheckpointStorage
import net.corda.node.services.persistence.DBTransactionMappingStorage
import net.corda.node.services.persistence.DBTransactionStorage
@ -82,18 +86,15 @@ import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair
* Marked as SingletonSerializeAsToken to prevent the invisible reference to AbstractNode in the ServiceHub accidentally
* sweeping up the Node into the Kryo checkpoint serialization via any flows holding a reference to ServiceHub.
*/
// TODO Log warning if this node is a notary but not one of the ones specified in the network parameters, both for core and custom
// In theory the NodeInfo for the node should be passed in, instead, however currently this is constructed by the
// AbstractNode. It should be possible to generate the NodeInfo outside of AbstractNode, so it can be passed in.
abstract class AbstractNode(config: NodeConfiguration,
abstract class AbstractNode(val configuration: NodeConfiguration,
val platformClock: Clock,
protected val versionInfo: VersionInfo,
protected val cordappLoader: CordappLoader,
@VisibleForTesting val busyNodeLatch: ReusableLatch = ReusableLatch()) : SingletonSerializeAsToken() {
open val configuration = config.apply {
require(minimumPlatformVersion <= versionInfo.platformVersion) {
"minimumPlatformVersion cannot be greater than the node's own version"
}
}
private val busyNodeLatch: ReusableLatch = ReusableLatch()) : SingletonSerializeAsToken() {
private class StartedNodeImpl<out N : AbstractNode>(
override val internals: N,
@ -116,14 +117,12 @@ abstract class AbstractNode(config: NodeConfiguration,
// low-performance prototyping period.
protected abstract val serverThread: AffinityExecutor
protected lateinit var networkParameters: NetworkParameters
private val cordappServices = MutableClassToInstanceMap.create<SerializeAsToken>()
private val flowFactories = ConcurrentHashMap<Class<out FlowLogic<*>>, InitiatedFlowFactory<*>>()
protected val partyKeys = mutableSetOf<KeyPair>()
protected val services: ServiceHubInternal get() = _services
private lateinit var _services: ServiceHubInternalImpl
protected lateinit var legalIdentity: PartyAndCertificate
private lateinit var allIdentities: List<PartyAndCertificate>
protected lateinit var info: NodeInfo
protected var myNotaryIdentity: PartyAndCertificate? = null
protected lateinit var checkpointStorage: CheckpointStorage
@ -157,10 +156,6 @@ abstract class AbstractNode(config: NodeConfiguration,
return SecureCordaRPCOps(services, smm, database, flowStarter)
}
private fun saveOwnNodeInfo() {
NodeInfoWatcher.saveToFile(configuration.baseDirectory, info, services.keyManagementService)
}
private fun initCertificate() {
if (configuration.devMode) {
log.warn("Corda node is running in dev mode.")
@ -169,33 +164,34 @@ abstract class AbstractNode(config: NodeConfiguration,
validateKeystore()
}
private fun makeSchemaService() = NodeSchemaService(cordappLoader)
open fun generateNodeInfo() {
check(started == null) { "Node has already been started" }
initCertificate()
log.info("Generating nodeInfo ...")
val schemaService = makeSchemaService()
initialiseDatabasePersistence(schemaService) {
val transactionStorage = makeTransactionStorage()
makeServices(schemaService, transactionStorage, StateLoaderImpl(transactionStorage))
saveOwnNodeInfo()
}
initCertificate()
initNodeInfo()
}
open fun start(): StartedNode<AbstractNode> {
check(started == null) { "Node has already been started" }
initCertificate()
log.info("Node starting up ...")
val schemaService = makeSchemaService()
initCertificate()
val keyPairs = initNodeInfo()
readNetworkParameters()
val schemaService = NodeSchemaService(cordappLoader)
// Do all of this in a database transaction so anything that might need a connection has one.
val startedImpl = initialiseDatabasePersistence(schemaService) {
val transactionStorage = makeTransactionStorage()
val stateLoader = StateLoaderImpl(transactionStorage)
val services = makeServices(schemaService, transactionStorage, stateLoader)
saveOwnNodeInfo()
val services = makeServices(keyPairs, schemaService, transactionStorage, stateLoader)
smm = makeStateMachineManager()
val flowStarter = FlowStarterImpl(serverThread, smm)
val schedulerService = NodeSchedulerService(platformClock, this@AbstractNode.database, flowStarter, stateLoader, unfinishedSchedules = busyNodeLatch, serverThread = serverThread)
val schedulerService = NodeSchedulerService(
platformClock,
this@AbstractNode.database,
flowStarter,
stateLoader,
unfinishedSchedules = busyNodeLatch,
serverThread = serverThread)
if (serverThread is ExecutorService) {
runOnStop += {
// We wait here, even though any in-flight messages should have been drained away because the
@ -232,6 +228,35 @@ abstract class AbstractNode(config: NodeConfiguration,
}
}
private fun initNodeInfo(): Set<KeyPair> {
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
val keyPairs = mutableSetOf(identityKeyPair)
myNotaryIdentity = configuration.notary?.let {
if (it.isClusterConfig) {
val (notaryIdentity, notaryIdentityKeyPair) = obtainIdentity(it)
keyPairs += notaryIdentityKeyPair
notaryIdentity
} else {
// In case of a single notary service myNotaryIdentity will be the node's single identity.
identity
}
}
info = NodeInfo(
myAddresses(),
setOf(identity, myNotaryIdentity).filterNotNull(),
versionInfo.platformVersion,
platformClock.instant().toEpochMilli()
)
NodeInfoWatcher.saveToFile(configuration.baseDirectory, info, identityKeyPair)
return keyPairs
}
protected abstract fun myAddresses(): List<NetworkHostAndPort>
protected open fun makeStateMachineManager(): StateMachineManager {
return StateMachineManagerImpl(
services,
@ -458,23 +483,25 @@ abstract class AbstractNode(config: NodeConfiguration,
* Builds node internal, advertised, and plugin services.
* Returns a list of tokenizable services to be added to the serialisation context.
*/
private fun makeServices(schemaService: SchemaService, transactionStorage: WritableTransactionStorage, stateLoader: StateLoader): MutableList<Any> {
private fun makeServices(keyPairs: Set<KeyPair>, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, stateLoader: StateLoader): MutableList<Any> {
checkpointStorage = DBCheckpointStorage()
val metrics = MetricRegistry()
attachments = NodeAttachmentService(metrics)
val cordappProvider = CordappProviderImpl(cordappLoader, attachments)
_services = ServiceHubInternalImpl(schemaService, transactionStorage, stateLoader, MonitoringService(metrics), cordappProvider)
legalIdentity = obtainIdentity(notaryConfig = null)
// TODO We keep only notary identity as additional legalIdentity if we run it on a node . Multiple identities need more design thinking.
myNotaryIdentity = getNotaryIdentity()
allIdentities = listOf(legalIdentity, myNotaryIdentity).filterNotNull()
network = makeMessagingService(legalIdentity)
val addresses = myAddresses() // TODO There is no support for multiple IP addresses yet.
info = NodeInfo(addresses, allIdentities, versionInfo.platformVersion, platformClock.instant().toEpochMilli())
val networkMapCache = services.networkMapCache
val identityService = makeIdentityService()
val keyManagementService = makeKeyManagementService(identityService, keyPairs)
_services = ServiceHubInternalImpl(
identityService,
keyManagementService,
schemaService,
transactionStorage,
stateLoader,
MonitoringService(metrics),
cordappProvider)
network = makeMessagingService()
val tokenizableServices = mutableListOf(attachments, network, services.vaultService,
services.keyManagementService, services.identityService, platformClock,
services.auditService, services.monitoringService, networkMapCache, services.schemaService,
services.auditService, services.monitoringService, services.networkMapCache, services.schemaService,
services.transactionVerifierService, services.validatedTransactions, services.contractUpgradeService,
services, cordappProvider, this)
makeNetworkServices(tokenizableServices)
@ -489,12 +516,6 @@ abstract class AbstractNode(config: NodeConfiguration,
HibernateObserver.install(services.vaultService.rawUpdates, database.hibernateConfig)
}
/**
* Obtain the node's notary identity if it's configured to be one. If part of a distributed notary then this will be
* the distributed identity shared across all the nodes of the cluster.
*/
protected fun getNotaryIdentity(): PartyAndCertificate? = configuration.notary?.let { obtainIdentity(it) }
@VisibleForTesting
protected open fun acceptableLiveFiberCountOnStop(): Int = 0
@ -549,9 +570,6 @@ abstract class AbstractNode(config: NodeConfiguration,
}
}
/** Return list of node's addresses. It's overridden in MockNetwork as we don't have real addresses for MockNodes. */
protected abstract fun myAddresses(): List<NetworkHostAndPort>
open protected fun checkNetworkMapIsInitialized() {
if (!services.networkMapCache.loadDBSuccess) {
// TODO: There should be a consistent approach to configuration error exceptions.
@ -559,8 +577,15 @@ abstract class AbstractNode(config: NodeConfiguration,
}
}
protected open fun makeKeyManagementService(identityService: IdentityService): KeyManagementService {
return PersistentKeyManagementService(identityService, partyKeys)
protected open fun makeKeyManagementService(identityService: IdentityService, keyPairs: Set<KeyPair>): KeyManagementService {
return PersistentKeyManagementService(identityService, keyPairs)
}
private fun readNetworkParameters() {
val file = configuration.baseDirectory / "network-parameters"
networkParameters = file.readAll().deserialize<SignedData<NetworkParameters>>().verified()
log.info(networkParameters.toString())
check(networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) { "Node is too old for the network" }
}
private fun makeCoreNotaryService(notaryConfig: NotaryConfig): NotaryService {
@ -593,13 +618,13 @@ abstract class AbstractNode(config: NodeConfiguration,
}
}
protected open fun makeIdentityService(trustRoot: X509Certificate,
clientCa: CertificateAndKeyPair?,
legalIdentity: PartyAndCertificate): IdentityService {
val caCertificates: Array<X509Certificate> = listOf(legalIdentity.certificate, clientCa?.certificate?.cert)
.filterNotNull()
.toTypedArray()
return PersistentIdentityService(allIdentities, trustRoot = trustRoot, caCertificates = *caCertificates)
private fun makeIdentityService(): IdentityService {
val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword)
val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
val trustRoot = trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
val clientCa = caKeyStore.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
val caCertificates = arrayOf(info.legalIdentitiesAndCerts[0].certificate, clientCa.certificate.cert)
return PersistentIdentityService(info.legalIdentitiesAndCerts, trustRoot = trustRoot, caCertificates = *caCertificates)
}
protected abstract fun makeTransactionVerifierService(): TransactionVerifierService
@ -619,29 +644,23 @@ abstract class AbstractNode(config: NodeConfiguration,
_started = null
}
protected abstract fun makeMessagingService(legalIdentity: PartyAndCertificate): MessagingService
protected abstract fun makeMessagingService(): MessagingService
protected abstract fun startMessagingService(rpcOps: RPCOps)
private fun obtainIdentity(notaryConfig: NotaryConfig?): PartyAndCertificate {
private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair<PartyAndCertificate, KeyPair> {
val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
val (id, singleName) = if (notaryConfig == null) {
// Node's main identity
val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) {
// Node's main identity or if it's a single node notary
Pair("identity", myLegalName)
} else {
val notaryId = notaryConfig.run {
NotaryService.constructId(validating, raft != null, bftSMaRt != null, custom)
}
if (notaryConfig.bftSMaRt == null && notaryConfig.raft == null) {
// Node's notary identity
Pair(notaryId, myLegalName.copy(commonName = notaryId))
} else {
// The node is part of a distributed notary whose identity must already be generated beforehand
Pair(notaryId, null)
}
// The node is part of a distributed notary whose identity must already be generated beforehand.
Pair(notaryId, null)
}
// TODO: Integrate with Key management service?
val privateKeyAlias = "$id-private-key"
@ -653,7 +672,7 @@ abstract class AbstractNode(config: NodeConfiguration,
keyStore.signAndSaveNewKeyPair(singleName, privateKeyAlias, generateKeyPair())
}
val (x509Cert, keys) = keyStore.certificateAndKeyPair(privateKeyAlias)
val (x509Cert, keyPair) = keyStore.certificateAndKeyPair(privateKeyAlias)
// TODO: Use configuration to indicate composite key should be used instead of public key for the identity.
val compositeKeyAlias = "$id-composite-key"
@ -679,8 +698,8 @@ abstract class AbstractNode(config: NodeConfiguration,
throw ConfigurationException("The name '$singleName' for $id doesn't match what's in the key store: $subject")
}
partyKeys += keys
return PartyAndCertificate(CertificateFactory.getInstance("X509").generateCertPath(certificates))
val certPath = CertificateFactory.getInstance("X509").generateCertPath(certificates)
return Pair(PartyAndCertificate(certPath), keyPair)
}
protected open fun generateKeyPair() = cryptoGenerateKeyPair()
@ -689,6 +708,11 @@ abstract class AbstractNode(config: NodeConfiguration,
}
private inner class ServiceHubInternalImpl(
override val identityService: IdentityService,
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
// the identity key. But the infrastructure to make that easy isn't here yet.
override val keyManagementService: KeyManagementService,
override val schemaService: SchemaService,
override val validatedTransactions: WritableTransactionStorage,
private val stateLoader: StateLoader,
@ -699,22 +723,16 @@ abstract class AbstractNode(config: NodeConfiguration,
override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage()
override val auditService = DummyAuditService()
override val transactionVerifierService by lazy { makeTransactionVerifierService() }
override val networkMapCache by lazy { NetworkMapCacheImpl(PersistentNetworkMapCache(this@AbstractNode.database, this@AbstractNode.configuration), identityService) }
override val networkMapCache by lazy {
NetworkMapCacheImpl(
PersistentNetworkMapCache(
this@AbstractNode.database,
this@AbstractNode.configuration,
networkParameters.notaries),
identityService)
}
override val vaultService by lazy { makeVaultService(keyManagementService, stateLoader) }
override val contractUpgradeService by lazy { ContractUpgradeServiceImpl() }
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
// the identity key. But the infrastructure to make that easy isn't here yet.
override val keyManagementService by lazy { makeKeyManagementService(identityService) }
override val identityService by lazy {
val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword)
val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
makeIdentityService(
trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA),
caKeyStore.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA),
legalIdentity)
}
override val attachments: AttachmentStorage get() = this@AbstractNode.attachments
override val networkService: MessagingService get() = network
override val clock: Clock get() = platformClock

View File

@ -4,7 +4,6 @@ import com.codahale.metrics.JmxReporter
import net.corda.core.CordaException
import net.corda.core.concurrent.CordaFuture
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.concurrent.thenMatch
import net.corda.core.internal.uncheckedCast
@ -45,7 +44,7 @@ import kotlin.system.exitProcess
*
* @param configuration This is typically loaded from a TypeSafe HOCON configuration file.
*/
open class Node(override val configuration: NodeConfiguration,
open class Node(configuration: NodeConfiguration,
versionInfo: VersionInfo,
val initialiseSerialization: Boolean = true,
cordappLoader: CordappLoader = makeCordappLoader(configuration)
@ -131,11 +130,11 @@ open class Node(override val configuration: NodeConfiguration,
private lateinit var userService: RPCUserService
override fun makeMessagingService(legalIdentity: PartyAndCertificate): MessagingService {
override fun makeMessagingService(): MessagingService {
userService = RPCUserServiceImpl(configuration.rpcUsers)
val serverAddress = configuration.messagingServerAddress ?: makeLocalMessageBroker()
val advertisedAddress = configuration.messagingServerAddress ?: getAdvertisedAddress()
val advertisedAddress = info.addresses.single()
printBasicNodeInfo("Incoming connection address", advertisedAddress.toString())
@ -143,7 +142,7 @@ open class Node(override val configuration: NodeConfiguration,
configuration,
versionInfo,
serverAddress,
legalIdentity.owningKey,
info.legalIdentities[0].owningKey,
serverThread,
database,
services.monitoringService,
@ -157,14 +156,18 @@ open class Node(override val configuration: NodeConfiguration,
}
}
override fun myAddresses(): List<NetworkHostAndPort> {
return listOf(configuration.messagingServerAddress ?: getAdvertisedAddress())
}
private fun getAdvertisedAddress(): NetworkHostAndPort {
return with(configuration) {
val useHost = if (detectPublicIp) {
val host = if (detectPublicIp) {
tryDetectIfNotPublicHost(p2pAddress.host) ?: p2pAddress.host
} else {
p2pAddress.host
}
NetworkHostAndPort(useHost, p2pAddress.port)
NetworkHostAndPort(host, p2pAddress.port)
}
}
@ -196,11 +199,6 @@ open class Node(override val configuration: NodeConfiguration,
(network as NodeMessagingClient).start(rpcOps, userService)
}
override fun myAddresses(): List<NetworkHostAndPort> {
val address = network.myAddress as ArtemisMessagingComponent.ArtemisPeerAddress
return listOf(address.hostAndPort)
}
/**
* If the node is persisting to an embedded H2 database, then expose this via TCP with a JDBC URL of the form:
* jdbc:h2:tcp://<host>:<port>/node

View File

@ -18,7 +18,6 @@ interface NodeConfiguration : NodeSSLConfiguration {
// myLegalName should be only used in the initial network registration, we should use the name from the certificate instead of this.
// TODO: Remove this so we don't accidentally use this identity in the code?
val myLegalName: CordaX500Name
val minimumPlatformVersion: Int
val emailAddress: String
val exportJMXto: String
val dataSourceProperties: Properties
@ -52,15 +51,17 @@ data class NotaryConfig(val validating: Boolean,
"raft, bftSMaRt, and custom configs cannot be specified together"
}
}
val isClusterConfig: Boolean get() = raft != null || bftSMaRt != null
}
data class RaftConfig(val nodeAddress: NetworkHostAndPort, val clusterAddresses: List<NetworkHostAndPort>)
/** @param exposeRaces for testing only, so its default is not in reference.conf but here. */
data class BFTSMaRtConfiguration constructor(val replicaId: Int,
val clusterAddresses: List<NetworkHostAndPort>,
val debug: Boolean = false,
val exposeRaces: Boolean = false
data class BFTSMaRtConfiguration(
val replicaId: Int,
val clusterAddresses: List<NetworkHostAndPort>,
val debug: Boolean = false,
val exposeRaces: Boolean = false
) {
init {
require(replicaId >= 0) { "replicaId cannot be negative" }
@ -85,7 +86,6 @@ data class NodeConfigurationImpl(
override val dataSourceProperties: Properties,
override val database: Properties?,
override val certificateSigningService: URL,
override val minimumPlatformVersion: Int = 1,
override val rpcUsers: List<User>,
override val verifierType: VerifierType,
// TODO typesafe config supports the notion of durations. Make use of that by mapping it to java.time.Duration.
@ -113,8 +113,6 @@ data class NodeConfigurationImpl(
// This is a sanity feature do not remove.
require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" }
require(devModeOptions == null || devMode) { "Cannot use devModeOptions outside of dev mode" }
require(myLegalName.commonName == null) { "Common name must be null: $myLegalName" }
require(minimumPlatformVersion >= 1) { "minimumPlatformVersion cannot be less than 1" }
}
}

View File

@ -2,9 +2,9 @@ package net.corda.node.services.network
import net.corda.cordform.CordformNode
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.sign
import net.corda.core.internal.*
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.KeyManagementService
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.utilities.loggerFor
@ -15,6 +15,7 @@ import rx.Scheduler
import rx.schedulers.Schedulers
import java.io.IOException
import java.nio.file.Path
import java.security.KeyPair
import java.util.concurrent.TimeUnit
import kotlin.streams.toList
@ -48,13 +49,13 @@ class NodeInfoWatcher(private val nodePath: Path,
*
* @param path the path where to write the file, if non-existent it will be created.
* @param nodeInfo the NodeInfo to serialize.
* @param keyManager a KeyManagementService used to sign the NodeInfo data.
* @param signingKey used to sign the NodeInfo data.
*/
fun saveToFile(path: Path, nodeInfo: NodeInfo, keyManager: KeyManagementService) {
fun saveToFile(path: Path, nodeInfo: NodeInfo, signingKey: KeyPair) {
try {
path.createDirectories()
val serializedBytes = nodeInfo.serialize()
val regSig = keyManager.sign(serializedBytes.bytes, nodeInfo.legalIdentities.first().owningKey)
val regSig = signingKey.sign(serializedBytes.bytes)
val signedData = SignedData(serializedBytes, regSig)
signedData.serialize().open().copyTo(
path / "${NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX}${serializedBytes.hash}")

View File

@ -10,9 +10,9 @@ import net.corda.core.internal.bufferUntilSubscribed
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.messaging.DataFeed
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.NetworkMapCache.MapChange
import net.corda.core.node.services.NotaryService
import net.corda.core.node.services.PartyInfo
import net.corda.core.schemas.NodeInfoSchemaV1
import net.corda.core.serialization.SingletonSerializeAsToken
@ -32,7 +32,10 @@ import java.util.*
import javax.annotation.concurrent.ThreadSafe
import kotlin.collections.HashMap
class NetworkMapCacheImpl(networkMapCacheBase: NetworkMapCacheBaseInternal, private val identityService: IdentityService) : NetworkMapCacheBaseInternal by networkMapCacheBase, NetworkMapCacheInternal {
class NetworkMapCacheImpl(
networkMapCacheBase: NetworkMapCacheBaseInternal,
private val identityService: IdentityService
) : NetworkMapCacheBaseInternal by networkMapCacheBase, NetworkMapCacheInternal {
init {
networkMapCacheBase.allNodes.forEach { it.legalIdentitiesAndCerts.forEach { identityService.verifyAndRegisterIdentity(it) } }
networkMapCacheBase.changed.subscribe { mapChange ->
@ -57,13 +60,16 @@ class NetworkMapCacheImpl(networkMapCacheBase: NetworkMapCacheBaseInternal, priv
* Extremely simple in-memory cache of the network map.
*/
@ThreadSafe
open class PersistentNetworkMapCache(private val database: CordaPersistence, configuration: NodeConfiguration) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal {
open class PersistentNetworkMapCache(
private val database: CordaPersistence,
val configuration: NodeConfiguration,
notaries: List<NotaryInfo>
) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal {
companion object {
val logger = loggerFor<PersistentNetworkMapCache>()
}
// TODO Small explanation, partyNodes and registeredNodes is left in memory as it was before, because it will be removed in
// next PR that gets rid of services. These maps are used only for queries by service.
// TODO Cleanup registered and party nodes
protected val registeredNodes: MutableMap<PublicKey, NodeInfo> = Collections.synchronizedMap(HashMap())
protected val partyNodes: MutableList<NodeInfo> get() = registeredNodes.map { it.value }.toMutableList()
private val _changed = PublishSubject.create<MapChange>()
@ -77,22 +83,9 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, con
override val nodeReady: CordaFuture<Void?> get() = _registrationFuture
private var _loadDBSuccess: Boolean = false
override val loadDBSuccess get() = _loadDBSuccess
// TODO From the NetworkMapService redesign doc: Remove the concept of network services.
// As a temporary hack, just assume for now that every network has a notary service named "Notary Service" that can be looked up in the map.
// This should eliminate the only required usage of services.
// It is ensured on node startup when constructing a notary that the name contains "notary".
override val notaryIdentities: List<Party>
get() {
return partyNodes
.flatMap {
// TODO: validate notary identity certificates before loading into network map cache.
// Notary certificates have to be signed by the doorman directly
it.legalIdentities
}
.filter { it.name.commonName?.startsWith(NotaryService.ID_PREFIX) ?: false }
.toSet() // Distinct, because of distributed service nodes
.sortedBy { it.name.toString() }
}
override val notaryIdentities: List<Party> = notaries.map { it.identity }
private val validatingNotaries = notaries.mapNotNull { if (it.validating) it.identity else null }
private val nodeInfoSerializer = NodeInfoWatcher(configuration.baseDirectory,
configuration.additionalNodeInfoPollingFrequencyMsec)
@ -107,6 +100,8 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, con
nodeInfoSerializer.nodeInfoUpdates().subscribe { node -> addNode(node) }
}
override fun isValidatingNotary(party: Party): Boolean = party in validatingNotaries
override fun getPartyInfo(party: Party): PartyInfo? {
val nodes = database.transaction { queryByIdentityKey(session, party.owningKey) }
if (nodes.size == 1 && nodes[0].isLegalIdentity(party)) {
@ -286,7 +281,6 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, con
return NodeInfoSchemaV1.PersistentNodeInfo(
id = 0,
addresses = nodeInfo.addresses.map { NodeInfoSchemaV1.DBHostAndPort.fromHostAndPort(it) },
// TODO Another ugly hack with special first identity...
legalIdentitiesAndCerts = nodeInfo.legalIdentitiesAndCerts.mapIndexed { idx, elem ->
NodeInfoSchemaV1.DBPartyAndCertificate(elem, isMain = idx == 0)
},

View File

@ -5,10 +5,10 @@ import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.cert
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.trace
import java.nio.file.Files
import java.nio.file.Path
object ServiceIdentityGenerator {
@ -20,13 +20,12 @@ object ServiceIdentityGenerator {
* This method should be called *before* any of the nodes are started.
*
* @param dirs List of node directories to place the generated identity and key pairs in.
* @param serviceId The service id of the distributed service.
* @param serviceName The legal name of the distributed service, with service id as CN.
* @param serviceName The legal name of the distributed service.
* @param threshold The threshold for the generated group [CompositeKey].
*/
// TODO: This needs to write out to the key store, not just files on disk
fun generateToDisk(dirs: List<Path>,
serviceName: CordaX500Name,
serviceId: String,
threshold: Int = 1): Party {
log.trace { "Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}" }
val keyPairs = (1..dirs.size).map { generateKeyPair() }
@ -39,9 +38,8 @@ object ServiceIdentityGenerator {
keyPairs.zip(dirs) { keyPair, dir ->
val serviceKeyCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, issuer.certificate, issuer.keyPair, serviceName, keyPair.public)
val compositeKeyCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, issuer.certificate, issuer.keyPair, serviceName, notaryKey)
val certPath = Files.createDirectories(dir / "certificates") / "distributedService.jks"
val certPath = (dir / "certificates").createDirectories() / "distributedService.jks"
val keystore = loadOrCreateKeyStore(certPath, "cordacadevpass")
val serviceId = serviceName.commonName
keystore.setCertificateEntry("$serviceId-composite-key", compositeKeyCert.cert)
keystore.setKeyEntry("$serviceId-private-key", keyPair.private, "cordacadevkeypass".toCharArray(), arrayOf(serviceKeyCert.cert, issuer.certificate.cert, rootCert))
keystore.save(certPath, "cordacadevpass")

View File

@ -55,22 +55,22 @@ class CordaRPCOpsImplTest {
val testJar = "net/corda/node/testing/test.jar"
}
lateinit var mockNet: MockNetwork
lateinit var aliceNode: StartedNode<MockNode>
lateinit var notaryNode: StartedNode<MockNode>
lateinit var notary: Party
lateinit var rpc: CordaRPCOps
lateinit var stateMachineUpdates: Observable<StateMachineUpdate>
lateinit var transactions: Observable<SignedTransaction>
lateinit var vaultTrackCash: Observable<Vault.Update<Cash.State>>
private lateinit var mockNet: MockNetwork
private lateinit var aliceNode: StartedNode<MockNode>
private lateinit var notaryNode: StartedNode<MockNode>
private lateinit var notary: Party
private lateinit var rpc: CordaRPCOps
private lateinit var stateMachineUpdates: Observable<StateMachineUpdate>
private lateinit var transactions: Observable<SignedTransaction>
private lateinit var vaultTrackCash: Observable<Vault.Update<Cash.State>>
private val user = User("user", "pwd", permissions = emptySet())
@Before
fun setup() {
mockNet = MockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset"))
aliceNode = mockNet.createNode()
notaryNode = mockNet.createNotaryNode(validating = false)
aliceNode = mockNet.createNode()
rpc = SecureCordaRPCOps(aliceNode.services, aliceNode.smm, aliceNode.database, aliceNode.services)
CURRENT_RPC_CONTEXT.set(RpcContext(user))

View File

@ -615,18 +615,25 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) {
notaryNode: StartedNode<*>,
vararg extraSigningNodes: StartedNode<*>): Map<SecureHash, SignedTransaction> {
val notaryParty = notaryNode.info.legalIdentities[0]
val signed = wtxToSign.map {
val id = it.id
val sigs = mutableListOf<TransactionSignature>()
val nodeKey = node.info.chooseIdentity().owningKey
sigs.add(node.services.keyManagementService.sign(SignableData(id, SignatureMetadata(1, Crypto.findSignatureScheme(nodeKey).schemeNumberID)), nodeKey))
sigs.add(notaryNode.services.keyManagementService.sign(SignableData(id, SignatureMetadata(1,
Crypto.findSignatureScheme(notaryNode.info.legalIdentities[1].owningKey).schemeNumberID)), notaryNode.info.legalIdentities[1].owningKey))
sigs += node.services.keyManagementService.sign(
SignableData(id, SignatureMetadata(1, Crypto.findSignatureScheme(nodeKey).schemeNumberID)),
nodeKey
)
sigs += notaryNode.services.keyManagementService.sign(
SignableData(id, SignatureMetadata(1, Crypto.findSignatureScheme(notaryParty.owningKey).schemeNumberID)),
notaryParty.owningKey
)
extraSigningNodes.forEach { currentNode ->
sigs.add(currentNode.services.keyManagementService.sign(
SignableData(id, SignatureMetadata(1, Crypto.findSignatureScheme(currentNode.info.chooseIdentity().owningKey).schemeNumberID)),
sigs += currentNode.services.keyManagementService.sign(
SignableData(id, SignatureMetadata(
1,
Crypto.findSignatureScheme(currentNode.info.chooseIdentity().owningKey).schemeNumberID)),
currentNode.info.chooseIdentity().owningKey)
)
}
SignedTransaction(it, sigs)
}

View File

@ -1,5 +1,7 @@
package net.corda.node.services
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.*
import net.corda.core.crypto.generateKeyPair
import net.corda.core.flows.NotaryChangeFlow
@ -13,8 +15,12 @@ import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.node.internal.StartedNode
import net.corda.testing.*
import net.corda.node.services.config.NotaryConfig
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.chooseIdentity
import net.corda.testing.contracts.DummyContract
import net.corda.testing.dummyCommand
import net.corda.testing.getTestPartyAndCertificate
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNodeParameters
import org.assertj.core.api.Assertions.assertThatExceptionOfType
@ -27,24 +33,32 @@ import kotlin.test.assertEquals
import kotlin.test.assertTrue
class NotaryChangeTests {
lateinit var mockNet: MockNetwork
lateinit var oldNotaryNode: StartedNode<MockNetwork.MockNode>
lateinit var newNotaryNode: StartedNode<MockNetwork.MockNode>
lateinit var clientNodeA: StartedNode<MockNetwork.MockNode>
lateinit var clientNodeB: StartedNode<MockNetwork.MockNode>
lateinit var newNotaryParty: Party
lateinit var oldNotaryParty: Party
private lateinit var mockNet: MockNetwork
private lateinit var oldNotaryNode: StartedNode<MockNetwork.MockNode>
private lateinit var clientNodeA: StartedNode<MockNetwork.MockNode>
private lateinit var clientNodeB: StartedNode<MockNetwork.MockNode>
private lateinit var newNotaryParty: Party
private lateinit var oldNotaryParty: Party
@Before
fun setUp() {
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
oldNotaryNode = mockNet.createNotaryNode(MockNodeParameters(legalName = DUMMY_NOTARY.name))
val (oldNotaryNode, newNotaryNode) = listOf(
createUnstartedNotary(DUMMY_NOTARY.name),
createUnstartedNotary(DUMMY_NOTARY.name.copy(organisation = "Dummy Notary 2"))
).map { it.start() }
this.oldNotaryNode = oldNotaryNode
clientNodeA = mockNet.createNode()
clientNodeB = mockNet.createNode()
newNotaryNode = mockNet.createNotaryNode(MockNodeParameters(legalName = DUMMY_NOTARY.name.copy(organisation = "Dummy Notary 2")))
mockNet.runNetwork() // Clear network map registration messages
oldNotaryParty = newNotaryNode.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME)!!
newNotaryParty = newNotaryNode.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME.copy(organisation = "Dummy Notary 2"))!!
oldNotaryParty = newNotaryNode.services.networkMapCache.getNotary(DUMMY_NOTARY.name)!!
newNotaryParty = newNotaryNode.services.networkMapCache.getNotary(DUMMY_NOTARY.name.copy(organisation = "Dummy Notary 2"))!!
}
private fun createUnstartedNotary(name: CordaX500Name): MockNetwork.MockNode {
return mockNet.createUnstartedNode(MockNodeParameters(
legalName = name,
configOverrides = { doReturn(NotaryConfig(validating = true)).whenever(it).notary }
))
}
@After

View File

@ -42,6 +42,7 @@ class ArtemisMessagingTests {
companion object {
const val TOPIC = "platform.self"
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
@ -50,21 +51,21 @@ class ArtemisMessagingTests {
@JvmField
val temporaryFolder = TemporaryFolder()
val serverPort = freePort()
val rpcPort = freePort()
val identity = generateKeyPair()
private val serverPort = freePort()
private val rpcPort = freePort()
private val identity = generateKeyPair()
lateinit var config: NodeConfiguration
lateinit var database: CordaPersistence
lateinit var userService: RPCUserService
lateinit var networkMapRegistrationFuture: CordaFuture<Unit>
private lateinit var config: NodeConfiguration
private lateinit var database: CordaPersistence
private lateinit var userService: RPCUserService
private lateinit var networkMapRegistrationFuture: CordaFuture<Unit>
var messagingClient: NodeMessagingClient? = null
var messagingServer: ArtemisMessagingServer? = null
private var messagingClient: NodeMessagingClient? = null
private var messagingServer: ArtemisMessagingServer? = null
lateinit var networkMapCache: NetworkMapCacheImpl
private lateinit var networkMapCache: NetworkMapCacheImpl
val rpcOps = object : RPCOps {
private val rpcOps = object : RPCOps {
override val protocolVersion: Int get() = throw UnsupportedOperationException()
}
@ -78,7 +79,7 @@ class ArtemisMessagingTests {
LogHelper.setLevel(PersistentUniquenessProvider::class)
database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService)
networkMapRegistrationFuture = doneFuture(Unit)
networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, config), rigorousMock())
networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, config, emptyList()), rigorousMock())
}
@After

View File

@ -72,18 +72,14 @@ class FlowFrameworkTests {
@Before
fun start() {
mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts"))
mockNet = MockNetwork(
servicePeerAllocationStrategy = RoundRobin(),
cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts"))
val notary = mockNet.createNotaryNode()
aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME))
bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME))
mockNet.runNetwork()
// We intentionally create our own notary and ignore the one provided by the network
// Note that these notaries don't operate correctly as they don't share their state. They are only used for testing
// service addressing.
val notary = mockNet.createNotaryNode()
receivedSessionMessagesObservable().forEach { receivedSessionMessages += it }
mockNet.runNetwork()
// Extract identities
alice = aliceNode.info.singleIdentity()

View File

@ -27,10 +27,10 @@ class AttachmentDemoTest {
invokeRpc(CordaRPCOps::wellKnownPartyFromX500Name),
invokeRpc(CordaRPCOps::internalVerifiedTransactionsFeed)
)))
val (nodeA, nodeB) = listOf(
val (_, nodeA, nodeB) = listOf(
startNotaryNode(DUMMY_NOTARY.name, validating = false),
startNode(providedName = DUMMY_BANK_A.name, rpcUsers = demoUser, maximumHeapSize = "1g"),
startNode(providedName = DUMMY_BANK_B.name, rpcUsers = demoUser, maximumHeapSize = "1g"),
startNotaryNode(DUMMY_NOTARY.name, validating = false))
startNode(providedName = DUMMY_BANK_B.name, rpcUsers = demoUser, maximumHeapSize = "1g"))
.map { it.getOrThrow() }
startWebserver(nodeB).getOrThrow()

View File

@ -2,23 +2,26 @@ package net.corda.bank
import net.corda.bank.api.BankOfCordaClientApi
import net.corda.bank.api.BankOfCordaWebApi.IssueRequestParams
import net.corda.core.internal.concurrent.transpose
import net.corda.core.utilities.getOrThrow
import net.corda.testing.BOC
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.driver.driver
import net.corda.testing.notary
import org.junit.Test
import kotlin.test.assertTrue
class BankOfCordaHttpAPITest {
@Test
fun `issuer flow via Http`() {
driver(extraCordappPackagesToScan = listOf("net.corda.finance"), dsl = {
val bigCorpNodeFuture = startNode(providedName = BIGCORP_LEGAL_NAME)
val nodeBankOfCordaFuture = startNotaryNode(BOC.name, validating = false)
val (nodeBankOfCorda) = listOf(nodeBankOfCordaFuture, bigCorpNodeFuture).map { it.getOrThrow() }
val nodeBankOfCordaApiAddr = startWebserver(nodeBankOfCorda).getOrThrow().listenAddress
val notaryName = notary().node.nodeInfo.legalIdentities[1].name
assertTrue(BankOfCordaClientApi(nodeBankOfCordaApiAddr).requestWebIssue(IssueRequestParams(1000, "USD", BIGCORP_LEGAL_NAME, "1", BOC.name, notaryName)))
}, isDebug = true)
driver(isDebug = true, extraCordappPackagesToScan = listOf("net.corda.finance")) {
val (_, bocNode) = listOf(
startNotaryNode(providedName = DUMMY_NOTARY.name),
startNode(providedName = BOC.name),
startNode(providedName = BIGCORP_LEGAL_NAME)
).transpose().getOrThrow()
val bocApiAddress = startWebserver(bocNode).getOrThrow().listenAddress
val issueRequestParams = IssueRequestParams(1000, "USD", BIGCORP_LEGAL_NAME, "1", BOC.name, DUMMY_NOTARY.name)
assertTrue(BankOfCordaClientApi(bocApiAddress).requestWebIssue(issueRequestParams))
}
}
}

View File

@ -28,9 +28,10 @@ class BankOfCordaRPCClientTest {
val bocManager = User("bocManager", "password1", permissions = setOf(
startFlow<CashIssueAndPaymentFlow>()) + commonPermissions)
val bigCorpCFO = User("bigCorpCFO", "password2", permissions = emptySet<String>() + commonPermissions)
val nodeBankOfCordaFuture = startNotaryNode(BOC.name, rpcUsers = listOf(bocManager), validating = false)
val nodeBigCorporationFuture = startNode(providedName = BIGCORP_LEGAL_NAME, rpcUsers = listOf(bigCorpCFO))
val (nodeBankOfCorda, nodeBigCorporation) = listOf(nodeBankOfCordaFuture, nodeBigCorporationFuture).map { it.getOrThrow() }
val (nodeBankOfCorda, nodeBigCorporation) = listOf(
startNotaryNode(BOC.name, rpcUsers = listOf(bocManager), validating = false),
startNode(providedName = BIGCORP_LEGAL_NAME, rpcUsers = listOf(bigCorpCFO))
).map { it.getOrThrow() }
// Bank of Corda RPC Client
val bocClient = nodeBankOfCorda.rpcClientToNode()

View File

@ -57,7 +57,7 @@ private class BankOfCordaDriver {
try {
when (role) {
Role.ISSUER -> {
driver(dsl = {
driver(isDebug = true, extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")) {
startNotaryNode(providedName = DUMMY_NOTARY.name, validating = true)
val bankUser = User(
BANK_USERNAME,
@ -79,7 +79,7 @@ private class BankOfCordaDriver {
startNode(providedName = BIGCORP_LEGAL_NAME, rpcUsers = listOf(bigCorpUser))
startWebserver(bankOfCorda.get())
waitForAllNodesToFinish()
}, isDebug = true, extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset"))
}
}
else -> {
val requestParams = IssueRequestParams(options.valueOf(quantity), options.valueOf(currency), BIGCORP_LEGAL_NAME,

View File

@ -205,7 +205,7 @@ class NodeInterestRatesTest {
@Test
fun `network tearoff`() {
val mockNet = MockNetwork(initialiseSerialization = false, cordappPackages = listOf("net.corda.finance.contracts", "net.corda.irs"))
val n1 = mockNet.createNotaryNode()
val aliceNode = mockNet.createPartyNode(ALICE.name)
val oracleNode = mockNet.createNode().apply {
internals.registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java)
internals.registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java)
@ -218,7 +218,7 @@ class NodeInterestRatesTest {
val flow = FilteredRatesFlow(tx, oracleNode.info.chooseIdentity(), fixOf, BigDecimal("0.675"), BigDecimal("0.1"))
LogHelper.setLevel("rates")
mockNet.runNetwork()
val future = n1.services.startFlow(flow).resultFuture
val future = aliceNode.services.startFlow(flow).resultFuture
mockNet.runNetwork()
future.getOrThrow()
// We should now have a valid fix of our tx from the oracle.

View File

@ -140,7 +140,7 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
node1.internals.registerInitiatedFlow(FixingFlow.Fixer::class.java)
node2.internals.registerInitiatedFlow(FixingFlow.Fixer::class.java)
val notaryId = notary.info.legalIdentities[1]
val notaryId = notary.info.legalIdentities[0]
@InitiatingFlow
class StartDealFlow(val otherParty: Party,

View File

@ -5,6 +5,7 @@ import net.corda.cordform.CordformDefinition
import net.corda.cordform.CordformNode
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.div
import net.corda.core.node.services.NotaryService
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.config.BFTSMaRtConfiguration
import net.corda.node.services.config.NotaryConfig
@ -13,11 +14,7 @@ import net.corda.node.services.transactions.minCorrectReplicas
import net.corda.node.utilities.ServiceIdentityGenerator
import net.corda.testing.ALICE
import net.corda.testing.BOB
import net.corda.testing.internal.demorun.name
import net.corda.testing.internal.demorun.node
import net.corda.testing.internal.demorun.notary
import net.corda.testing.internal.demorun.rpcUsers
import net.corda.testing.internal.demorun.runNodes
import net.corda.testing.internal.demorun.*
fun main(args: Array<String>) = BFTNotaryCordform().runNodes()
@ -66,6 +63,10 @@ class BFTNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") {
}
override fun setup(context: CordformContext) {
ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it.toString()) }, clusterName, minCorrectReplicas(clusterSize))
ServiceIdentityGenerator.generateToDisk(
notaryNames.map { context.baseDirectory(it.toString()) },
clusterName,
NotaryService.constructId(validating = false, bft = true),
minCorrectReplicas(clusterSize))
}
}

View File

@ -5,6 +5,7 @@ import net.corda.cordform.CordformDefinition
import net.corda.cordform.CordformNode
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.div
import net.corda.core.node.services.NotaryService
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.config.RaftConfig
@ -58,6 +59,9 @@ class RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") {
}
override fun setup(context: CordformContext) {
ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it.toString()) }, clusterName)
ServiceIdentityGenerator.generateToDisk(
notaryNames.map { context.baseDirectory(it.toString()) },
clusterName,
NotaryService.constructId(validating = true, raft = true))
}
}

View File

@ -30,12 +30,12 @@ class TraderDemoTest {
startFlow<CommercialPaperIssueFlow>(),
all()))
driver(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance")) {
val (nodeA, nodeB, bankNode) = listOf(
val (_, nodeA, nodeB, bankNode) = listOf(
startNotaryNode(DUMMY_NOTARY.name, validating = false),
startNode(providedName = DUMMY_BANK_A.name, rpcUsers = listOf(demoUser)),
startNode(providedName = DUMMY_BANK_B.name, rpcUsers = listOf(demoUser)),
startNode(providedName = BOC.name, rpcUsers = listOf(bankUser)),
startNotaryNode(DUMMY_NOTARY.name, validating = false))
.map { (it.getOrThrow() as NodeHandle.InProcess).node }
startNode(providedName = BOC.name, rpcUsers = listOf(bankUser))
).map { (it.getOrThrow() as NodeHandle.InProcess).node }
nodeA.internals.registerInitiatedFlow(BuyerFlow::class.java)
val (nodeARpc, nodeBRpc) = listOf(nodeA, nodeB).map {

View File

@ -15,7 +15,7 @@ import net.corda.testing.driver.DriverDSLExposedInterface
/**
* A simple wrapper for objects provided by the integration test driver DSL. The fields are lazy so
* node construction won't start until you access the members. You can get one of these from the
* [alice], [bob] and [aliceBobAndNotary] functions.
* [alice], [bob] and [notaryAliceAndBob] functions.
*/
class PredefinedTestNode internal constructor(party: Party, driver: DriverDSLExposedInterface, ifNotaryIsValidating: Boolean?) {
val rpcUsers = listOf(User("admin", "admin", setOf("ALL"))) // TODO: Randomize?
@ -56,10 +56,10 @@ fun DriverDSLExposedInterface.notary(): PredefinedTestNode = PredefinedTestNode(
* Returns plain, entirely stock nodes pre-configured with the [ALICE], [BOB] and [DUMMY_NOTARY] X.500 names in that
* order. They have been started up in parallel and are now ready to use.
*/
fun DriverDSLExposedInterface.aliceBobAndNotary(): List<PredefinedTestNode> {
fun DriverDSLExposedInterface.notaryAliceAndBob(): List<PredefinedTestNode> {
val notary = notary()
val alice = alice()
val bob = bob()
val notary = notary()
listOf(alice.nodeFuture, bob.nodeFuture, notary.nodeFuture).transpose().get()
listOf(notary.nodeFuture, alice.nodeFuture, bob.nodeFuture).transpose().get()
return listOf(alice, bob, notary)
}

View File

@ -61,7 +61,6 @@ fun testNodeConfiguration(
return rigorousMock<MockableNodeConfiguration>().also {
doReturn(baseDirectory).whenever(it).baseDirectory
doReturn(myLegalName).whenever(it).myLegalName
doReturn(1).whenever(it).minimumPlatformVersion
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn("trustpass").whenever(it).trustStorePassword
doReturn(emptyList<User>()).whenever(it).rpcUsers

View File

@ -13,13 +13,13 @@ import net.corda.core.concurrent.CordaFuture
import net.corda.core.concurrent.firstOf
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.ThreadBox
import net.corda.core.internal.*
import net.corda.core.internal.concurrent.*
import net.corda.core.internal.div
import net.corda.core.internal.times
import net.corda.core.messaging.CordaRPCOps
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.NotaryService
import net.corda.core.toFuture
import net.corda.core.utilities.*
import net.corda.node.internal.Node
@ -36,6 +36,8 @@ import net.corda.nodeapi.internal.addShutdownHook
import net.corda.testing.ALICE
import net.corda.testing.BOB
import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.common.internal.NetworkParametersCopier
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.initialiseTestSerialization
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
import okhttp3.OkHttpClient
@ -43,10 +45,10 @@ import okhttp3.Request
import org.slf4j.Logger
import rx.Observable
import rx.observables.ConnectableObservable
import java.io.File
import java.net.*
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
import java.time.Duration
import java.time.Instant
import java.time.ZoneOffset.UTC
@ -59,6 +61,7 @@ import java.util.concurrent.TimeUnit.MILLISECONDS
import java.util.concurrent.TimeUnit.SECONDS
import java.util.concurrent.TimeoutException
import java.util.concurrent.atomic.AtomicInteger
import kotlin.collections.ArrayList
import kotlin.concurrent.thread
@ -69,7 +72,6 @@ import kotlin.concurrent.thread
*
* TODO this file is getting way too big, it should be split into several files.
*/
private val log: Logger = loggerFor<DriverDSL>()
private val DEFAULT_POLL_INTERVAL = 500.millis
@ -87,7 +89,7 @@ private val DRIVER_REQUIRED_PERMISSIONS = setOf(
*/
interface DriverDSLExposedInterface : CordformContext {
/**
* Starts a [net.corda.node.internal.Node] in a separate process.
* Start a node.
*
* @param defaultParameters The default parameters for the node. Allows the node to be configured in builder style
* when called from Java code.
@ -97,7 +99,7 @@ interface DriverDSLExposedInterface : CordformContext {
* @param rpcUsers List of users who are authorised to use the RPC system. Defaults to empty list.
* @param startInSameProcess Determines if the node should be started inside the same process the Driver is running
* in. If null the Driver-level value will be used.
* @return The [NodeInfo] of the started up node retrieved from the network map service.
* @return A [CordaFuture] on the [NodeHandle] to the node. The future will complete when the node is available.
*/
fun startNode(
defaultParameters: NodeParameters = NodeParameters(),
@ -113,7 +115,6 @@ interface DriverDSLExposedInterface : CordformContext {
rpcUsers: List<User> = emptyList(),
verifierType: VerifierType = VerifierType.InMemory,
customOverrides: Map<String, Any?> = emptyMap(),
//TODO Switch the default value
validating: Boolean = true): CordaFuture<NodeHandle>
/**
@ -122,9 +123,7 @@ interface DriverDSLExposedInterface : CordformContext {
* @param parameters The default parameters for the driver.
* @return The value returned in the [dsl] closure.
*/
fun startNode(parameters: NodeParameters): CordaFuture<NodeHandle> {
return startNode(defaultParameters = parameters)
}
fun startNode(parameters: NodeParameters): CordaFuture<NodeHandle> = startNode(defaultParameters = parameters)
fun startNodes(
nodes: List<CordformNode>,
@ -323,7 +322,6 @@ fun <A> driver(
systemProperties: Map<String, String> = defaultParameters.systemProperties,
useTestClock: Boolean = defaultParameters.useTestClock,
initialiseSerialization: Boolean = defaultParameters.initialiseSerialization,
startNodesInProcess: Boolean = defaultParameters.startNodesInProcess,
extraCordappPackagesToScan: List<String> = defaultParameters.extraCordappPackagesToScan,
dsl: DriverDSLExposedInterface.() -> A
@ -466,7 +464,8 @@ fun getTimestampAsDirectoryName(): String {
return DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(UTC).format(Instant.now())
}
class ListenProcessDeathException(hostAndPort: NetworkHostAndPort, listenProcess: Process) : CordaException("The process that was expected to listen on $hostAndPort has died with status: ${listenProcess.exitValue()}")
class ListenProcessDeathException(hostAndPort: NetworkHostAndPort, listenProcess: Process) :
CordaException("The process that was expected to listen on $hostAndPort has died with status: ${listenProcess.exitValue()}")
/**
* @throws ListenProcessDeathException if [listenProcess] dies before the check succeeds, i.e. the check can't succeed as intended.
@ -657,6 +656,7 @@ class DriverDSL(
private val nodeInfoFilesCopier = NodeInfoFilesCopier()
// Map from a nodes legal name to an observable emitting the number of nodes in its network map.
private val countObservables = mutableMapOf<CordaX500Name, Observable<Int>>()
private var networkParameters: NetworkParametersCopier? = null
class State {
val processes = ArrayList<CordaFuture<Process>>()
@ -749,6 +749,7 @@ class DriverDSL(
verifierType: VerifierType,
customOverrides: Map<String, Any?>,
validating: Boolean): CordaFuture<NodeHandle> {
createNetworkParameters(listOf(providedName), providedName, validating, "identity")
val config = customOverrides + NotaryConfig(validating).toConfigMap()
return startNode(providedName = providedName, rpcUsers = rpcUsers, verifierType = verifierType, customOverrides = config)
}
@ -788,21 +789,28 @@ class DriverDSL(
return config.toConfigMap()
}
val nodeNames = (0 until clusterSize).map { CordaX500Name("Notary Service $it", "Zurich", "CH") }
val paths = nodeNames.map { baseDirectory(it) }
ServiceIdentityGenerator.generateToDisk(paths, notaryName)
require(clusterSize > 0)
val nodeNames = (0 until clusterSize).map { notaryName.copy(organisation = "${notaryName.organisation}-$it") }
val notaryIdentity = createNetworkParameters(
nodeNames,
notaryName,
validating = true,
serviceId = NotaryService.constructId(validating = true, raft = true))
val clusterAddress = portAllocation.nextHostAndPort()
// Start the first node that will bootstrap the cluster
val firstNotaryFuture = startNode(
providedName = nodeNames.first(),
providedName = nodeNames[0],
rpcUsers = rpcUsers,
verifierType = verifierType,
customOverrides = notaryConfig(clusterAddress) + mapOf(
"database.serverNameTablePrefix" to if (nodeNames.isNotEmpty()) nodeNames.first().toString().replace(Regex("[^0-9A-Za-z]+"), "") else ""
"database.serverNameTablePrefix" to nodeNames[0].toString().replace(Regex("[^0-9A-Za-z]+"), "")
),
startInSameProcess = startInSameProcess
)
// All other nodes will join the cluster
val restNotaryFutures = nodeNames.drop(1).map {
val nodeAddress = portAllocation.nextHostAndPort()
@ -816,13 +824,20 @@ class DriverDSL(
}
return firstNotaryFuture.flatMap { firstNotary ->
val notaryParty = firstNotary.nodeInfo.legalIdentities[1] // TODO For now the second identity is notary identity.
restNotaryFutures.transpose().map { restNotaries ->
Pair(notaryParty, listOf(firstNotary) + restNotaries)
}
restNotaryFutures.transpose().map { restNotaries -> Pair(notaryIdentity, listOf(firstNotary) + restNotaries) }
}
}
private fun createNetworkParameters(notaryNodeNames: List<CordaX500Name>, notaryName: CordaX500Name, validating: Boolean, serviceId: String): Party {
check(networkParameters == null) { "Notaries must be started first" }
val identity = ServiceIdentityGenerator.generateToDisk(
notaryNodeNames.map { baseDirectory(it) },
notaryName,
serviceId)
networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(identity, validating))))
return identity
}
private fun queryWebserver(handle: NodeHandle, process: Process): WebserverHandle {
val protocol = if (handle.configuration.useHTTPS) "https://" else "http://"
val url = URL("$protocol${handle.webAddress}/api/status")
@ -854,9 +869,8 @@ class DriverDSL(
}
fun baseDirectory(nodeName: CordaX500Name): Path {
val nodeDirectoryName = String(nodeName.organisation.filter { !it.isWhitespace() }.toCharArray())
val nodeDirectoryName = nodeName.organisation.filter { !it.isWhitespace() }
return driverDirectory / nodeDirectoryName
}
override fun baseDirectory(nodeName: String): Path = baseDirectory(CordaX500Name.parse(nodeName))
@ -887,7 +901,7 @@ class DriverDSL(
private fun allNodesConnected(rpc: CordaRPCOps): CordaFuture<Int> {
val (snapshot, updates) = rpc.networkMapFeed()
val counterObservable = nodeCountObservable(snapshot.size, updates)
countObservables.put(rpc.nodeInfo().legalIdentities.first().name, counterObservable)
countObservables[rpc.nodeInfo().legalIdentities[0].name] = counterObservable
/* TODO: this might not always be the exact number of nodes one has to wait for,
* for example in the following sequence
* 1 start 3 nodes in order, A, B, C.
@ -897,7 +911,7 @@ class DriverDSL(
val requiredNodes = countObservables.size
// This is an observable which yield the minimum number of nodes in each node network map.
val smallestSeenNetworkMapSize = Observable.combineLatest(countObservables.values.toList()) { args : Array<Any> ->
val smallestSeenNetworkMapSize = Observable.combineLatest(countObservables.values.toList()) { args: Array<Any> ->
args.map { it as Int }.min() ?: 0
}
val future = smallestSeenNetworkMapSize.filter { it >= requiredNodes }.toFuture()
@ -905,15 +919,23 @@ class DriverDSL(
return future
}
private fun startNodeInternal(config: Config, webAddress: NetworkHostAndPort, startInProcess: Boolean?, maximumHeapSize: String): CordaFuture<NodeHandle> {
val nodeConfiguration = config.parseAsNodeConfiguration()
nodeInfoFilesCopier.addConfig(nodeConfiguration.baseDirectory)
private fun startNodeInternal(config: Config,
webAddress: NetworkHostAndPort,
startInProcess: Boolean?,
maximumHeapSize: String): CordaFuture<NodeHandle> {
val configuration = config.parseAsNodeConfiguration()
val baseDirectory = configuration.baseDirectory.createDirectories()
if (networkParameters == null) {
networkParameters = NetworkParametersCopier(testNetworkParameters(emptyList()))
}
networkParameters!!.install(baseDirectory)
nodeInfoFilesCopier.addConfig(baseDirectory)
val onNodeExit: () -> Unit = {
nodeInfoFilesCopier.removeConfig(nodeConfiguration.baseDirectory)
countObservables.remove(nodeConfiguration.myLegalName)
nodeInfoFilesCopier.removeConfig(baseDirectory)
countObservables.remove(configuration.myLegalName)
}
if (startInProcess ?: startNodesInProcess) {
val nodeAndThreadFuture = startInProcessNode(executorService, nodeConfiguration, config, cordappPackages)
val nodeAndThreadFuture = startInProcessNode(executorService, configuration, config, cordappPackages)
shutdownManager.registerShutdown(
nodeAndThreadFuture.map { (node, thread) ->
{
@ -923,21 +945,21 @@ class DriverDSL(
}
)
return nodeAndThreadFuture.flatMap { (node, thread) ->
establishRpc(nodeConfiguration, openFuture()).flatMap { rpc ->
establishRpc(configuration, openFuture()).flatMap { rpc ->
allNodesConnected(rpc).map {
NodeHandle.InProcess(rpc.nodeInfo(), rpc, nodeConfiguration, webAddress, node, thread, onNodeExit)
NodeHandle.InProcess(rpc.nodeInfo(), rpc, configuration, webAddress, node, thread, onNodeExit)
}
}
}
} else {
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
val processFuture = startOutOfProcessNode(executorService, nodeConfiguration, config, quasarJarPath, debugPort, systemProperties, cordappPackages, maximumHeapSize)
val processFuture = startOutOfProcessNode(executorService, configuration, config, quasarJarPath, debugPort, systemProperties, cordappPackages, maximumHeapSize)
registerProcess(processFuture)
return processFuture.flatMap { process ->
val processDeathFuture = poll(executorService, "process death") {
if (process.isAlive) null else process
}
establishRpc(nodeConfiguration, processDeathFuture).flatMap { rpc ->
establishRpc(configuration, processDeathFuture).flatMap { rpc ->
// Call waitUntilNetworkReady in background in case RPC is failing over:
val forked = executorService.fork {
allNodesConnected(rpc)
@ -945,11 +967,11 @@ class DriverDSL(
val networkMapFuture = forked.flatMap { it }
firstOf(processDeathFuture, networkMapFuture) {
if (it == processDeathFuture) {
throw ListenProcessDeathException(nodeConfiguration.p2pAddress, process)
throw ListenProcessDeathException(configuration.p2pAddress, process)
}
processDeathFuture.cancel(false)
log.info("Node handle is ready. NodeInfo: ${rpc.nodeInfo()}, WebAddress: ${webAddress}")
NodeHandle.OutOfProcess(rpc.nodeInfo(), rpc, nodeConfiguration, webAddress, debugPort, process,
log.info("Node handle is ready. NodeInfo: ${rpc.nodeInfo()}, WebAddress: $webAddress")
NodeHandle.OutOfProcess(rpc.nodeInfo(), rpc, configuration, webAddress, debugPort, process,
onNodeExit)
}
}
@ -985,12 +1007,19 @@ class DriverDSL(
// Write node.conf
writeConfig(nodeConf.baseDirectory, "node.conf", config)
// TODO pass the version in?
val node = Node(nodeConf, MOCK_VERSION_INFO, initialiseSerialization = false, cordappLoader = CordappLoader.createDefaultWithTestPackages(nodeConf, cordappPackages)).start()
val node = Node(
nodeConf,
MOCK_VERSION_INFO,
initialiseSerialization = false,
cordappLoader = CordappLoader.createDefaultWithTestPackages(nodeConf, cordappPackages))
.start()
val nodeThread = thread(name = nodeConf.myLegalName.organisation) {
node.internals.run()
}
node to nodeThread
}.flatMap { nodeAndThread -> addressMustBeBoundFuture(executorService, nodeConf.p2pAddress).map { nodeAndThread } }
}.flatMap {
nodeAndThread -> addressMustBeBoundFuture(executorService, nodeConf.p2pAddress).map { nodeAndThread }
}
}
private fun startOutOfProcessNode(
@ -1004,7 +1033,7 @@ class DriverDSL(
maximumHeapSize: String
): CordaFuture<Process> {
val processFuture = executorService.fork {
log.info("Starting out-of-process Node ${nodeConf.myLegalName.organisation}, debug port is " + debugPort ?: "not enabled")
log.info("Starting out-of-process Node ${nodeConf.myLegalName.organisation}, debug port is " + (debugPort ?: "not enabled"))
// Write node.conf
writeConfig(nodeConf.baseDirectory, "node.conf", config)
@ -1015,7 +1044,13 @@ class DriverDSL(
"java.io.tmpdir" to System.getProperty("java.io.tmpdir") // Inherit from parent process
)
// See experimental/quasar-hook/README.md for how to generate.
val excludePattern = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**)"
val excludePattern = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;" +
"com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;" +
"com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;" +
"io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;" +
"org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;" +
"org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;" +
"org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**)"
val extraJvmArguments = systemProperties.removeResolvedClasspath().map { "-D${it.key}=${it.value}" } +
"-javaagent:$quasarJarPath=$excludePattern"
val loggingLevel = if (debugPort == null) "INFO" else "DEBUG"
@ -1081,7 +1116,7 @@ class DriverDSL(
}
fun writeConfig(path: Path, filename: String, config: Config) {
path.toFile().mkdirs()
File("$path/$filename").writeText(config.root().render(ConfigRenderOptions.defaults()))
val configString = config.root().render(ConfigRenderOptions.defaults())
configString.byteInputStream().copyTo(path / filename, REPLACE_EXISTING)
}

View File

@ -16,11 +16,14 @@ import net.corda.node.services.config.parseAsNodeConfiguration
import net.corda.node.services.config.plus
import net.corda.nodeapi.User
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.common.internal.NetworkParametersCopier
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.driver.addressMustNotBeBoundFuture
import net.corda.testing.getFreeLocalPorts
import net.corda.testing.node.MockServices
import org.apache.logging.log4j.Level
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import java.nio.file.Path
@ -40,6 +43,7 @@ abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyLi
@JvmField
val tempFolder = TemporaryFolder()
private lateinit var defaultNetworkParameters: NetworkParametersCopier
private val nodes = mutableListOf<StartedNode<Node>>()
private val nodeInfos = mutableListOf<NodeInfo>()
@ -47,6 +51,11 @@ abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyLi
System.setProperty("consoleLogLevel", Level.DEBUG.name().toLowerCase())
}
@Before
fun init() {
defaultNetworkParameters = NetworkParametersCopier(testNetworkParameters(emptyList()))
}
/**
* Stops the network map node and all the nodes started by [startNode]. This is called automatically after each test
* but can also be called manually within a test.
@ -86,6 +95,7 @@ abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyLi
)
val parsedConfig = config.parseAsNodeConfiguration()
defaultNetworkParameters.install(baseDirectory)
val node = Node(
parsedConfig,
MockServices.MOCK_VERSION_INFO.copy(platformVersion = platformVersion),

View File

@ -15,6 +15,7 @@ fun CordformDefinition.clean() {
/**
* Creates and starts all nodes required for the demo.
*/
// TODO add notaries to cordform!
fun CordformDefinition.runNodes() = driver(
isDebug = true,
driverDirectory = driverDirectory,

View File

@ -18,7 +18,10 @@ import java.math.BigInteger
/**
* Network map cache with no backing map service.
*/
class MockNetworkMapCache(database: CordaPersistence, configuration: NodeConfiguration) : PersistentNetworkMapCache(database, configuration) {
class MockNetworkMapCache(
database: CordaPersistence,
configuration: NodeConfiguration
) : PersistentNetworkMapCache(database, configuration, emptyList()) {
private companion object {
val BANK_C = getTestPartyAndCertificate(CordaX500Name(organisation = "Bank C", locality = "London", country = "GB"), entropyToKeyPair(BigInteger.valueOf(1000)).public)
val BANK_D = getTestPartyAndCertificate(CordaX500Name(organisation = "Bank D", locality = "London", country = "GB"), entropyToKeyPair(BigInteger.valueOf(2000)).public)

View File

@ -7,13 +7,13 @@ import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.crypto.entropyToKeyPair
import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.createDirectories
import net.corda.core.internal.createDirectory
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.MessageRecipients
import net.corda.core.messaging.RPCOps
import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.KeyManagementService
import net.corda.core.serialization.SerializationWhitelist
@ -34,14 +34,17 @@ import net.corda.node.services.transactions.BFTSMaRt
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.node.utilities.ServiceIdentityGenerator
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.common.internal.NetworkParametersCopier
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.initialiseTestSerialization
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import net.corda.testing.testNodeConfiguration
import org.apache.activemq.artemis.utils.ReusableLatch
import org.slf4j.Logger
import sun.plugin.dom.DOMObjectFactory.createNode
import java.io.Closeable
import java.math.BigInteger
import java.nio.file.Path
@ -72,8 +75,6 @@ data class MockNetworkParameters(
}
/**
* @param notaryIdentity a set of service entries to use in place of the node's default service entries,
* for example where a node's service is part of a cluster.
* @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value,
* but can be overridden to cause nodes to have stable or colliding identity/service keys.
* @param configOverrides add/override behaviour of the [NodeConfiguration] mock object.
@ -82,12 +83,10 @@ data class MockNetworkParameters(
data class MockNodeParameters(
val forcedID: Int? = null,
val legalName: CordaX500Name? = null,
val notaryIdentity: Pair<ServiceInfo, KeyPair>? = null,
val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
val configOverrides: (NodeConfiguration) -> Any? = {}) {
fun setForcedID(forcedID: Int?) = copy(forcedID = forcedID)
fun setLegalName(legalName: CordaX500Name?) = copy(legalName = legalName)
fun setNotaryIdentity(notaryIdentity: Pair<ServiceInfo, KeyPair>?) = copy(notaryIdentity = notaryIdentity)
fun setEntropyRoot(entropyRoot: BigInteger) = copy(entropyRoot = entropyRoot)
fun setConfigOverrides(configOverrides: (NodeConfiguration) -> Any?) = copy(configOverrides = configOverrides)
}
@ -96,8 +95,8 @@ data class MockNodeArgs(
val config: NodeConfiguration,
val network: MockNetwork,
val id: Int,
val notaryIdentity: Pair<ServiceInfo, KeyPair>?,
val entropyRoot: BigInteger)
val entropyRoot: BigInteger
)
/**
* A mock node brings up a suite of in-memory services in a fast manner suitable for unit testing.
@ -129,10 +128,13 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
val messagingNetwork = InMemoryMessagingNetwork(networkSendManuallyPumped, servicePeerAllocationStrategy, busyLatch)
// A unique identifier for this network to segregate databases with the same nodeID but different networks.
private val networkId = random63BitValue()
private val _nodes = mutableListOf<MockNode>()
private lateinit var networkParameters: NetworkParametersCopier
private var notaryInfos: MutableList<NotaryInfo>? = ArrayList()
private val _nodes = ArrayList<MockNode>()
private val serializationEnv = initialiseTestSerialization(initialiseSerialization)
/** A read only view of the current set of executing nodes. */
val nodes: List<MockNode> get() = _nodes
private val serializationEnv = initialiseTestSerialization(initialiseSerialization)
init {
filesystem.getPath("/nodes").createDirectory()
@ -152,11 +154,11 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
}
override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean {
if (!isShutdown) {
return if (!isShutdown) {
flush()
return true
true
} else {
return super.awaitTermination(timeout, unit)
super.awaitTermination(timeout, unit)
}
}
}
@ -166,44 +168,69 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
TestClock(),
MOCK_VERSION_INFO,
CordappLoader.createDefaultWithTestPackages(args.config, args.network.cordappPackages),
args.network.busyLatch) {
args.network.busyLatch
) {
val mockNet = args.network
val id = args.id
internal val notaryIdentity = args.notaryIdentity
val entropyRoot = args.entropyRoot
private val entropyRoot = args.entropyRoot
var counter = entropyRoot
override val log: Logger = loggerFor<MockNode>()
override val serverThread: AffinityExecutor =
if (mockNet.threadPerNode)
if (mockNet.threadPerNode) {
ServiceAffinityExecutor("Mock node $id thread", 1)
else {
} else {
mockNet.sharedUserCount.incrementAndGet()
mockNet.sharedServerThread
}
override val started: StartedNode<MockNode>? get() = uncheckedCast(super.started)
override fun start(): StartedNode<MockNode> = uncheckedCast(super.start())
override fun start(): StartedNode<MockNode> {
installNetworkParameters()
val started: StartedNode<MockNode> = uncheckedCast(super.start())
advertiseNodeToNetwork(started)
return started
}
private fun installNetworkParameters() {
mockNet.notaryInfos?.let {
mockNet.networkParameters = NetworkParametersCopier(testNetworkParameters(it))
}
mockNet.notaryInfos = null
mockNet.networkParameters.install(configuration.baseDirectory)
}
private fun advertiseNodeToNetwork(newNode: StartedNode<MockNode>) {
mockNet.nodes
.mapNotNull { it.started }
.forEach { existingNode ->
newNode.services.networkMapCache.addNode(existingNode.info)
existingNode.services.networkMapCache.addNode(newNode.info)
}
}
// We only need to override the messaging service here, as currently everything that hits disk does so
// through the java.nio API which we are already mocking via Jimfs.
override fun makeMessagingService(legalIdentity: PartyAndCertificate): MessagingService {
override fun makeMessagingService(): MessagingService {
require(id >= 0) { "Node ID must be zero or positive, was passed: " + id }
return mockNet.messagingNetwork.createNodeWithID(
!mockNet.threadPerNode,
id,
serverThread,
getNotaryIdentity(),
myNotaryIdentity,
myLegalName,
database)
.start()
.getOrThrow()
database
).start().getOrThrow()
}
fun setMessagingServiceSpy(messagingServiceSpy: MessagingServiceSpy) {
network = messagingServiceSpy
}
override fun makeKeyManagementService(identityService: IdentityService): KeyManagementService {
return E2ETestKeyManagementService(identityService, partyKeys + (notaryIdentity?.let { setOf(it.second) } ?: emptySet()))
override fun makeKeyManagementService(identityService: IdentityService, keyPairs: Set<KeyPair>): KeyManagementService {
return E2ETestKeyManagementService(identityService, keyPairs)
}
override fun startMessagingService(rpcOps: RPCOps) {
@ -224,7 +251,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
override fun makeTransactionVerifierService() = InMemoryTransactionVerifierService(1)
override fun myAddresses() = emptyList<NetworkHostAndPort>()
override fun myAddresses(): List<NetworkHostAndPort> = emptyList()
// Allow unit tests to modify the serialization whitelist list before the node start,
// so they don't have to ServiceLoad test whitelists into all unit tests.
@ -269,17 +296,21 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
}
}
fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters()) = createUnstartedNode(parameters, defaultFactory)
fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters()): MockNode {
return createUnstartedNode(parameters, defaultFactory)
}
fun <N : MockNode> createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters(), nodeFactory: (MockNodeArgs) -> N): N {
return createNodeImpl(parameters, nodeFactory, false)
}
fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedNode<MockNode> = createNode(parameters, defaultFactory)
fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedNode<MockNode> {
return createNode(parameters, defaultFactory)
}
/** Like the other [createNode] but takes a [nodeFactory] and propagates its [MockNode] subtype. */
fun <N : MockNode> createNode(parameters: MockNodeParameters = MockNodeParameters(), nodeFactory: (MockNodeArgs) -> N): StartedNode<N> {
val node: StartedNode<N> = uncheckedCast(createNodeImpl(parameters, nodeFactory, true).started)!!
ensureAllNetworkMapCachesHaveAllNodeInfos()
return node
return uncheckedCast(createNodeImpl(parameters, nodeFactory, true).started)!!
}
private fun <N : MockNode> createNodeImpl(parameters: MockNodeParameters, nodeFactory: (MockNodeArgs) -> N, start: Boolean): N {
@ -290,13 +321,24 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
doReturn(makeTestDataSourceProperties("node_${id}_net_$networkId")).whenever(it).dataSourceProperties
parameters.configOverrides(it)
}
return nodeFactory(MockNodeArgs(config, this, id, parameters.notaryIdentity, parameters.entropyRoot)).apply {
if (start) {
start()
ensureAllNetworkMapCachesHaveAllNodeInfos()
val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot))
_nodes += node
config.notary?.let { notaryConfig ->
val notaryInfos = notaryInfos ?: throw IllegalStateException("Cannot add notaries once nodes have started")
if (!notaryConfig.isClusterConfig) {
// Create the node's main identity, which will also double as its notary identity
val identity = ServiceIdentityGenerator.generateToDisk(listOf(config.baseDirectory), config.myLegalName, "identity")
notaryInfos += NotaryInfo(identity, notaryConfig.validating)
}
_nodes.add(this)
}
if (start) {
node.start()
}
return node
}
fun baseDirectory(nodeId: Int): Path = filesystem.getPath("/nodes/$nodeId")
@ -309,7 +351,6 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
*/
@JvmOverloads
fun runNetwork(rounds: Int = -1) {
ensureAllNetworkMapCachesHaveAllNodeInfos()
check(!networkSendManuallyPumped)
fun pumpAll() = messagingNetwork.endpoints.map { it.pumpReceive(false) }
@ -324,23 +365,26 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
}
@JvmOverloads
fun createNotaryNode(parameters: MockNodeParameters = MockNodeParameters(legalName = DUMMY_NOTARY.name), validating: Boolean = true): StartedNode<MockNode> {
fun createNotaryNode(parameters: MockNodeParameters = MockNodeParameters(legalName = DUMMY_NOTARY.name),
validating: Boolean = true): StartedNode<MockNode> {
return createNotaryNode(parameters, validating, defaultFactory)
}
fun <N : MockNode> createNotaryNode(parameters: MockNodeParameters = MockNodeParameters(legalName = DUMMY_NOTARY.name),
validating: Boolean = true,
nodeFactory: (MockNodeArgs) -> N): StartedNode<N> {
return createNode(parameters.copy(configOverrides = {
doReturn(NotaryConfig(validating)).whenever(it).notary
parameters.configOverrides(it)
}), nodeFactory)
return createNode(
parameters.copy(configOverrides = {
doReturn(NotaryConfig(validating)).whenever(it).notary
parameters.configOverrides(it)
}),
nodeFactory
)
}
@JvmOverloads
fun createPartyNode(legalName: CordaX500Name? = null,
notaryIdentity: Pair<ServiceInfo, KeyPair>? = null): StartedNode<MockNode> {
return createNode(MockNodeParameters(legalName = legalName, notaryIdentity = notaryIdentity))
fun createPartyNode(legalName: CordaX500Name? = null): StartedNode<MockNode> {
return createNode(MockNodeParameters(legalName = legalName))
}
@Suppress("unused") // This is used from the network visualiser tool.
@ -355,21 +399,9 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
}
}
private fun ensureAllNetworkMapCachesHaveAllNodeInfos() {
val infos = nodes.mapNotNull { it.started?.info }
nodes.filter { it.hasDBConnection() }
.mapNotNull { it.started?.services?.networkMapCache }
.forEach {
for (nodeInfo in infos) {
it.addNode(nodeInfo)
}
}
}
fun startNodes() {
require(nodes.isNotEmpty())
nodes.forEach { it.started ?: it.start() }
ensureAllNetworkMapCachesHaveAllNodeInfos()
}
fun stopNodes() {

View File

@ -2,10 +2,14 @@ package net.corda.smoketesting
import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCConnection
import net.corda.client.rpc.internal.KryoClientSerializationScheme
import net.corda.core.internal.copyTo
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor
import net.corda.testing.common.internal.NetworkParametersCopier
import net.corda.testing.common.internal.testNetworkParameters
import java.nio.file.Path
import java.nio.file.Paths
import java.time.Instant
@ -45,9 +49,17 @@ class NodeProcess(
private val buildDirectory: Path = Paths.get("build"),
private val cordaJar: Path = Paths.get(this::class.java.getResource("/corda.jar").toURI())
) {
companion object {
private companion object {
val javaPath: Path = Paths.get(System.getProperty("java.home"), "bin", "java")
val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(systemDefault())
val defaultNetworkParameters = run {
// TODO withTestSerialization is in test-utils, which we don't have access to
KryoClientSerializationScheme.initialiseSerialization()
// There are no notaries in the network parameters for smoke test nodes. If this is required then we would
// need to introduce the concept of a "network" which predefines the notaries, like the driver and MockNetwork
NetworkParametersCopier(testNetworkParameters(emptyList()))
}
init {
try {
Class.forName("net.corda.node.Corda")
@ -66,8 +78,8 @@ class NodeProcess(
val nodeDir = baseDirectory(config).createDirectories()
log.info("Node directory: {}", nodeDir)
val confFile = nodeDir.resolve("node.conf").toFile()
confFile.writeText(config.toText())
config.toText().byteInputStream().copyTo(nodeDir / "node.conf")
defaultNetworkParameters.install(nodeDir)
val process = startNode(nodeDir)
val client = CordaRPCClient(NetworkHostAndPort("localhost", config.rpcPort))

View File

@ -1,6 +1,10 @@
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'com.jfrog.artifactory'
dependencies {
compile project(':core')
}
jar {
baseName 'corda-test-common'
}

View File

@ -0,0 +1,32 @@
package net.corda.testing.common.internal
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.entropyToKeyPair
import net.corda.core.crypto.sign
import net.corda.core.internal.copyTo
import net.corda.core.internal.div
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.serialize
import java.math.BigInteger
import java.nio.file.FileAlreadyExistsException
import java.nio.file.Path
class NetworkParametersCopier(networkParameters: NetworkParameters) {
private companion object {
val DUMMY_MAP_KEY = entropyToKeyPair(BigInteger.valueOf(123))
}
private val serializedNetworkParameters = networkParameters.let {
val serialize = it.serialize()
val signature = DUMMY_MAP_KEY.sign(serialize)
SignedData(serialize, signature).serialize()
}
fun install(dir: Path) {
try {
serializedNetworkParameters.open().copyTo(dir / "network-parameters")
} catch (e: FileAlreadyExistsException) {
// Leave the file untouched if it already exists
}
}
}

View File

@ -0,0 +1,18 @@
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 java.time.Instant
fun testNetworkParameters(notaries: List<NotaryInfo>): NetworkParameters {
return NetworkParameters(
minimumPlatformVersion = 1,
notaries = notaries,
modifiedTime = Instant.now(),
eventHorizon = 10000.days,
maxMessageSize = 40000,
maxTransactionSize = 40000,
epoch = 1
)
}

View File

@ -8,7 +8,6 @@ description 'Testing utilities for Corda'
dependencies {
compile project(':test-common')
compile project(':core')
compile project(':node')
compile project(':client:mock')

View File

@ -30,7 +30,6 @@ val DUMMY_NOTARY_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(20))
/** Dummy notary identity for tests and simulations */
val DUMMY_NOTARY_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(DUMMY_NOTARY)
val DUMMY_NOTARY: Party get() = Party(CordaX500Name(organisation = "Notary Service", locality = "Zurich", country = "CH"), DUMMY_NOTARY_KEY.public)
val DUMMY_NOTARY_SERVICE_NAME: CordaX500Name = DUMMY_NOTARY.name.copy(commonName = "corda.notary.validating")
val DUMMY_BANK_A_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(40)) }
/** Dummy bank identity for tests and simulations */

View File

@ -36,7 +36,6 @@ import net.corda.explorer.model.CordaView
import net.corda.finance.utils.CityDatabase
import net.corda.finance.utils.ScreenCoordinate
import net.corda.finance.utils.WorldMapLocation
import net.corda.nodeapi.internal.ServiceType
import tornadofx.*
class Network : CordaView() {
@ -45,6 +44,7 @@ class Network : CordaView() {
// Inject data.
private val myIdentity by observableValue(NetworkIdentityModel::myIdentity)
private val notaries by observableList(NetworkIdentityModel::notaryNodes)
private val notaryIdentities by observableList(NetworkIdentityModel::notaries)
private val peers by observableList(NetworkIdentityModel::parties)
private val transactions by observableList(TransactionDataModel::partiallyResolvedTransactions)
var centralPeer: String? = null
@ -103,7 +103,7 @@ class Network : CordaView() {
hgap = 5.0
vgap = 5.0
for (identity in identities) {
val isNotary = identity.name.commonName?.let { ServiceType.parse(it).isNotary() } == true
val isNotary = identity.party in notaryIdentities
row("${if (isNotary) "Notary " else ""}Public Key :") {
copyableLabel(SimpleObjectProperty(identity.owningKey.toBase58String()))
}

View File

@ -24,7 +24,7 @@ val dummyNotarisationTest = LoadTest<NotariseCommand, Unit>(
val issuerServices = MockServices(DUMMY_CASH_ISSUER_KEY)
val generateTx = Generator.pickOne(simpleNodes).flatMap { node ->
Generator.int().map {
val issueBuilder = DummyContract.generateInitial(it, notary.info.legalIdentities[1], DUMMY_CASH_ISSUER) // TODO notary choice
val issueBuilder = DummyContract.generateInitial(it, notary.info.legalIdentities[0], DUMMY_CASH_ISSUER) // TODO notary choice
val issueTx = issuerServices.signInitialTransaction(issueBuilder)
val asset = issueTx.tx.outRef<DummyContract.SingleOwnerState>(0)
val moveBuilder = DummyContract.move(asset, DUMMY_CASH_ISSUER.party)

View File

@ -6,6 +6,7 @@ import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.concurrent.*
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.NetworkHostAndPort
@ -248,7 +249,7 @@ data class VerifierDriverDSL(
val jdwpPort = if (driverDSL.isDebug) driverDSL.debugPortAllocation.nextPort() else null
val processFuture = driverDSL.executorService.fork {
val verifierName = CordaX500Name(organisation = "Verifier$id", locality = "London", country = "GB")
val baseDirectory = driverDSL.driverDirectory / verifierName.organisation
val baseDirectory = (driverDSL.driverDirectory / verifierName.organisation).createDirectories()
val config = createConfiguration(baseDirectory, address)
val configFilename = "verifier.conf"
writeConfig(baseDirectory, configFilename, config)

View File

@ -7,6 +7,7 @@ import net.corda.core.messaging.startFlow
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
@ -14,7 +15,6 @@ import net.corda.node.services.config.VerifierType
import net.corda.testing.ALICE
import net.corda.testing.ALICE_NAME
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.DUMMY_NOTARY_SERVICE_NAME
import org.junit.Test
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
@ -129,12 +129,12 @@ class VerifierTests {
@Test
fun `single verifier works with a node`() {
verifierDriver(extraCordappPackagesToScan = listOf("net.corda.finance.contracts")) {
val aliceFuture = startNode(providedName = ALICE.name)
val notaryFuture = startNotaryNode(DUMMY_NOTARY.name, verifierType = VerifierType.OutOfProcess)
val aliceNode = aliceFuture.get()
val notaryNode = notaryFuture.get()
val (notaryNode, aliceNode) = listOf(
startNotaryNode(DUMMY_NOTARY.name, verifierType = VerifierType.OutOfProcess),
startNode(providedName = ALICE.name)
).transpose().getOrThrow()
val alice = aliceNode.rpc.wellKnownPartyFromX500Name(ALICE_NAME)!!
val notary = notaryNode.rpc.notaryPartyFromX500Name(DUMMY_NOTARY_SERVICE_NAME)!!
val notary = notaryNode.rpc.notaryPartyFromX500Name(DUMMY_NOTARY.name)!!
startVerifier(notaryNode)
aliceNode.rpc.startFlow(::CashIssueFlow, 10.DOLLARS, OpaqueBytes.of(0), notary).returnValue.get()
notaryNode.waitUntilNumberOfVerifiers(1)