diff --git a/docs/generate-docsite.sh b/docs/generate-docsite.sh index b3bc4405ee..51c3e4e1d8 100755 --- a/docs/generate-docsite.sh +++ b/docs/generate-docsite.sh @@ -21,7 +21,7 @@ fi virtualenv -p python2.7 virtualenv fi . virtualenv/bin/activate - if [ ! -d "docs/virtualenv/lib/python2.7/site-packages/sphinx" ] + if [ ! -d "virtualenv/lib/python2.7/site-packages/sphinx" ] then echo "Installing pip dependencies ... " pip install -r requirements.txt diff --git a/docs/source/corda-configuration-files.rst b/docs/source/corda-configuration-files.rst index e7e9d441fb..e0fbbdf9a7 100644 --- a/docs/source/corda-configuration-files.rst +++ b/docs/source/corda-configuration-files.rst @@ -23,27 +23,8 @@ Configuration File Examples General node configuration file for hosting the IRSDemo services. -.. code-block:: text - - basedir : "./nodea" - myLegalName : "Bank A" - nearestCity : "London" - keyStorePassword : "cordacadevpass" - trustStorePassword : "trustpass" - dataSourceProperties : { - dataSourceClassName : org.h2.jdbcx.JdbcDataSource - "dataSource.url" : "jdbc:h2:"${basedir}"/persistence" - "dataSource.user" : sa - "dataSource.password" : "" - } - artemisAddress : "localhost:31337" - webAddress : "localhost:31339" - extraAdvertisedServiceIds: "corda.interest_rates" - networkMapAddress : "localhost:12345" - useHTTPS : false - rpcUsers : [ - { user=user1, password=letmein, permissions=[ cash ] } - ] +.. literalinclude:: example-code/src/main/resources/example-node.conf + :language: cfg NetworkMapService plus Simple Notary configuration file. diff --git a/docs/source/example-code/src/main/resources/example-network-map-node.conf b/docs/source/example-code/src/main/resources/example-network-map-node.conf new file mode 100644 index 0000000000..6a5c944631 --- /dev/null +++ b/docs/source/example-code/src/main/resources/example-network-map-node.conf @@ -0,0 +1,9 @@ +basedir : "./nameserver" +myLegalName : "Notary Service" +nearestCity : "London" +keyStorePassword : "cordacadevpass" +trustStorePassword : "trustpass" +artemisAddress : "my-network-map:10000" +webAddress : "localhost:10001" +extraAdvertisedServiceIds: "" +useHTTPS : false diff --git a/docs/source/example-code/src/main/resources/example-node.conf b/docs/source/example-code/src/main/resources/example-node.conf new file mode 100644 index 0000000000..c52a6f3e01 --- /dev/null +++ b/docs/source/example-code/src/main/resources/example-node.conf @@ -0,0 +1,19 @@ +basedir : "./nodea" +myLegalName : "Bank A" +nearestCity : "London" +keyStorePassword : "cordacadevpass" +trustStorePassword : "trustpass" +dataSourceProperties : { + dataSourceClassName : org.h2.jdbcx.JdbcDataSource + "dataSource.url" : "jdbc:h2:"${basedir}"/persistence" + "dataSource.user" : sa + "dataSource.password" : "" +} +artemisAddress : "my-corda-node:10002" +webAddress : "localhost:10003" +extraAdvertisedServiceIds: "corda.interest_rates" +networkMapAddress : "my-network-map:10000" +useHTTPS : false +rpcUsers : [ + { user=user1, password=letmein, permissions=[ StartProtocol.net.corda.protocols.CashProtocol ] } +] diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/ExampleNodeConfTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/ExampleNodeConfTest.kt new file mode 100644 index 0000000000..7a898d17b1 --- /dev/null +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/ExampleNodeConfTest.kt @@ -0,0 +1,32 @@ +package net.corda.docs + +import net.corda.node.services.config.ConfigHelper +import net.corda.node.services.config.FullNodeConfiguration +import org.junit.Test +import java.nio.file.Paths +import kotlin.reflect.declaredMemberProperties + +class ExampleNodeConfTest { + @Test + fun exampleNodeConfParsesFine() { + val exampleNodeConfFilenames = arrayOf( + "example-node.conf", + "example-network-map-node.conf" + ) + + exampleNodeConfFilenames.forEach { + println("Checking $it") + val configResource = ExampleNodeConfTest::class.java.classLoader.getResource(it) + val nodeConfig = FullNodeConfiguration( + ConfigHelper.loadConfig( + baseDirectoryPath = Paths.get("some-example-base-dir"), + configFileOverride = Paths.get(configResource.toURI()) + ) + ) + // Force the config fields as they are resolved lazily + nodeConfig.javaClass.kotlin.declaredMemberProperties.forEach { member -> + member.get(nodeConfig) + } + } + } +} \ No newline at end of file diff --git a/docs/source/setting-up-a-corda-network.rst b/docs/source/setting-up-a-corda-network.rst new file mode 100644 index 0000000000..f840d6fdda --- /dev/null +++ b/docs/source/setting-up-a-corda-network.rst @@ -0,0 +1,81 @@ +.. _log4j2: http://logging.apache.org/log4j/2.x/ + +Introduction - What is a corda network? +======================================================== + +A Corda network consists of a number of machines running nodes, including a single node operating as the network map service. These nodes communicate using persistent protocols in order to create and validate transactions. + +There are four broader categories of functionality one such node may have. These pieces of functionality are provided as services, and one node may run several of them. + +* Network map: The node running the network map provides a way to resolve identities to physical node addresses and associated public keys. +* Notary: Nodes running a notary service witness state spends and have the final say in whether a transaction is a double-spend or not. +* Oracle: Network services that link the ledger to the outside world by providing facts that affect the validity of transactions. +* Regular node: All nodes have a vault and may start protocols communicating with other nodes, notaries and oracles and evolve their private ledger. + +Setting up your own network +=========================== + +Certificates +------------ + +If two nodes are to communicate successfully then both need to have +each other's root certificate in their truststores. The simplest way +to achieve this is to have all nodes sign off of a single root. + +Later R3 will provide this root for production use, but for testing you +can use ``certSigningRequestUtility.jar`` to generate a node +certificate with a fixed test root: + +.. sourcecode:: bash + + # Build the jars + ./gradlew buildCordaJAR + # Generate certificate + java -jar build/libs/certSigningRequestUtility.jar --base-dir NODE_DIRECTORY/ + +Configuration +------------- + +A node can be configured by adding/editing ``node.conf`` in the node's directory. For details see :doc:`corda-configuration-files` + +An example configuration: + +.. literalinclude:: example-code/src/main/resources/example-node.conf + :language: cfg + +The most important fields regarding network configuration are: + +* ``artemisAddress``: This specifies a host and port. Note that the address bound will **NOT** be ``my-corda-node``, but rather ``::`` (all addresses on all interfaces). The hostname specified is the hostname *that must be externally resolvable by other nodes in the network*. In the above configuration this is the resolvable name of a machine in a vpn. +* ``webAddress``: The address the webserver should bind. Note that the port should be distinct from that of ``artemisAddress``. +* ``networkMapAddress``: The resolvable name and artemis port of the network map node. Note that if this node itself is to be the network map this field should not be specified. + +Starting the nodes +------------------ + +You may now start the nodes in any order. Note that the node is not fully started until it has successfully registered with the network map! + +You should see a banner, some log lines and eventually ``Node started up and registered``, indicating that the node is fully started. + +.. TODO: Add a better way of polling for startup +A programmatic way of determining whether a node is up is to check whether it's ``webAddress`` is bound. + +In terms of process management there is no prescribed method. You may start the jars by hand or perhaps use systemd and friends. + +Logging +------- + +Only a handful of important lines are printed to the console. For +details/diagnosing problems check the logs. + +Logging is standard log4j2_ and may be configured accordingly. Logs +are by default redirected to files in ``NODE_DIRECTORY/logs/``. + + +Connecting to the nodes +----------------------- + +Once a node has started up successfully you may connect to it as a client to initiate protocols/query state etc. Depending on your network setup you may need to tunnel to do this remotely. + +See the :doc:`tutorial-clientrpc-api` on how to establish an RPC link. + +Sidenote: A client is always associated with a single node with a single identity, which only sees their part of the ledger. diff --git a/docs/source/tutorial-clientrpc-api.rst b/docs/source/tutorial-clientrpc-api.rst index 1cbdf18987..58e3a2fdb7 100644 --- a/docs/source/tutorial-clientrpc-api.rst +++ b/docs/source/tutorial-clientrpc-api.rst @@ -66,6 +66,8 @@ The RPC we need to initiate a Cash transaction is ``startFlowDynamic`` which may Finally we have everything in place: we start a couple of nodes, connect to them, and start creating transactions while listening on successfully created ones, which are dumped to the console. We just need to run it!: +.. sourcecode:: bash + # Build the example ./gradlew docs/source/example-code:installDist # Start it @@ -94,4 +96,4 @@ requests or responses with the `Kryo` instance RPC uses. Here's an example of h See more on plugins in :doc:`creating-a-cordapp`. .. warning:: We will be replacing the use of Kryo in RPC with a stable message format and this will mean that this plugin - customisation point will either go away completely or change. \ No newline at end of file + customisation point will either go away completely or change. diff --git a/node/logs/archive/node-voodoo.2016-11-22-1.log.gz b/node/logs/archive/node-voodoo.2016-11-22-1.log.gz new file mode 100644 index 0000000000..39ef8d16a9 Binary files /dev/null and b/node/logs/archive/node-voodoo.2016-11-22-1.log.gz differ diff --git a/node/logs/archive/node-voodoo.2016-11-23-1.log.gz b/node/logs/archive/node-voodoo.2016-11-23-1.log.gz new file mode 100644 index 0000000000..54686d0026 Binary files /dev/null and b/node/logs/archive/node-voodoo.2016-11-23-1.log.gz differ diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index 15a3cecb2a..949948fd1e 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -120,7 +120,7 @@ class Node(override val configuration: FullNodeConfiguration, networkMapAddress: override fun makeMessagingService(): MessagingServiceInternal { val legalIdentity = obtainLegalIdentity() val myIdentityOrNullIfNetworkMapService = if (networkMapService != null) legalIdentity.owningKey else null - userService = RPCUserServiceImpl(configuration.config) + userService = RPCUserServiceImpl(configuration) val serverAddr = with(configuration) { messagingServerAddress ?: { messageBroker = ArtemisMessagingServer(this, artemisAddress, myIdentityOrNullIfNetworkMapService, services.networkMapCache, userService) diff --git a/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt b/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt index ae9e004258..e515717155 100644 --- a/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt +++ b/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt @@ -1,8 +1,7 @@ package net.corda.node.services -import com.typesafe.config.Config import net.corda.core.flows.FlowLogic -import net.corda.node.services.config.getListOrElse +import net.corda.node.services.config.FullNodeConfiguration /** * Service for retrieving [User] objects representing RPC users who are authorised to use the RPC system. A [User] @@ -16,21 +15,9 @@ interface RPCUserService { // TODO Store passwords as salted hashes // TODO Or ditch this and consider something like Apache Shiro -class RPCUserServiceImpl(config: Config) : RPCUserService { +class RPCUserServiceImpl(config: FullNodeConfiguration) : RPCUserService { - private val _users: Map - - init { - _users = config.getListOrElse("rpcUsers") { emptyList() } - .map { - val username = it.getString("user") - require(username.matches("\\w+".toRegex())) { "Username $username contains invalid characters" } - val password = it.getString("password") - val permissions = it.getListOrElse("permissions") { emptyList() }.toSet() - User(username, password, permissions) - } - .associateBy(User::username) - } + private val _users = config.rpcUsers.associateBy(User::username) override fun getUser(username: String): User? = _users[username] diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index d7a5bbd6d8..9d2ace3310 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -7,6 +7,7 @@ import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.services.ServiceInfo import net.corda.node.internal.Node import net.corda.node.serialization.NodeClock +import net.corda.node.services.User import net.corda.node.services.messaging.NodeMessagingClient import net.corda.node.services.network.NetworkMapService import net.corda.node.utilities.TestClock @@ -51,6 +52,15 @@ class FullNodeConfiguration(val config: Config) : NodeConfiguration { val useTestClock: Boolean by config.getOrElse { false } val notaryNodeAddress: HostAndPort? by config.getOrElse { null } val notaryClusterAddresses: List = config.getListOrElse("notaryClusterAddresses") { emptyList() }.map { HostAndPort.fromString(it) } + val rpcUsers: List = + config.getListOrElse("rpcUsers") { emptyList() } + .map { + val username = it.getString("user") + require(username.matches("\\w+".toRegex())) { "Username $username contains invalid characters" } + val password = it.getString("password") + val permissions = it.getListOrElse("permissions") { emptyList() }.toSet() + User(username, password, permissions) + } fun createNode(): Node { // This is a sanity feature do not remove. diff --git a/node/src/test/kotlin/net/corda/node/services/ArtemisMessagingTests.kt b/node/src/test/kotlin/net/corda/node/services/ArtemisMessagingTests.kt index b93d45f44b..c93126dde3 100644 --- a/node/src/test/kotlin/net/corda/node/services/ArtemisMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/ArtemisMessagingTests.kt @@ -11,6 +11,7 @@ import net.corda.core.messaging.Message import net.corda.core.messaging.createMessage import net.corda.core.node.services.DEFAULT_SESSION_ID import net.corda.core.utilities.LogHelper +import net.corda.node.services.config.FullNodeConfiguration import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.messaging.ArtemisMessagingServer import net.corda.node.services.messaging.NodeMessagingClient @@ -65,7 +66,7 @@ class ArtemisMessagingTests { @Before fun setUp() { - userService = RPCUserServiceImpl(ConfigFactory.empty()) + userService = RPCUserServiceImpl(FullNodeConfiguration(ConfigFactory.empty())) // TODO: create a base class that provides a default implementation config = object : NodeConfiguration { override val basedir: Path = temporaryFolder.newFolder().toPath() diff --git a/node/src/test/kotlin/net/corda/node/services/RPCUserServiceImplTest.kt b/node/src/test/kotlin/net/corda/node/services/RPCUserServiceImplTest.kt index d26967d5ea..9d11ef23e0 100644 --- a/node/src/test/kotlin/net/corda/node/services/RPCUserServiceImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/RPCUserServiceImplTest.kt @@ -1,6 +1,7 @@ package net.corda.node.services import com.typesafe.config.ConfigFactory +import net.corda.node.services.config.FullNodeConfiguration import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test @@ -68,6 +69,6 @@ class RPCUserServiceImplTest { } private fun loadWithContents(configString: String): RPCUserServiceImpl { - return RPCUserServiceImpl(ConfigFactory.parseString(configString)) + return RPCUserServiceImpl(FullNodeConfiguration(ConfigFactory.parseString(configString))) } } \ No newline at end of file