One demo definition for both IntelliJ and gradle (#697)

* Raft notary demo now defined in one place that both IntelliJ/driver and gradle/runnodes can run
* New module cordform-common for code common to cordformation and corda
* Add single notary demo
This commit is contained in:
Andrzej Cichocki 2017-05-18 15:58:35 +01:00 committed by GitHub
parent edf269dbe5
commit c8d71a96f5
28 changed files with 477 additions and 251 deletions

2
.gitignore vendored
View File

@ -32,6 +32,7 @@ lib/dokka.jar
.idea/libraries
.idea/shelf
.idea/dataSources
/gradle-plugins/.idea
# Include the -parameters compiler option by default in IntelliJ required for serialization.
!.idea/compiler.xml
@ -53,6 +54,7 @@ lib/dokka.jar
# Gradle:
# .idea/gradle.xml
# .idea/libraries
/gradle-plugins/gradle*
# Mongo Explorer plugin:
# .idea/mongoSettings.xml

2
.idea/compiler.xml generated
View File

@ -17,6 +17,8 @@
<module name="corda-webserver_integrationTest" target="1.8" />
<module name="corda-webserver_main" target="1.8" />
<module name="corda-webserver_test" target="1.8" />
<module name="cordform-common_main" target="1.8" />
<module name="cordform-common_test" target="1.8" />
<module name="core_main" target="1.8" />
<module name="core_test" target="1.8" />
<module name="demobench_main" target="1.8" />

View File

@ -41,6 +41,7 @@ buildscript {
ext.rxjava_version = '1.2.4'
ext.requery_version = '1.2.1'
ext.dokka_version = '0.9.13'
ext.eddsa_version = '0.2.0'
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
ext.java8_minUpdateVersion = '131'
@ -60,6 +61,7 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}"
classpath "org.ajoberstar:grgit:1.1.0"
classpath "net.i2p.crypto:eddsa:$eddsa_version" // Needed for ServiceIdentityGenerator in the build environment.
}
}

View File

@ -1,4 +1,4 @@
gradlePluginsVersion=0.12.0
gradlePluginsVersion=0.12.1
kotlinVersion=1.1.2
guavaVersion=21.0
bouncycastleVersion=1.56

View File

@ -0,0 +1,15 @@
apply plugin: 'java'
apply plugin: 'maven-publish'
apply plugin: 'net.corda.plugins.publish-utils'
repositories {
mavenCentral()
}
dependencies {
// TypeSafe Config: for simple and human friendly config files.
compile "com.typesafe:config:$typesafe_config_version"
// Bouncy Castle: for X.500 distinguished name manipulation
compile "org.bouncycastle:bcprov-jdk15on:$bouncycastle_version"
}

View File

@ -0,0 +1,8 @@
package net.corda.cordform;
import org.bouncycastle.asn1.x500.X500Name;
import java.nio.file.Path;
public interface CordformContext {
Path baseDirectory(X500Name nodeName);
}

View File

@ -0,0 +1,27 @@
package net.corda.cordform;
import org.bouncycastle.asn1.x500.X500Name;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.function.Consumer;
public abstract class CordformDefinition {
public final Path driverDirectory;
public final ArrayList<Consumer<? super CordformNode>> nodeConfigurers = new ArrayList<>();
public final X500Name networkMapNodeName;
public CordformDefinition(Path driverDirectory, X500Name networkMapNodeName) {
this.driverDirectory = driverDirectory;
this.networkMapNodeName = networkMapNodeName;
}
public void addNode(Consumer<? super CordformNode> configurer) {
nodeConfigurers.add(configurer);
}
/**
* Make arbitrary changes to the node directories before they are started.
* @param context Lookup of node directory by node name.
*/
public abstract void setup(CordformContext context);
}

View File

