diff --git a/build.gradle b/build.gradle index 427b33fe23..287dbccb53 100644 --- a/build.gradle +++ b/build.gradle @@ -84,7 +84,7 @@ tasks.withType(JavaExec) { jvmArgs "-Dco.paralleluniverse.fibers.verifyInstrumentation" } -// Package up the other demo programs. +// Package up the demo programs. task getRateFixDemo(type: CreateStartScripts) { mainClassName = "demos.RateFixDemoKt" applicationName = "get-rate-fix" @@ -101,6 +101,14 @@ task getIRSDemo(type: CreateStartScripts) { classpath = jar.outputs.files + project.configurations.runtime } +task getTraderDemo(type: CreateStartScripts) { + mainClassName = "demos.TraderDemoKt" + applicationName = "trader-demo" + defaultJvmOpts = ["-javaagent:${configurations.quasar.singleFile}"] + outputDir = new File(project.buildDir, 'scripts') + classpath = jar.outputs.files + project.configurations.runtime +} + // Force windows script classpath to wildcard path to avoid the 'Command Line Is Too Long' issues // with generated scripts. Include Jolokia .war explicitly as this isn't picked up by wildcard tasks.withType(CreateStartScripts) @@ -138,5 +146,6 @@ jar.dependsOn quasarScan applicationDistribution.into("bin") { from(getRateFixDemo) from(getIRSDemo) + from(getTraderDemo) fileMode = 0755 -} \ No newline at end of file +} diff --git a/docs/source/running-the-demos.rst b/docs/source/running-the-demos.rst index 1bb27b15cf..92fae32040 100644 --- a/docs/source/running-the-demos.rst +++ b/docs/source/running-the-demos.rst @@ -14,29 +14,27 @@ The demos have only been tested on MacOS X and Ubuntu Linux. If you have success The demos create node data directories in the root of the project. If something goes wrong with them, blow away the directories and try again. -For Windows users, the contents of the shell scripts are very trivial and can easily be done by hand from a command -window. Essentially, it just runs Gradle to create the startup scripts, and then starts the node with one set of -flags or another. Alternatively you could play with the new Linux syscall support in Windows 10! - Trader demo ----------- Open two terminals, and in the first run::: - ./scripts/trader-demo.sh buyer + gradle installDist && ./build/install/r3prototyping/trader-demo.sh --mode=buyer -It will compile things, if necessary, then create a directory named "buyer" with a bunch of files inside and start -the node. You should see it waiting for a trade to begin. +It will compile things, if necessary, then create a directory named trader-demo/buyer with a bunch of files inside and +start the node. You should see it waiting for a trade to begin. In the second terminal, run:: - ./scripts/trader-demo.sh seller + ./build/install/r3prototyping/trader-demo.sh --mode=seller You should see some log lines scroll past, and within a few seconds the messages "Purchase complete - we are a happy customer!" and "Sale completed - we have a happy customer!" should be printed. If it doesn't work, jump on the mailing list and let us know. +On Windows, use the same commands, but run the batch file instead of the shell file. + IRS demo -------- diff --git a/node/src/main/kotlin/core/messaging/StateMachineManager.kt b/node/src/main/kotlin/core/messaging/StateMachineManager.kt index a029207704..34fdb8cdd3 100644 --- a/node/src/main/kotlin/core/messaging/StateMachineManager.kt +++ b/node/src/main/kotlin/core/messaging/StateMachineManager.kt @@ -94,11 +94,16 @@ class StateMachineManager(val serviceHub: ServiceHub, val executor: AffinityExec field.get(null) } + companion object { + var restoreCheckpointsOnStart = true + } + init { Fiber.setDefaultUncaughtExceptionHandler { fiber, throwable -> (fiber as ProtocolStateMachineImpl<*>).logger.error("Caught exception from protocol", throwable) } - restoreCheckpoints() + if (restoreCheckpointsOnStart) + restoreCheckpoints() } /** Reads the database map and resurrects any serialised state machines. */ diff --git a/scripts/trader-demo.sh b/scripts/trader-demo.sh deleted file mode 100755 index 58539c7093..0000000000 --- a/scripts/trader-demo.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -mode=$1 - -if [ ! -e ./gradlew ]; then - echo "Run from the root directory please" - exit 1 -fi - -if [[ "$SKIP_INSTALL" == "" ]]; then - ./gradlew installDist -fi - -if [[ "$mode" == "buyer" ]]; then - if [ ! -d buyer ]; then - mkdir buyer - echo "myLegalName = Bank A" >buyer/config - fi - - build/install/r3prototyping/bin/r3prototyping --dir=buyer --service-fake-trades --network-address=localhost -elif [[ "$mode" == "seller" ]]; then - if [ ! -d seller ]; then - mkdir seller - echo "myLegalName = Bank B" >seller/config - fi - - build/install/r3prototyping/bin/r3prototyping --dir=seller --fake-trade-with=localhost --network-address=localhost:31340 --network-map-identity-file=buyer/identity-public --network-map-address=localhost -else - echo "Run like this, one in each tab:" - echo - echo " scripts/trader-demo.sh buyer" - echo " scripts/trader-demo.sh seller" -fi diff --git a/src/main/kotlin/demos/TraderDemo.kt b/src/main/kotlin/demos/TraderDemo.kt index dd27b5c841..651244ed37 100644 --- a/src/main/kotlin/demos/TraderDemo.kt +++ b/src/main/kotlin/demos/TraderDemo.kt @@ -4,14 +4,15 @@ import co.paralleluniverse.fibers.Suspendable import com.google.common.net.HostAndPort import com.typesafe.config.ConfigFactory import contracts.CommercialPaper -import core.* import core.contracts.* import core.crypto.Party import core.crypto.SecureHash import core.crypto.generateKeyPair +import core.days +import core.logElapsedTime import core.messaging.SingleMessageRecipient +import core.messaging.StateMachineManager import core.node.Node -import core.node.NodeConfiguration import core.node.NodeConfigurationFromConfig import core.node.NodeInfo import core.node.services.NetworkMapService @@ -21,6 +22,8 @@ import core.node.services.ServiceType import core.node.subsystems.ArtemisMessagingService import core.node.subsystems.NodeWalletService import core.protocols.ProtocolLogic +import core.random63BitValue +import core.seconds import core.serialization.deserialize import core.utilities.ANSIProgressRenderer import core.utilities.BriefLogFormatter @@ -43,17 +46,10 @@ import kotlin.test.assertEquals fun main(args: Array) { val parser = OptionParser() - val networkAddressArg = parser.accepts("network-address").withRequiredArg().required() - val dirArg = parser.accepts("directory").withRequiredArg().defaultsTo("nodedata") - // Some dummy functionality that won't last long ... - - // Mode flags for the first demo. - val serviceFakeTradesArg = parser.accepts("service-fake-trades") - val fakeTradeWithArg = parser.accepts("fake-trade-with").requiredUnless(serviceFakeTradesArg).withRequiredArg() - - val networkMapIdentityFile = parser.accepts("network-map-identity-file").requiredIf(fakeTradeWithArg).withRequiredArg() - val networkMapNetAddr = parser.accepts("network-map-address").requiredIf(networkMapIdentityFile).withRequiredArg() + val modeArg = parser.accepts("mode").withRequiredArg().required() + val myNetworkAddress = parser.accepts("network-address").withRequiredArg().defaultsTo("localhost") + val theirNetworkAddress = parser.accepts("other-network-address").withRequiredArg().defaultsTo("localhost") val options = try { parser.parse(*args) @@ -63,39 +59,46 @@ fun main(args: Array) { exitProcess(1) } - // Suppress the Artemis MQ noise, and activate the demo logging. - BriefLogFormatter.initVerbose("+demo.buyer", "+demo.seller", "-org.apache.activemq") + val mode = options.valueOf(modeArg) - val dir = Paths.get(options.valueOf(dirArg)) - val configFile = dir.resolve("config") + val DIRNAME = "trader-demo" + val BUYER = "buyer" + val SELLER = "seller" - if (!Files.exists(dir)) { - Files.createDirectory(dir) - } - - val config = loadConfigFile(configFile) - - val advertisedServices: Set - val myNetAddr = HostAndPort.fromString(options.valueOf(networkAddressArg)).withDefaultPort(Node.DEFAULT_PORT) - val listening = options.has(serviceFakeTradesArg) - - if (listening && config.myLegalName != "Bank A") { - println("The buyer node must have a legal name of 'Bank A'. Please edit the config file.") + if (mode !in setOf(BUYER, SELLER)) { + printHelp() exitProcess(1) } - val networkMapId = if (options.has(networkMapIdentityFile)) { - val addr = HostAndPort.fromString(options.valueOf(networkMapNetAddr)).withDefaultPort(Node.DEFAULT_PORT) - val path = Paths.get(options.valueOf(networkMapIdentityFile)) + // Suppress the Artemis MQ noise, and activate the demo logging. + BriefLogFormatter.initVerbose("+demo.buyer", "+demo.seller", "-org.apache.activemq") + + val dir = Paths.get(DIRNAME, mode) + Files.createDirectories(dir) + + val advertisedServices: Set + val myNetAddr = HostAndPort.fromString(options.valueOf(myNetworkAddress)).withDefaultPort(if (mode == BUYER) Node.DEFAULT_PORT else 31340) + val theirNetAddr = HostAndPort.fromString(options.valueOf(theirNetworkAddress)).withDefaultPort(if (mode == SELLER) Node.DEFAULT_PORT else 31340) + + val listening = mode == BUYER + val config = run { + val override = ConfigFactory.parseString("""myLegalName = ${ if (mode == BUYER) "Bank A" else "Bank B" }""") + NodeConfigurationFromConfig(override.withFallback(ConfigFactory.load())) + } + + val networkMapId = if (mode == SELLER) { + val path = Paths.get(DIRNAME, BUYER, "identity-public") val party = Files.readAllBytes(path).deserialize() advertisedServices = emptySet() - NodeInfo(ArtemisMessagingService.makeRecipient(addr), party, setOf(NetworkMapService.Type)) + NodeInfo(ArtemisMessagingService.makeRecipient(theirNetAddr), party, setOf(NetworkMapService.Type)) } else { // We must be the network map service advertisedServices = setOf(NetworkMapService.Type, NotaryService.Type) null } + // TODO: Remove this once checkpoint resume works. + StateMachineManager.restoreCheckpointsOnStart = false val node = logElapsedTime("Node startup") { Node(dir, myNetAddr, config, networkMapId, advertisedServices).start() } if (listening) { @@ -110,11 +113,6 @@ fun main(args: Array) { ANSIProgressRenderer.progressTracker = buyer.progressTracker node.smm.add("demo.buyer", buyer).get() // This thread will halt forever here. } else { - if (!options.has(fakeTradeWithArg)) { - println("Need the --fake-trade-with command line argument") - exitProcess(1) - } - // Make sure we have the transaction prospectus attachment loaded into our store. if (node.storage.attachments.openAttachment(TraderDemoProtocolSeller.PROSPECTUS_HASH) == null) { TraderDemoProtocolSeller::class.java.getResourceAsStream("bank-of-london-cp.jar").use { @@ -123,8 +121,7 @@ fun main(args: Array) { } } - val peerAddr = HostAndPort.fromString(options.valuesOf(fakeTradeWithArg).single()).withDefaultPort(Node.DEFAULT_PORT) - val otherSide = ArtemisMessagingService.makeRecipient(peerAddr) + val otherSide = ArtemisMessagingService.makeRecipient(theirNetAddr) val seller = TraderDemoProtocolSeller(myNetAddr, otherSide) ANSIProgressRenderer.progressTracker = seller.progressTracker node.smm.add("demo.seller", seller).get() @@ -277,43 +274,6 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort, } -private fun loadConfigFile(configFile: Path): NodeConfiguration { - fun askAdminToEditConfig(configFile: Path?) { - println() - println("This is the first run, so you should edit the config file in $configFile and then start the node again.") - println() - exitProcess(1) - } - - val defaultLegalName = "Global MegaCorp, Ltd." - - if (!Files.exists(configFile)) { - createDefaultConfigFile(configFile, defaultLegalName) - askAdminToEditConfig(configFile) - } - - System.setProperty("config.file", configFile.toAbsolutePath().toString()) - val config = NodeConfigurationFromConfig(ConfigFactory.load()) - - // Make sure admin did actually edit at least the legal name. - if (config.myLegalName == defaultLegalName) - askAdminToEditConfig(configFile) - - return config -} - -private fun createDefaultConfigFile(configFile: Path?, defaultLegalName: String) { - Files.write(configFile, - """ - # Node configuration: give the buyer node the name 'Bank of Zurich' (no quotes) - # The seller node can be named whatever you like. - - myLegalName = $defaultLegalName - """.trimIndent().toByteArray()) -} - private fun printHelp() { - println(""" - Please refer to the documentation in docs/build/index.html to learn how to run the demo. - """.trimIndent()) + println("Please refer to the documentation in docs/build/index.html to learn how to run the demo.") }