@ -0,0 +1,92 @@
package net.corda.cordform;
import static java.util.Collections.emptyList;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigValueFactory;
import java.util.List;
import java.util.Map;
public class CordformNode {
protected static final String DEFAULT_HOST = "localhost";
/**
* Name of the node.
*/
private String name;
public String getName() {
return name;
}
/**
* A list of advertised services ID strings.
*/
public List<String> advertisedServices = emptyList();
/**
* If running a distributed notary, a list of node addresses for joining the Raft cluster
*/
public List<String> notaryClusterAddresses = emptyList();
/**
* Set the RPC users for this node. This configuration block allows arbitrary configuration.
* The recommended current structure is:
* [[['username': "username_here", 'password': "password_here", 'permissions': ["permissions_here"]]]
* The above is a list to a map of keys to values using Groovy map and list shorthands.
*
* Incorrect configurations will not cause a DSL error.
*/
public List<Map<String, Object>> rpcUsers = emptyList();
protected Config config = ConfigFactory.empty();
public Config getConfig() {
return config;
}
/**
* Set the name of the node.
*
* @param name The node name.
*/
public void name(String name) {
this.name = name;
config = config.withValue("myLegalName", ConfigValueFactory.fromAnyRef(name));
}
/**
* Set the nearest city to the node.
*
* @param nearestCity The name of the nearest city to the node.
*/
public void nearestCity(String nearestCity) {
config = config.withValue("nearestCity", ConfigValueFactory.fromAnyRef(nearestCity));
}
/**
* Set the Artemis P2P port for this node.
*
* @param p2pPort The Artemis messaging queue port.
*/
public void p2pPort(Integer p2pPort) {
config = config.withValue("p2pAddress", ConfigValueFactory.fromAnyRef(DEFAULT_HOST + ':' + p2pPort));
}
/**
* Set the Artemis RPC port for this node.
*
* @param rpcPort The Artemis RPC queue port.
*/
public void rpcPort(Integer rpcPort) {
config = config.withValue("rpcAddress", ConfigValueFactory.fromAnyRef(DEFAULT_HOST + ':' + rpcPort));
}
/**
* Set the port which to bind the Copycat (Raft) node to
*
* @param notaryPort The Raft port.
*/
public void notaryNodePort(Integer notaryPort) {
config = config.withValue("notaryNodeAddress", ConfigValueFactory.fromAnyRef(DEFAULT_HOST + ':' + notaryPort));
}
}

View File

@ -63,7 +63,7 @@ dependencies {
compile "com.fasterxml.jackson.core:jackson-databind:${jackson_version}"
// Java ed25519 implementation. See https://github.com/str4d/ed25519-java/
compile 'net.i2p.crypto:eddsa:0.2.0'
compile "net.i2p.crypto:eddsa:$eddsa_version"
// Bouncy castle support needed for X509 certificate manipulation
compile "org.bouncycastle:bcprov-jdk15on:${bouncycastle_version}"

View File

@ -113,8 +113,17 @@ infix fun <T> ListenableFuture<T>.success(body: (T) -> Unit): ListenableFuture<T
infix fun <T> ListenableFuture<T>.failure(body: (Throwable) -> Unit): ListenableFuture<T> = apply { failure(RunOnCallerThread, body) }
@Suppress("UNCHECKED_CAST") // We need the awkward cast because otherwise F cannot be nullable, even though it's safe.
infix fun <F, T> ListenableFuture<F>.map(mapper: (F) -> T): ListenableFuture<T> = Futures.transform(this, { (mapper as (F?) -> T)(it) })
infix fun <F, T> ListenableFuture<F>.flatMap(mapper: (F) -> ListenableFuture<T>): ListenableFuture<T> = Futures.transformAsync(this) { mapper(it!!) }
inline fun <T, reified R> Collection<T>.mapToArray(transform: (T) -> R) = run {
val iterator = iterator()
var expected = 0
Array(size) {
expected++ == it || throw UnsupportedOperationException("Array constructor is non-sequential!")
transform(iterator.next())
}
}
/** Executes the given block and sets the future to either the result, or any exception that was thrown. */
inline fun <T> SettableFuture<T>.catch(block: () -> T) {
try {

View File

@ -8,6 +8,8 @@ buildscript {
// If you bump this version you must re-bootstrap the codebase. See the README for more information.
ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
ext.bouncycastle_version = constants.getProperty("bouncycastleVersion")
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
repositories {
mavenLocal()
@ -37,7 +39,7 @@ bintrayConfig {
projectUrl = 'https://github.com/corda/corda'
gpgSign = true
gpgPassphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE')
publications = ['cordformation', 'quasar-utils']
publications = ['cordformation', 'quasar-utils', 'cordform-common']
license {
name = 'Apache-2.0'
url = 'https://www.apache.org/licenses/LICENSE-2.0'

View File

@ -4,8 +4,6 @@ buildscript {
file("$projectDir/../../constants.properties").withInputStream { constants.load(it) }
ext.kotlin_version = constants.getProperty("kotlinVersion")
ext.bouncycastle_version = constants.getProperty("bouncycastleVersion")
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
repositories {
mavenCentral()
@ -46,11 +44,7 @@ dependencies {
noderunner "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
// TypeSafe Config: for simple and human friendly config files.
compile "com.typesafe:config:$typesafe_config_version"
// Bouncy Castle: for X.500 distinguished name manipulation
compile "org.bouncycastle:bcprov-jdk15on:${bouncycastle_version}"
compile project(':cordform-common')
}
task createNodeRunner(type: Jar, dependsOn: [classes]) {

View File

@ -1,19 +1,28 @@
package net.corda.plugins
import static org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
import net.corda.cordform.CordformContext
import net.corda.cordform.CordformDefinition
import org.apache.tools.ant.filters.FixCrLfFilter
import org.bouncycastle.asn1.x500.X500Name
import org.gradle.api.DefaultTask
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.TaskAction
import java.nio.file.Path
import java.nio.file.Paths
/**
* Creates nodes based on the configuration of this task in the gradle configuration DSL.
*
* See documentation for examples.
*/
class Cordform extends DefaultTask {
protected Path directory = Paths.get("./build/nodes")
protected List<Node> nodes = new ArrayList<Node>()
/**
* Optionally the name of a CordformDefinition subclass to which all configuration will be delegated.
*/
String definitionClass
protected def directory = Paths.get("build", "nodes")
private def nodes = new ArrayList<Node>()
protected String networkMapNodeName
/**
@ -42,7 +51,7 @@ class Cordform extends DefaultTask {
* @param configureClosure A node configuration that will be deployed.
*/
void node(Closure configureClosure) {
nodes << project.configure(new Node(project), configureClosure)
nodes << (Node) project.configure(new Node(project), configureClosure)
}
/**
@ -51,7 +60,7 @@ class Cordform extends DefaultTask {
* @param name The name of the node as specified in the node configuration DSL.
* @return A node instance.
*/
protected Node getNodeByName(String name) {
private Node getNodeByName(String name) {
for(Node node : nodes) {
if(node.name == name) {
return node
@ -64,7 +73,7 @@ class Cordform extends DefaultTask {
/**
* Installs the run script into the nodes directory.
*/
protected void installRunScript() {
private void installRunScript() {
project.copy {
from Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.jar")
fileMode 0755
@ -85,19 +94,49 @@ class Cordform extends DefaultTask {
}
}
/**
* The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
*/
private CordformDefinition loadCordformDefinition() {
def plugin = project.convention.getPlugin(JavaPluginConvention.class)
def classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
URL[] urls = classpath.files.collect { it.toURI().toURL() }
(CordformDefinition) new URLClassLoader(urls, CordformDefinition.classLoader).loadClass(definitionClass).newInstance()
}
/**
* This task action will create and install the nodes based on the node configurations added.
*/
@TaskAction
void build() {
String networkMapNodeName
if (null != definitionClass) {
def cd = loadCordformDefinition()
networkMapNodeName = cd.networkMapNodeName.toString()
cd.nodeConfigurers.each { nc ->
node { Node it ->
nc.accept it
it.rootDir directory
}
}
cd.setup new CordformContext() {
Path baseDirectory(X500Name nodeName) {
project.projectDir.toPath().resolve(getNodeByName(nodeName.toString()).nodeDir.toPath())
}
}
} else {
networkMapNodeName = this.networkMapNodeName
nodes.each {
it.rootDir directory
}
}
installRunScript()
Node networkMapNode = getNodeByName(networkMapNodeName)
def networkMapNode = getNodeByName(networkMapNodeName)
nodes.each {
if(it != networkMapNode) {
it.networkMapAddress(networkMapNode.getP2PAddress(), networkMapNodeName)
}
it.build(directory.toFile())
it.build()
}
}
}

View File

@ -22,7 +22,7 @@ class Cordformation implements Plugin<Project> {
* @param filePathInJar The file in the JAR, relative to root, you wish to access.
* @return A file handle to the file in the JAR.
*/
static File getPluginFile(Project project, String filePathInJar) {
protected static File getPluginFile(Project project, String filePathInJar) {
return project.rootProject.resources.text.fromArchiveEntry(project.rootProject.buildscript.configurations.classpath.find {
it.name.contains('cordformation')
}, filePathInJar).asFile()

View File

@ -1,33 +1,21 @@
package net.corda.plugins
import com.typesafe.config.*
import net.corda.cordform.CordformNode
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.style.BCStyle
import org.gradle.api.Project
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
/**
* Represents a node that will be installed.
*/
class Node {
class Node extends CordformNode {
static final String NODEJAR_NAME = 'corda.jar'
static final String WEBJAR_NAME = 'corda-webserver.jar'
static final String DEFAULT_HOST = 'localhost'
/**
* Name of the node.
*/
public String name
/**
* A list of advertised services ID strings.
*/
protected List<String> advertisedServices = []
/**
* If running a distributed notary, a list of node addresses for joining the Raft cluster
*/
protected List<String> notaryClusterAddresses = []
/**
* Set the list of CorDapps to install to the plugins directory. Each cordapp is a fully qualified Maven
* dependency name, eg: com.example:product-name:0.1
@ -35,39 +23,10 @@ class Node {
* @note Your app will be installed by default and does not need to be included here.
*/
protected List<String> cordapps = []
/**
* Set the RPC users for this node. This configuration block allows arbitrary configuration.
* The recommended current structure is:
* [[['username': "username_here", 'password': "password_here", 'permissions': ["permissions_here"]]]
* The above is a list to a map of keys to values using Groovy map and list shorthands.
*
* @note Incorrect configurations will not cause a DSL error.
*/
protected List<Map<String, Object>> rpcUsers = []
private Config config = ConfigFactory.empty()
private File nodeDir
protected File nodeDir
private Project project
/**
* Set the name of the node.
*
* @param name The node name.
*/
void name(String name) {
this.name = name
config = config.withValue("myLegalName", ConfigValueFactory.fromAnyRef(name))
}
/**
* Set the nearest city to the node.
*
* @param nearestCity The name of the nearest city to the node.
*/
void nearestCity(String nearestCity) {
config = config.withValue("nearestCity", ConfigValueFactory.fromAnyRef(nearestCity))
}
/**
* Sets whether this node will use HTTPS communication.
*
@ -88,26 +47,6 @@ class Node {
config = config.withValue("useTestClock", ConfigValueFactory.fromAnyRef(useTestClock))
}
/**
* Set the Artemis P2P port for this node.
*
* @param p2pPort The Artemis messaging queue port.
*/
void p2pPort(Integer p2pPort) {
config = config.withValue("p2pAddress",
ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$p2pPort".toString()))
}
/**
* Set the Artemis RPC port for this node.
*
* @param rpcPort The Artemis RPC queue port.
*/
void rpcPort(Integer rpcPort) {
config = config.withValue("rpcAddress",
ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$rpcPort".toString()))
}
/**
* Set the HTTP web server port for this node.
*
@ -118,14 +57,6 @@ class Node {
ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$webPort".toString()))
}
/**
* Set the port which to bind the Copycat (Raft) node to
*/
void notaryNodePort(Integer notaryPort) {
config = config.withValue("notaryNodeAddress",
ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$notaryPort".toString()))
}
/**
* Set the network map address for this node.
*
@ -155,16 +86,19 @@ class Node {
this.project = project
}
void build(File rootDir) {
protected void rootDir(Path rootDir) {
def dirName
try {
X500Name x500Name = new X500Name(name)
dirName = x500Name.getRDNs(BCStyle.CN).getAt(0).getFirst().getValue().toString()
} catch(IllegalArgumentException ex) {
} catch(IllegalArgumentException ignore) {
// Can't parse as an X500 name, use the full string
dirName = name
}
nodeDir = new File(rootDir, dirName.replaceAll("\\s",""))
nodeDir = new File(rootDir.toFile(), dirName.replaceAll("\\s",""))
}
protected void build() {
configureRpcUsers()
installCordaJar()
installWebserverJar()

View File

@ -1,4 +1,6 @@
rootProject.name = 'corda-gradle-plugins'
include 'publish-utils'
include 'quasar-utils'
include 'cordformation'
include 'cordformation'
include 'cordform-common'
project(':cordform-common').projectDir = new File("$settingsDir/../cordform-common")

View File

@ -54,6 +54,7 @@ dependencies {
compile project(':node-schemas')
compile project(':node-api')
compile project(':client:rpc')
compile project(':cordform-common')
compile "com.google.code.findbugs:jsr305:3.0.1"

View File

@ -19,6 +19,10 @@ import net.corda.core.node.services.ServiceType
import net.corda.core.utilities.*
import net.corda.node.LOGS_DIRECTORY_NAME
import net.corda.node.services.config.*
import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.services.config.VerifierType
import net.corda.node.services.config.configOf
import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.node.utilities.ServiceIdentityGenerator
@ -26,6 +30,8 @@ import net.corda.nodeapi.ArtemisMessagingComponent
import net.corda.nodeapi.User
import net.corda.nodeapi.config.SSLConfiguration
import net.corda.nodeapi.config.parseAs
import net.corda.cordform.CordformNode
import net.corda.cordform.CordformContext
import okhttp3.OkHttpClient
import okhttp3.Request
import org.bouncycastle.asn1.x500.X500Name
@ -57,7 +63,7 @@ private val log: Logger = loggerFor<DriverDSL>()
/**
* This is the interface that's exposed to DSL users.
*/
interface DriverDSLExposedInterface {
interface DriverDSLExposedInterface : CordformContext {
/**
* Starts a [net.corda.node.internal.Node] in a separate process.
*
@ -74,6 +80,8 @@ interface DriverDSLExposedInterface {
verifierType: VerifierType = VerifierType.InMemory,
customOverrides: Map<String, Any?> = emptyMap()): ListenableFuture<NodeHandle>
fun startNodes(nodes: List<CordformNode>): List<ListenableFuture<NodeHandle>>
/**
* Starts a distributed notary cluster.
*
@ -100,7 +108,7 @@ interface DriverDSLExposedInterface {
/**
* Starts a network map service node. Note that only a single one should ever be running, so you will probably want
* to set networkMapStrategy to FalseNetworkMap in your [driver] call.
* to set networkMapStartStrategy to Dedicated(false) in your [driver] call.
*/
fun startDedicatedNetworkMapService(): ListenableFuture<Unit>
@ -166,11 +174,6 @@ sealed class PortAllocation {
}
}
sealed class NetworkMapStartStrategy {
data class Dedicated(val startAutomatically: Boolean) : NetworkMapStartStrategy()
data class Nominated(val legalName: X500Name, val address: HostAndPort) : NetworkMapStartStrategy()
}
/**
* [driver] allows one to start up nodes like this:
* driver {
@ -418,7 +421,6 @@ class DriverDSL(
val networkMapStartStrategy: NetworkMapStartStrategy
) : DriverDSLInternalInterface {
private val dedicatedNetworkMapAddress = portAllocation.nextHostAndPort()
private val dedicatedNetworkMapLegalName = DUMMY_MAP.name
var _executorService: ListeningScheduledExecutorService? = null
val executorService get() = _executorService!!
var _shutdownManager: ShutdownManager? = null
@ -471,57 +473,59 @@ class DriverDSL(
}
}
// TODO move to cmopanion
private fun toServiceConfig(address: HostAndPort, legalName: X500Name) = mapOf(
"address" to address.toString(),
"legalName" to legalName.toString()
)
private fun networkMapServiceConfigLookup(networkMapCandidates: List<CordformNode>): (X500Name) -> Map<String, String>? {
return networkMapStartStrategy.run {
when (this) {
is NetworkMapStartStrategy.Dedicated -> {
serviceConfig(dedicatedNetworkMapAddress).let {
{ _: X500Name -> it }
}
}
is NetworkMapStartStrategy.Nominated -> {
serviceConfig(HostAndPort.fromString(networkMapCandidates.filter {
it.name == legalName.toString()
}.single().config.getString("p2pAddress"))).let {
{ nodeName: X500Name -> if (nodeName == legalName) null else it }
}
}
}
}
}
override fun startNode(
providedName: X500Name?,
advertisedServices: Set<ServiceInfo>,
rpcUsers: List<User>,
verifierType: VerifierType,
customOverrides: Map<String, Any?>
): ListenableFuture<NodeHandle> {
customOverrides: Map<String, Any?>): ListenableFuture<NodeHandle> {
val p2pAddress = portAllocation.nextHostAndPort()
val rpcAddress = portAllocation.nextHostAndPort()
val webAddress = portAllocation.nextHostAndPort()
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
// TODO: Derive name from the full picked name, don't just wrap the common name
val name = providedName ?: X509Utilities.getDevX509Name("${oneOf(names).commonName}-${p2pAddress.port}")
val baseDirectory = driverDirectory / name.commonName
val configOverrides = configOf(
return startNode(p2pAddress, webAddress, name, configOf(
"myLegalName" to name.toString(),
"p2pAddress" to p2pAddress.toString(),
"rpcAddress" to rpcAddress.toString(),
"webAddress" to webAddress.toString(),
"extraAdvertisedServiceIds" to advertisedServices.map { it.toString() },
"networkMapService" to when (networkMapStartStrategy) {
is NetworkMapStartStrategy.Dedicated -> toServiceConfig(dedicatedNetworkMapAddress, dedicatedNetworkMapLegalName)
is NetworkMapStartStrategy.Nominated -> networkMapStartStrategy.run {
if (name != legalName) {
toServiceConfig(address, legalName)
} else {
p2pAddress == address || throw IllegalArgumentException("Passed-in address $address of nominated network map $legalName is wrong, it should be: $p2pAddress")
null
}
}
},
"networkMapService" to networkMapServiceConfigLookup(emptyList())(name),
"useTestClock" to useTestClock,
"rpcUsers" to rpcUsers.map { it.toMap() },
"verifierType" to verifierType.name
) + customOverrides
) + customOverrides)
}
private fun startNode(p2pAddress: HostAndPort, webAddress: HostAndPort, nodeName: X500Name, configOverrides: Config) = run {
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
val config = ConfigHelper.loadConfig(
baseDirectory = baseDirectory,
baseDirectory = baseDirectory(nodeName),
allowMissingConfig = true,
configOverrides = configOverrides)
val configuration = config.parseAs<FullNodeConfiguration>()
val processFuture = startNode(executorService, configuration, config, quasarJarPath, debugPort, systemProperties)
registerProcess(processFuture)
return processFuture.flatMap { process ->
processFuture.flatMap { process ->
// We continue to use SSL enabled port for RPC when its for node user.
establishRpc(p2pAddress, configuration).flatMap { rpc ->
rpc.waitUntilRegisteredWithNetworkMap().map {
@ -531,6 +535,22 @@ class DriverDSL(
}
}
override fun startNodes(nodes: List<CordformNode>): List<ListenableFuture<NodeHandle>> {
val networkMapServiceConfigLookup = networkMapServiceConfigLookup(nodes)
return nodes.map {
val p2pAddress = HostAndPort.fromString(it.config.getString("p2pAddress")); portAllocation.nextHostAndPort()
portAllocation.nextHostAndPort() // rpcAddress
val webAddress = portAllocation.nextHostAndPort()
val name = X500Name(it.name)
startNode(p2pAddress, webAddress, name, it.config + mapOf(
"extraAdvertisedServiceIds" to it.advertisedServices,
"networkMapService" to networkMapServiceConfigLookup(name),
"rpcUsers" to it.rpcUsers,
"notaryClusterAddresses" to it.notaryClusterAddresses
))
}
}
override fun startNotaryCluster(
notaryName: X500Name,
clusterSize: Int,
@ -539,7 +559,7 @@ class DriverDSL(
rpcUsers: List<User>
): ListenableFuture<Pair<Party, List<NodeHandle>>> {
val nodeNames = (1..clusterSize).map { DUMMY_NOTARY.name.appendToCommonName(it.toString()) }
val paths = nodeNames.map { driverDirectory / it.commonName }
val paths = nodeNames.map { baseDirectory(it) }
ServiceIdentityGenerator.generateToDisk(paths, type.id, notaryName)
val serviceInfo = ServiceInfo(type, notaryName)
@ -592,20 +612,22 @@ class DriverDSL(
Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build())
)
_shutdownManager = ShutdownManager(executorService)
if (networkMapStartStrategy is NetworkMapStartStrategy.Dedicated && networkMapStartStrategy.startAutomatically) {
if (networkMapStartStrategy.startDedicated) {
startDedicatedNetworkMapService()
}
}
override fun baseDirectory(nodeName: X500Name) = driverDirectory / nodeName.commonName.replace(WHITESPACE, "")
override fun startDedicatedNetworkMapService(): ListenableFuture<Unit> {
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
val apiAddress = portAllocation.nextHostAndPort().toString()
val baseDirectory = driverDirectory / dedicatedNetworkMapLegalName.commonName
val networkMapLegalName = networkMapStartStrategy.legalName
val config = ConfigHelper.loadConfig(
baseDirectory = baseDirectory,
baseDirectory = baseDirectory(networkMapLegalName),
allowMissingConfig = true,
configOverrides = configOf(
"myLegalName" to dedicatedNetworkMapLegalName.toString(),
"myLegalName" to networkMapLegalName.toString(),
// TODO: remove the webAddress as NMS doesn't need to run a web server. This will cause all
// node port numbers to be shifted, so all demos and docs need to be updated accordingly.
"webAddress" to apiAddress,

View File

@ -0,0 +1,23 @@
package net.corda.node.driver
import com.google.common.net.HostAndPort
import net.corda.core.utilities.DUMMY_MAP
import org.bouncycastle.asn1.x500.X500Name
sealed class NetworkMapStartStrategy {
internal abstract val startDedicated: Boolean
internal abstract val legalName: X500Name
internal fun serviceConfig(address: HostAndPort) = mapOf(
"address" to address.toString(),
"legalName" to legalName.toString()
)
class Dedicated(startAutomatically: Boolean) : NetworkMapStartStrategy() {
override val startDedicated = startAutomatically
override val legalName = DUMMY_MAP.name
}
class Nominated(override val legalName: X500Name) : NetworkMapStartStrategy() {
override val startDedicated = false
}
}

View File

@ -4,12 +4,12 @@ import net.corda.core.crypto.CompositeKey
import net.corda.core.identity.Party
import net.corda.core.crypto.generateKeyPair
import net.corda.core.serialization.serialize
import net.corda.core.serialization.storageKryo
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.trace
import org.bouncycastle.asn1.x500.X500Name
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
object ServiceIdentityGenerator {
private val log = loggerFor<ServiceIdentityGenerator>()
@ -36,17 +36,8 @@ object ServiceIdentityGenerator {
val privateKeyFile = "$serviceId-private-key"
val publicKeyFile = "$serviceId-public"
notaryParty.writeToFile(dir.resolve(publicKeyFile))
keyPair.serialize().writeToFile(dir.resolve(privateKeyFile))
// Use storageKryo as our whitelist is not available in the gradle build environment:
keyPair.serialize(storageKryo()).writeToFile(dir.resolve(privateKeyFile))
}
}
}
fun main(args: Array<String>) {
val dirs = args[0].split("|").map { Paths.get(it) }
val serviceId = args[1]
val serviceName = X500Name(args[2])
val quorumSize = args.getOrNull(3)?.toInt() ?: 1
println("Generating service identity for \"$serviceName\"")
ServiceIdentityGenerator.generateToDisk(dirs, serviceId, serviceName, quorumSize)
}

View File

@ -8,13 +8,6 @@ apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'net.corda.plugins.cordformation'
apply plugin: 'maven-publish'
ext {
deployTo = "build/nodes"
notaryType = "corda.notary.validating.raft"
notaryName = "CN=Raft,O=R3,OU=corda,L=Zurich,C=CH"
advertisedNotary = "$notaryType|$notaryName"
}
configurations {
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime
@ -31,6 +24,7 @@ dependencies {
compile project(':client:jfx')
compile project(':client:rpc')
compile project(':test-utils')
compile project(':cordform-common')
// Javax is required for webapis
compile "org.glassfish.jersey.core:jersey-server:${jersey_version}"
@ -55,76 +49,15 @@ publishing {
}
}
task cleanNodes {
doLast {
delete deployTo
}
task deployNodesSingle(type: Cordform, dependsOn: 'jar') {
definitionClass = 'net.corda.notarydemo.SingleNotaryCordform'
}
task generateNotaryIdentity(type: JavaExec, dependsOn: 'cleanNodes') {
classpath = sourceSets.main.runtimeClasspath
main = "net.corda.node.utilities.ServiceIdentityGeneratorKt"
def nodeDirs = ["$deployTo/Notary1",
"$deployTo/Notary2",
"$deployTo/Notary3"].join("|")
args = [nodeDirs, notaryType, notaryName]
}
task deployNodes(type: Cordform, dependsOn: ['jar', generateNotaryIdentity]) {
directory deployTo
networkMap "CN=Notary 1,O=R3,OU=corda,L=London,C=UK"
node {
name "CN=Alice Corp,O=Alice Corp,L=London,C=UK"
nearestCity "London"
advertisedServices = []
p2pPort 10002
rpcPort 10003
cordapps = []
rpcUsers = [['username': "demo", 'password': "demo", 'permissions': [
'StartFlow.net.corda.notarydemo.flows.DummyIssueAndMove',
'StartFlow.net.corda.notarydemo.flows.RPCStartableNotaryFlowClient'
]]]
}
node {
name "CN=Bob Plc,O=Bob Plc,L=London,C=UK"
nearestCity "New York"
advertisedServices = []
p2pPort 10005
rpcPort 10006
cordapps = []
}
node {
name "CN=Notary 1,O=R3,OU=corda,L=London,C=UK"
nearestCity "London"
advertisedServices = [advertisedNotary]
p2pPort 10009
rpcPort 10010
cordapps = []
notaryNodePort 10008
}
node {
name "CN=Notary 2,O=R3,OU=corda,L=London,C=UK"
nearestCity "London"
advertisedServices = [advertisedNotary]
p2pPort 10013
rpcPort 10014
cordapps = []
notaryNodePort 10012
notaryClusterAddresses = ["localhost:10008"]
}
node {
name "CN=Notary 3,O=R3,OU=corda,L=London,C=UK"
nearestCity "London"
advertisedServices = [advertisedNotary]
p2pPort 10017
rpcPort 10018
cordapps = []
notaryNodePort 10016
notaryClusterAddresses = ["localhost:10008"]
}
task deployNodesRaft(type: Cordform, dependsOn: 'jar') {
definitionClass = 'net.corda.notarydemo.RaftNotaryCordform'
}
task notarise(type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath
main = 'net.corda.notarydemo.NotaryDemoKt'
main = 'net.corda.notarydemo.NotariseKt'
}

View File

@ -0,0 +1,28 @@
package net.corda.demorun
import net.corda.node.driver.NetworkMapStartStrategy
import net.corda.node.driver.PortAllocation
import net.corda.node.driver.driver
import net.corda.cordform.CordformDefinition
import net.corda.cordform.CordformNode
fun CordformDefinition.node(configure: CordformNode.() -> Unit) = addNode { cordformNode -> cordformNode.configure() }
fun CordformDefinition.clean() {
System.err.println("Deleting: $driverDirectory")
driverDirectory.toFile().deleteRecursively()
}
/**
* Creates and starts all nodes required for the demo.
*/
fun CordformDefinition.runNodes() = driver(
isDebug = true,
driverDirectory = driverDirectory,
networkMapStartStrategy = NetworkMapStartStrategy.Nominated(networkMapNodeName),
portAllocation = PortAllocation.Incremental(10001)
) {
setup(this)
startNodes(nodeConfigurers.map { configurer -> CordformNode().also { configurer.accept(it) } })
waitForAllNodesToFinish()
}

View File

@ -0,0 +1,9 @@
package net.corda.notarydemo
import net.corda.demorun.clean
fun main(args: Array<String>) {
listOf(SingleNotaryCordform, RaftNotaryCordform).forEach {
it.clean()
}
}

View File

@ -1,29 +0,0 @@
package net.corda.notarydemo
import com.google.common.net.HostAndPort
import net.corda.core.crypto.appendToCommonName
import net.corda.core.div
import net.corda.core.utilities.ALICE
import net.corda.core.utilities.BOB
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.node.driver.NetworkMapStartStrategy
import net.corda.node.driver.PortAllocation
import net.corda.node.driver.driver
import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.nodeapi.User
import net.corda.notarydemo.flows.DummyIssueAndMove
import net.corda.notarydemo.flows.RPCStartableNotaryFlowClient
import org.bouncycastle.asn1.x500.X500Name
/** Creates and starts all nodes required for the demo. */
fun main(args: Array<String>) {
val demoUser = listOf(User("demo", "demo", setOf(startFlowPermission<DummyIssueAndMove>(), startFlowPermission<RPCStartableNotaryFlowClient>())))
val networkMap = NetworkMapStartStrategy.Nominated(DUMMY_NOTARY.name.appendToCommonName("1"), HostAndPort.fromParts("localhost", 10009))
driver(isDebug = true, driverDirectory = "build" / "notary-demo-nodes", networkMapStartStrategy = networkMap, portAllocation = PortAllocation.Incremental(10001)) {
startNode(ALICE.name, rpcUsers = demoUser)
startNode(BOB.name)
startNotaryCluster(X500Name("CN=Raft,O=R3,OU=corda,L=Zurich,C=CH"), clusterSize = 3, type = RaftValidatingNotaryService.type)
waitForAllNodesToFinish()
}
}

View File

@ -0,0 +1,73 @@
package net.corda.notarydemo
import net.corda.core.crypto.appendToCommonName
import net.corda.core.div
import net.corda.core.node.services.ServiceInfo
import net.corda.core.utilities.ALICE
import net.corda.core.utilities.BOB
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.demorun.node
import net.corda.demorun.runNodes
import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.node.utilities.ServiceIdentityGenerator
import net.corda.nodeapi.User
import net.corda.notarydemo.flows.DummyIssueAndMove
import net.corda.notarydemo.flows.RPCStartableNotaryFlowClient
import net.corda.cordform.CordformDefinition
import net.corda.cordform.CordformContext
import org.bouncycastle.asn1.x500.X500Name
fun main(args: Array<String>) = RaftNotaryCordform.runNodes()
private val notaryNames = (1..3).map { DUMMY_NOTARY.name.appendToCommonName(" $it") }
object RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes", notaryNames[0]) {
private val advertisedNotary = ServiceInfo(RaftValidatingNotaryService.type, X500Name("CN=Raft,O=R3,OU=corda,L=Zurich,C=CH"))
init {
node {
name(ALICE.name.toString())
nearestCity("London")
p2pPort(10002)
rpcPort(10003)
rpcUsers = listOf(User("demo", "demo", setOf(startFlowPermission<DummyIssueAndMove>(), startFlowPermission<RPCStartableNotaryFlowClient>())).toMap())
}
node {
name(BOB.name.toString())
nearestCity("New York")
p2pPort(10005)
rpcPort(10006)
}
node {
name(notaryNames[0].toString())
nearestCity("London")
advertisedServices = listOf(advertisedNotary.toString())
p2pPort(10009)
rpcPort(10010)
notaryNodePort(10008)
}
node {
name(notaryNames[1].toString())
nearestCity("London")
advertisedServices = listOf(advertisedNotary.toString())
p2pPort(10013)
rpcPort(10014)
notaryNodePort(10012)
notaryClusterAddresses = listOf("localhost:10008")
}
node {
name(notaryNames[2].toString())
nearestCity("London")
advertisedServices = listOf(advertisedNotary.toString())
p2pPort(10017)
rpcPort(10018)
notaryNodePort(10016)
notaryClusterAddresses = listOf("localhost:10008")
}
}
override fun setup(context: CordformContext) {
ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it) }, advertisedNotary.type.id, advertisedNotary.name!!)
}
}

View File

@ -0,0 +1,46 @@
package net.corda.notarydemo
import net.corda.core.div
import net.corda.core.node.services.ServiceInfo
import net.corda.core.utilities.ALICE
import net.corda.core.utilities.BOB
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.demorun.node
import net.corda.demorun.runNodes
import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.ValidatingNotaryService
import net.corda.nodeapi.User
import net.corda.notarydemo.flows.DummyIssueAndMove
import net.corda.notarydemo.flows.RPCStartableNotaryFlowClient
import net.corda.cordform.CordformDefinition
import net.corda.cordform.CordformContext
fun main(args: Array<String>) = SingleNotaryCordform.runNodes()
object SingleNotaryCordform : CordformDefinition("build" / "notary-demo-nodes", DUMMY_NOTARY.name) {
init {
node {
name(ALICE.name.toString())
nearestCity("London")
p2pPort(10002)
rpcPort(10003)
rpcUsers = listOf(User("demo", "demo", setOf(startFlowPermission<DummyIssueAndMove>(), startFlowPermission<RPCStartableNotaryFlowClient>())).toMap())
}
node {
name(BOB.name.toString())
nearestCity("New York")
p2pPort(10005)
rpcPort(10006)
}
node {
name(DUMMY_NOTARY.name.toString())
nearestCity("London")
advertisedServices = listOf(ServiceInfo(ValidatingNotaryService.type).toString())
p2pPort(10009)
rpcPort(10010)
notaryNodePort(10008)
}
}
override fun setup(context: CordformContext) {}
}

View File

@ -31,4 +31,5 @@ include 'samples:irs-demo'
include 'samples:network-visualiser'
include 'samples:simm-valuation-demo'
include 'samples:raft-notary-demo'
include 'samples:bank-of-corda-demo'
include 'samples:bank-of-corda-demo'
include 'cordform-common'