diff --git a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt index a2fa583bf0..002da1e4d0 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt @@ -107,6 +107,11 @@ interface CordaRPCOps : RPCOps { */ fun uploadAttachment(jar: InputStream): SecureHash + /** + * Uploads a file by data type to the node and returns a meaningful string to the user + */ + fun uploadFile(dataType: String, name: String?, file: InputStream): String + /** * Returns the node-local current time. */ diff --git a/core/src/main/kotlin/net/corda/core/node/services/Services.kt b/core/src/main/kotlin/net/corda/core/node/services/Services.kt index 3de6b3f864..6dd5763882 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/Services.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/Services.kt @@ -10,6 +10,8 @@ import net.corda.core.toFuture import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction import rx.Observable +import java.io.File +import java.io.InputStream import java.security.KeyPair import java.security.PrivateKey import java.security.PublicKey @@ -216,6 +218,16 @@ interface KeyManagementService { fun freshKey(): KeyPair } +// TODO: Move and document +interface FileUploader { + /** + * Accepts the data in the given input stream, and returns some sort of useful return message that will be sent + * back to the user in the response. + */ + fun upload(file: InputStream): String + fun accepts(prefix: String): Boolean +} + /** * A sketch of an interface to a simple key/value storage system. Intended for persistence of simple blobs like * transactions, serialised flow state machines and so on. Again, this isn't intended to imply lack of SQL or @@ -232,6 +244,9 @@ interface StorageService { /** Provides access to storage of arbitrary JAR files (which may contain only data, no code). */ val attachments: AttachmentStorage + /** Provides file uploads of arbitrary files to services **/ + val uploaders: List + val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage } diff --git a/node/src/main/kotlin/net/corda/node/ArgsParser.kt b/node/src/main/kotlin/net/corda/node/ArgsParser.kt index 4716bbcc2c..7e3bbb6d79 100644 --- a/node/src/main/kotlin/net/corda/node/ArgsParser.kt +++ b/node/src/main/kotlin/net/corda/node/ArgsParser.kt @@ -21,6 +21,7 @@ class ArgsParser { .withRequiredArg() .defaultsTo("node.conf") private val logToConsoleArg = optionParser.accepts("log-to-console", "If set, prints logging to the console as well as to a file.") + private val isWebserverArg = optionParser.accepts("webserver") private val helpArg = optionParser.accepts("help").forHelp() fun parse(vararg args: String): CmdLineOptions { @@ -30,13 +31,14 @@ class ArgsParser { } val baseDirectory = Paths.get(optionSet.valueOf(baseDirectoryArg)).normalize().toAbsolutePath() val configFile = baseDirectory / optionSet.valueOf(configFileArg) - return CmdLineOptions(baseDirectory, configFile, optionSet.has(helpArg), optionSet.has(logToConsoleArg)) + val isWebserver = optionSet.has(isWebserverArg) + return CmdLineOptions(baseDirectory, configFile, optionSet.has(helpArg), optionSet.has(logToConsoleArg), isWebserver) } fun printHelp(sink: PrintStream) = optionParser.printHelpOn(sink) } -data class CmdLineOptions(val baseDirectory: Path, val configFile: Path?, val help: Boolean, val logToConsole: Boolean) { +data class CmdLineOptions(val baseDirectory: Path, val configFile: Path?, val help: Boolean, val logToConsole: Boolean, val isWebserver: Boolean) { fun loadConfig(allowMissingConfig: Boolean = false, configOverrides: Map = emptyMap()): Config { return ConfigHelper.loadConfig(baseDirectory, configFile, allowMissingConfig, configOverrides) } diff --git a/node/src/main/kotlin/net/corda/node/Corda.kt b/node/src/main/kotlin/net/corda/node/Corda.kt index ca7c9e969a..825973f671 100644 --- a/node/src/main/kotlin/net/corda/node/Corda.kt +++ b/node/src/main/kotlin/net/corda/node/Corda.kt @@ -7,6 +7,7 @@ import net.corda.core.utilities.Emoji import net.corda.node.internal.Node import net.corda.node.services.config.FullNodeConfiguration import net.corda.node.utilities.ANSIProgressObserver +import net.corda.node.webserver.WebServer import org.fusesource.jansi.Ansi import org.fusesource.jansi.AnsiConsole import org.slf4j.LoggerFactory @@ -78,33 +79,30 @@ fun main(args: Array) { log.info("VM ${info.vmName} ${info.vmVendor} ${info.vmVersion}") log.info("Machine: ${InetAddress.getLocalHost().hostName}") log.info("Working Directory: ${cmdlineOptions.baseDirectory}") + log.info("Started as webserver: ${cmdlineOptions.isWebserver}") try { cmdlineOptions.baseDirectory.createDirectories() - val node = conf.createNode() - node.start() - printPluginsAndServices(node) + if(!cmdlineOptions.isWebserver) { + val node = conf.createNode() + node.start() + printPluginsAndServices(node) - thread { - Thread.sleep(30.seconds.toMillis()) - while (!node.networkMapRegistrationFuture.isDone) { - printBasicNodeInfo("Waiting for response from network map ...") - Thread.sleep(30.seconds.toMillis()) + node.networkMapRegistrationFuture.success { + val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0 + printBasicNodeInfo("Node started up and registered in $elapsed sec") + + if (renderBasicInfoToConsole) + ANSIProgressObserver(node.smm) + } failure { + log.error("Error during network map registration", it) + exitProcess(1) } + node.run() + } else { + WebServer(conf).start() } - - node.networkMapRegistrationFuture.success { - val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0 - printBasicNodeInfo("Node started up and registered in $elapsed sec") - - if (renderBasicInfoToConsole) - ANSIProgressObserver(node.smm) - } failure { - log.error("Error during network map registration", it) - exitProcess(1) - } - node.run() } catch (e: Exception) { log.error("Exception during node startup", e) exitProcess(1) diff --git a/node/src/main/kotlin/net/corda/node/driver/Driver.kt b/node/src/main/kotlin/net/corda/node/driver/Driver.kt index 3f99f81464..bd4512bb99 100644 --- a/node/src/main/kotlin/net/corda/node/driver/Driver.kt +++ b/node/src/main/kotlin/net/corda/node/driver/Driver.kt @@ -2,8 +2,6 @@ package net.corda.node.driver -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.KotlinModule import com.google.common.net.HostAndPort import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture @@ -15,7 +13,6 @@ import net.corda.core.crypto.Party import net.corda.core.node.NodeInfo import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceType -import net.corda.core.utilities.ApiUtils import net.corda.core.utilities.loggerFor import net.corda.node.services.User import net.corda.node.services.config.ConfigHelper @@ -26,9 +23,9 @@ import net.corda.node.services.messaging.CordaRPCClient import net.corda.node.services.messaging.NodeMessagingClient import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.RaftValidatingNotaryService -import net.corda.node.utilities.JsonSupport import net.corda.node.utilities.ServiceIdentityGenerator import okhttp3.OkHttpClient +import okhttp3.Request import org.slf4j.Logger import java.io.File import java.net.* @@ -44,9 +41,6 @@ import java.util.concurrent.* import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.SECONDS import java.util.concurrent.atomic.AtomicInteger -import com.sun.corba.se.spi.presentation.rmi.StubAdapter.request -import org.bouncycastle.crypto.tls.ConnectionEnd.client -import okhttp3.Request /** @@ -201,6 +195,7 @@ fun genericD return returnValue } catch (exception: Throwable) { println("Driver shutting down because of exception $exception") + exception.printStackTrace() throw exception } finally { driverDsl.shutdown() @@ -423,19 +418,23 @@ open class DriverDSL( } private fun queryWebserver(configuration: FullNodeConfiguration): HostAndPort? { - val protocol = if(configuration.useHTTPS) { "https://" } else { "http://" } + val protocol = if (configuration.useHTTPS) { + "https://" + } else { + "http://" + } val url = URL(protocol + configuration.webAddress.toString() + "/api/status") val client = OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS).build() val retries = 5 - for(i in 0..retries) { + for (i in 0..retries) { try { val response = client.newCall(Request.Builder().url(url).build()).execute() if (response.isSuccessful && (response.body().string() == "started")) { return configuration.webAddress } } catch(e: ConnectException) { - log.debug("Retrying webserver info at ${ configuration.webAddress}") + log.debug("Retrying webserver info at ${configuration.webAddress}") } } @@ -467,7 +466,6 @@ open class DriverDSL( configOverrides = mapOf( "myLegalName" to networkMapLegalName, "artemisAddress" to networkMapAddress.toString(), - "webAddress" to apiAddress.toString(), "extraAdvertisedServiceIds" to "", "useTestClock" to useTestClock ) @@ -533,7 +531,7 @@ open class DriverDSL( executorService: ScheduledExecutorService, nodeConf: FullNodeConfiguration, debugPort: Int?): ListenableFuture { - val className = "net.corda.node.webserver.MainKt" // cannot directly get class for this, so just use string + val className = "net.corda.node.Corda" // cannot directly get class for this, so just use string val separator = System.getProperty("file.separator") val classpath = System.getProperty("java.class.path") val path = System.getProperty("java.home") + separator + "bin" + separator + "java" @@ -548,7 +546,7 @@ open class DriverDSL( listOf( "-cp", classpath, className, "--base-directory", nodeConf.baseDirectory.toString(), - "--web-address", nodeConf.webAddress.toString()) + "--webserver") val builder = ProcessBuilder(javaArgs) builder.redirectError(Paths.get("error.$className.log").toFile()) builder.inheritIO() diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index c90a6e4fd0..32693de64a 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -51,6 +51,7 @@ import net.corda.node.utilities.databaseTransaction import org.apache.activemq.artemis.utils.ReusableLatch import org.jetbrains.exposed.sql.Database import org.slf4j.Logger +import java.io.File import java.nio.file.FileAlreadyExistsException import java.nio.file.Path import java.security.KeyPair @@ -103,11 +104,6 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, // low-performance prototyping period. protected abstract val serverThread: AffinityExecutor - // Objects in this list will be scanned by the DataUploadServlet and can be handed new data via HTTP. - // Don't mutate this after startup. - protected val _servicesThatAcceptUploads = ArrayList() - val servicesThatAcceptUploads: List = _servicesThatAcceptUploads - private val flowFactories = ConcurrentHashMap, (Party) -> FlowLogic<*>>() protected val partyKeys = mutableSetOf() @@ -225,6 +221,10 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, customServices.clear() customServices.addAll(buildPluginServices(tokenizableServices)) + val uploaders: List = listOf(storageServices.first.attachments as NodeAttachmentService) + + customServices.filter { it is AcceptsFileUpload }.map { it as AcceptsFileUpload } + (storage as StorageServiceImpl).initUploaders(uploaders) + // TODO: uniquenessProvider creation should be inside makeNotaryService(), but notary service initialisation // depends on smm, while smm depends on tokenizableServices, which uniquenessProvider is part of advertisedServices.singleOrNull { it.type.isNotary() }?.let { @@ -350,9 +350,6 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, val service = serviceConstructor.apply(services) serviceList.add(service) tokenizableServices.add(service) - if (service is AcceptsFileUpload) { - _servicesThatAcceptUploads += service - } } return serviceList } @@ -482,7 +479,6 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, val attachments = makeAttachmentStorage(dir) val checkpointStorage = DBCheckpointStorage() val transactionStorage = DBTransactionStorage() - _servicesThatAcceptUploads += attachments val stateMachineTransactionMappingStorage = DBTransactionMappingStorage() return Pair( constructStorageService(attachments, transactionStorage, stateMachineTransactionMappingStorage), diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt index 952d67d532..e78493f4e0 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -108,6 +108,10 @@ class CordaRPCOpsImpl( override fun attachmentExists(id: SecureHash) = services.storageService.attachments.openAttachment(id) != null override fun uploadAttachment(jar: InputStream) = services.storageService.attachments.importAttachment(jar) override fun currentNodeTime(): Instant = Instant.now(services.clock) + override fun uploadFile(dataType: String, name: String?, file: InputStream): String { + val acceptor = services.storageService.uploaders.firstOrNull { it.accepts(dataType) } + return acceptor?.upload(file) ?: throw RuntimeException("Cannot find file upload acceptor for $dataType") + } override fun partyFromKey(key: CompositeKey) = services.identityService.partyFromKey(key) override fun partyFromName(name: String) = services.identityService.partyFromName(name) diff --git a/node/src/main/kotlin/net/corda/node/services/api/AcceptsFileUpload.kt b/node/src/main/kotlin/net/corda/node/services/api/AcceptsFileUpload.kt index bdf0c54d33..10bcfb7e65 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/AcceptsFileUpload.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/AcceptsFileUpload.kt @@ -1,5 +1,7 @@ package net.corda.node.services.api +import net.corda.core.crypto.SecureHash +import net.corda.core.node.services.FileUploader import java.io.InputStream /** @@ -7,16 +9,12 @@ import java.io.InputStream * * TODO: In future, also accept uploads over the MQ interface too. */ -interface AcceptsFileUpload { +interface AcceptsFileUpload: FileUploader { /** A string that prefixes the URLs, e.g. "attachments" or "interest-rates". Should be OK for URLs. */ val dataTypePrefix: String /** What file extensions are acceptable for the file to be handed to upload() */ val acceptableFileExtensions: List - /** - * Accepts the data in the given input stream, and returns some sort of useful return message that will be sent - * back to the user in the response. - */ - fun upload(data: InputStream): String + override fun accepts(prefix: String) = prefix == dataTypePrefix } diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCStructures.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCStructures.kt index ca2954e5cb..f82f032f5b 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RPCStructures.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCStructures.kt @@ -36,6 +36,7 @@ import net.corda.node.services.messaging.ArtemisMessagingComponent.NetworkMapAdd import net.i2p.crypto.eddsa.EdDSAPrivateKey import net.i2p.crypto.eddsa.EdDSAPublicKey import org.apache.activemq.artemis.api.core.SimpleString +import org.apache.commons.fileupload.MultipartStream import org.objenesis.strategy.StdInstantiatorStrategy import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -146,6 +147,7 @@ private class RPCKryo(observableSerializer: Serializer>? = null) register(BufferedInputStream::class.java, InputStreamSerializer) register(Class.forName("sun.net.www.protocol.jar.JarURLConnection\$JarURLInputStream"), InputStreamSerializer) + register(MultipartStream.ItemInputStream::class.java, InputStreamSerializer) noReferencesWithin() @@ -208,6 +210,7 @@ private class RPCKryo(observableSerializer: Serializer>? = null) register(SimpleString::class.java) register(ServiceEntry::class.java) // Exceptions. We don't bother sending the stack traces as the client will fill in its own anyway. + register(RuntimeException::class.java) register(IllegalArgumentException::class.java) register(ArrayIndexOutOfBoundsException::class.java) register(IndexOutOfBoundsException::class.java) diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/StorageServiceImpl.kt b/node/src/main/kotlin/net/corda/node/services/persistence/StorageServiceImpl.kt index 5e42cc8aec..6e41630fb0 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/StorageServiceImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/StorageServiceImpl.kt @@ -1,12 +1,15 @@ package net.corda.node.services.persistence -import net.corda.core.node.services.AttachmentStorage -import net.corda.core.node.services.StateMachineRecordedTransactionMappingStorage -import net.corda.core.node.services.TransactionStorage -import net.corda.core.node.services.TxWritableStorageService +import net.corda.core.node.services.* import net.corda.core.serialization.SingletonSerializeAsToken open class StorageServiceImpl(override val attachments: AttachmentStorage, override val validatedTransactions: TransactionStorage, override val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage) - : SingletonSerializeAsToken(), TxWritableStorageService + : SingletonSerializeAsToken(), TxWritableStorageService { + lateinit override var uploaders: List + + fun initUploaders(uploadersList: List) { + uploaders = uploadersList + } +} diff --git a/node/src/main/kotlin/net/corda/node/webserver/Main.kt b/node/src/main/kotlin/net/corda/node/webserver/Main.kt deleted file mode 100644 index b9a46df769..0000000000 --- a/node/src/main/kotlin/net/corda/node/webserver/Main.kt +++ /dev/null @@ -1,44 +0,0 @@ -package net.corda.node.webserver - -import joptsimple.OptionParser -import net.corda.node.driver.driver -import net.corda.node.services.config.ConfigHelper -import net.corda.node.services.config.FullNodeConfiguration -import java.io.File -import java.nio.file.Paths -import kotlin.system.exitProcess - -fun main(args: Array) { - // TODO: Print basic webserver info - val parser = OptionParser() - val baseDirectoryArg = parser.accepts("base-directory", "The directory to put all files under").withRequiredArg() - val webAddressArg = parser.accepts("web-address", "The web address for this server to bind").withOptionalArg() - val logToConsoleArg = parser.accepts("log-to-console", "If set, prints logging to the console as well as to a file.") - val helpArg = parser.accepts("help").forHelp() - val cmdlineOptions = try { - parser.parse(*args) - } catch (ex: Exception) { - println("Unknown command line arguments: ${ex.message}") - exitProcess(1) - } - - // Maybe render command line help. - if (cmdlineOptions.has(helpArg)) { - parser.printHelpOn(System.out) - exitProcess(0) - } - - // Set up logging. - if (cmdlineOptions.has(logToConsoleArg)) { - // This property is referenced from the XML config file. - System.setProperty("consoleLogLevel", "info") - } - - val baseDirectoryPath = Paths.get(cmdlineOptions.valueOf(baseDirectoryArg)) - val config = ConfigHelper.loadConfig(baseDirectoryPath) - - println("Starting server") - val nodeConf = FullNodeConfiguration(baseDirectoryPath, config) - val server = WebServer(nodeConf).start() - println("Exiting") -} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/webserver/Test.kt b/node/src/main/kotlin/net/corda/node/webserver/Test.kt deleted file mode 100644 index 7f01f53815..0000000000 --- a/node/src/main/kotlin/net/corda/node/webserver/Test.kt +++ /dev/null @@ -1,15 +0,0 @@ -package net.corda.node.webserver - -import net.corda.node.driver.driver -import net.corda.node.services.config.FullNodeConfiguration - -fun main(args: Array) { - // TODO: Print basic webserver info - System.setProperty("consoleLogLevel", "info") - driver { - val node = startNode().get() - //WebServer(node.configuration).start() // Old in memory way - startWebserver(node) - waitForAllNodesToFinish() - } -} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/webserver/WebServer.kt b/node/src/main/kotlin/net/corda/node/webserver/WebServer.kt index 1927d57555..b9f06a87c6 100644 --- a/node/src/main/kotlin/net/corda/node/webserver/WebServer.kt +++ b/node/src/main/kotlin/net/corda/node/webserver/WebServer.kt @@ -102,6 +102,7 @@ class WebServer(val config: FullNodeConfiguration) { private fun buildServletContextHandler(localRpc: CordaRPCOps): ServletContextHandler { return ServletContextHandler().apply { contextPath = "/" + setAttribute("rpc", localRpc) addServlet(DataUploadServlet::class.java, "/upload/*") addServlet(AttachmentDownloadServlet::class.java, "/attachments/*") @@ -111,8 +112,6 @@ class WebServer(val config: FullNodeConfiguration) { resourceConfig.register(APIServerImpl(localRpc)) val webAPIsOnClasspath = pluginRegistries.flatMap { x -> x.webApis } - println("NUM PLUGINS: ${pluginRegistries.size}") - println("NUM WEBAPIS: ${webAPIsOnClasspath.size}") for (webapi in webAPIsOnClasspath) { log.info("Add plugin web API from attachment $webapi") val customAPI = try { diff --git a/node/src/main/kotlin/net/corda/node/webserver/servlets/DataUploadServlet.kt b/node/src/main/kotlin/net/corda/node/webserver/servlets/DataUploadServlet.kt index 6bb64f7da4..76a337128c 100644 --- a/node/src/main/kotlin/net/corda/node/webserver/servlets/DataUploadServlet.kt +++ b/node/src/main/kotlin/net/corda/node/webserver/servlets/DataUploadServlet.kt @@ -1,5 +1,6 @@ package net.corda.node.webserver.servlets +import net.corda.core.messaging.CordaRPCOps import net.corda.core.utilities.loggerFor import net.corda.node.internal.Node import net.corda.node.services.api.AcceptsFileUpload @@ -12,26 +13,19 @@ import javax.servlet.http.HttpServletResponse /** * Accepts binary streams, finds the right [AcceptsFileUpload] implementor and hands the stream off to it. */ -class DataUploadServlet : HttpServlet() { +class DataUploadServlet: HttpServlet() { private val log = loggerFor() override fun doPost(req: HttpServletRequest, resp: HttpServletResponse) { - val node = servletContext.getAttribute("node") as Node - @Suppress("DEPRECATION") // Bogus warning due to superclass static method being deprecated. val isMultipart = ServletFileUpload.isMultipartContent(req) + val rpc = servletContext.getAttribute("rpc") as CordaRPCOps if (!isMultipart) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "This end point is for data uploads only.") return } - val acceptor: AcceptsFileUpload? = findAcceptor(node, req) - if (acceptor == null) { - resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Got a file upload request for an unknown data type") - return - } - val upload = ServletFileUpload() val iterator = upload.getItemIterator(req) val messages = ArrayList() @@ -43,18 +37,15 @@ class DataUploadServlet : HttpServlet() { while (iterator.hasNext()) { val item = iterator.next() - if (item.name != null && !acceptor.acceptableFileExtensions.any { item.name.endsWith(it) }) { - resp.sendError(HttpServletResponse.SC_BAD_REQUEST, - "${item.name}: Must be have a filename ending in one of: ${acceptor.acceptableFileExtensions}") - return - } - log.info("Receiving ${item.name}") - item.openStream().use { - val message = acceptor.upload(it) - log.info("${item.name} successfully accepted: $message") - messages += message + try { + val dataType = req.pathInfo.substring(1).substringBefore('/') + messages += rpc.uploadFile(dataType, item.name, item.openStream()) + log.info("${item.name} successfully accepted: ${messages.last()}") + } catch(e: RuntimeException) { + println(e) + resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Got a file upload request for an unknown data type") } } @@ -62,8 +53,4 @@ class DataUploadServlet : HttpServlet() { val writer = resp.writer messages.forEach { writer.println(it) } } - - private fun findAcceptor(node: Node, req: HttpServletRequest): AcceptsFileUpload? { - return node.servicesThatAcceptUploads.firstOrNull { req.pathInfo.substring(1).substringBefore('/') == it.dataTypePrefix } - } } diff --git a/node/src/test/kotlin/net/corda/node/ArgsParserTest.kt b/node/src/test/kotlin/net/corda/node/ArgsParserTest.kt index 224a0a540b..c143da1680 100644 --- a/node/src/test/kotlin/net/corda/node/ArgsParserTest.kt +++ b/node/src/test/kotlin/net/corda/node/ArgsParserTest.kt @@ -17,7 +17,8 @@ class ArgsParserTest { baseDirectory = workingDirectory, configFile = workingDirectory / "node.conf", help = false, - logToConsole = false)) + logToConsole = false, + isWebserver = false)) } @Test @@ -28,7 +29,8 @@ class ArgsParserTest { baseDirectory = expectedBaseDir, configFile = expectedBaseDir / "node.conf", help = false, - logToConsole = false)) + logToConsole = false, + isWebserver = false)) } @Test @@ -39,7 +41,8 @@ class ArgsParserTest { baseDirectory = baseDirectory, configFile = baseDirectory / "node.conf", help = false, - logToConsole = false)) + logToConsole = false, + isWebserver = false)) } @Test @@ -49,7 +52,8 @@ class ArgsParserTest { baseDirectory = workingDirectory, configFile = workingDirectory / "different.conf", help = false, - logToConsole = false)) + logToConsole = false, + isWebserver = false)) } @Test @@ -60,7 +64,19 @@ class ArgsParserTest { baseDirectory = workingDirectory, configFile = configFile, help = false, - logToConsole = false)) + logToConsole = false, + isWebserver = false)) + } + + @Test + fun `just webserver `() { + val cmdLineOptions = parser.parse("--webserver") + assertThat(cmdLineOptions).isEqualTo(CmdLineOptions( + baseDirectory = workingDirectory, + configFile = workingDirectory / "node.conf", + help = false, + logToConsole = false, + isWebserver = true)) } @Test diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt index 52f2757ecc..cc98f123a0 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt @@ -50,8 +50,9 @@ private class BankOfCordaDriver { driver(dsl = { val user = User("user1", "test", permissions = setOf(startFlowPermission(), startFlowPermission())) startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type))) - startNode("BankOfCorda", rpcUsers = listOf(user), advertisedServices = setOf(ServiceInfo(ServiceType.corda.getSubType("issuer.USD")))) + val bankOfCorda = startNode("BankOfCorda", rpcUsers = listOf(user), advertisedServices = setOf(ServiceInfo(ServiceType.corda.getSubType("issuer.USD")))) startNode("BigCorporation", rpcUsers = listOf(user)) + startWebserver(bankOfCorda.get()) waitForAllNodesToFinish() }, isDebug = true) } @@ -75,7 +76,8 @@ private class BankOfCordaDriver { } } catch (e: Exception) { - printHelp(parser) + println("Exception occurred: $e \n ${e.printStackTrace()}") + exitProcess(1) } } } diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/Main.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/Main.kt index 4e2589e7fb..baed312051 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/Main.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/Main.kt @@ -1,5 +1,7 @@ package net.corda.irs +import com.google.common.util.concurrent.Futures +import net.corda.core.getOrThrow import net.corda.core.node.services.ServiceInfo import net.corda.irs.api.NodeInterestRates import net.corda.node.driver.driver @@ -11,9 +13,16 @@ import net.corda.node.services.transactions.SimpleNotaryService */ fun main(args: Array) { driver(dsl = { - startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type), ServiceInfo(NodeInterestRates.type))).get() - startNode("Bank A") - startNode("Bank B") + val (controller, nodeA, nodeB) = Futures.allAsList( + startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type), ServiceInfo(NodeInterestRates.type))), + startNode("Bank A"), + startNode("Bank B") + ).getOrThrow() + + startWebserver(controller) + startWebserver(nodeA) + startWebserver(nodeB) + waitForAllNodesToFinish() }, useTestClock = true, isDebug = true) } diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt index b172a9768d..c26340d080 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt @@ -23,6 +23,7 @@ import net.corda.node.utilities.JDBCHashedTable import net.corda.node.utilities.localDate import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.statements.InsertStatement +import org.jetbrains.exposed.sql.transactions.transaction import java.io.InputStream import java.math.BigDecimal import java.security.KeyPair @@ -110,7 +111,10 @@ object NodeInterestRates { override fun upload(data: InputStream): String { val fixes = parseFile(data.bufferedReader().readText()) - oracle.knownFixes = fixes + // TODO: Look into why knownFixes requires a transaction + transaction { + oracle.knownFixes = fixes + } val msg = "Interest rates oracle accepted ${fixes.size} new interest rate fixes" println(msg) return msg diff --git a/samples/raft-notary-demo/src/main/kotlin/net/corda/notarydemo/NotaryDemo.kt b/samples/raft-notary-demo/src/main/kotlin/net/corda/notarydemo/NotaryDemo.kt index f2511e7840..be4f1f03b4 100644 --- a/samples/raft-notary-demo/src/main/kotlin/net/corda/notarydemo/NotaryDemo.kt +++ b/samples/raft-notary-demo/src/main/kotlin/net/corda/notarydemo/NotaryDemo.kt @@ -24,7 +24,6 @@ fun main(args: Array) { /** Interface for using the notary demo API from a client. */ private class NotaryDemoClientApi(val rpc: CordaRPCOps) { - private val notary by lazy { rpc.networkMapUpdates().first.first { it.advertisedServices.any { it.info.type.isNotary() } }.notaryIdentity } @@ -37,17 +36,21 @@ private class NotaryDemoClientApi(val rpc: CordaRPCOps) { private val TRANSACTION_COUNT = 10 } - /** Makes a call to the demo api to start transaction notarisation. */ + /** Makes calls to the node rpc to start transaction notarisation. */ fun startNotarisation() { - val response = notarise(TRANSACTION_COUNT) - println(response) + notarise(TRANSACTION_COUNT) } - fun notarise(count: Int): String { + fun notarise(count: Int) { val transactions = buildTransactions(count) val signers = notariseTransactions(transactions) + val transactionSigners = transactions.zip(signers).map { + val (tx, signer) = it + "Tx [${tx.tx.id.prefixChars()}..] signed by $signer" + }.joinToString("\n") - return buildResponse(transactions, signers) + println("Notary: \"${notary.name}\", with composite key: ${notary.owningKey}\n" + + "Notarised ${transactions.size} transactions:\n" + transactionSigners) } /** @@ -72,20 +75,7 @@ private class NotaryDemoClientApi(val rpc: CordaRPCOps) { val signatureFutures = transactions.map { rpc.startFlow(NotaryFlow::Client, it).returnValue.toBlocking().toFuture() } - val signers = signatureFutures.map { it.get().by.toStringShort() } - return signers - } - - /** Builds a response for the caller containing the list of transaction ids and corresponding signer keys. */ - private fun buildResponse(transactions: List, signers: List): String { - val transactionSigners = transactions.zip(signers).map { - val (tx, signer) = it - "Tx [${tx.tx.id.prefixChars()}..] signed by $signer" - }.joinToString("\n") - - val response = "Notary: \"${notary.name}\", with composite key: ${notary.owningKey}\n" + - "Notarised ${transactions.size} transactions:\n" + transactionSigners - return response + return signatureFutures.map { it.get().by.toStringShort() } } } diff --git a/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt b/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt index b8a9acf55f..2deef657f4 100644 --- a/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt +++ b/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt @@ -1,9 +1,9 @@ package net.corda.vega +import com.google.common.util.concurrent.Futures import com.opengamma.strata.product.common.BuySell import net.corda.core.getOrThrow import net.corda.core.node.services.ServiceInfo -import net.corda.node.driver.NodeHandle import net.corda.node.driver.driver import net.corda.node.services.transactions.SimpleNotaryService import net.corda.testing.IntegrationTestCategory @@ -12,14 +12,11 @@ import net.corda.vega.api.PortfolioApi import net.corda.vega.api.PortfolioApiUtils import net.corda.vega.api.SwapDataModel import net.corda.vega.api.SwapDataView -import net.corda.vega.portfolio.Portfolio import org.junit.Test import java.math.BigDecimal import java.time.LocalDate -import java.util.* -import java.util.concurrent.Future -class SimmValuationTest: IntegrationTestCategory { +class SimmValuationTest : IntegrationTestCategory { private companion object { // SIMM demo can only currently handle one valuation date due to a lack of market data or a market data source. val valuationDate = LocalDate.parse("2016-06-06") @@ -31,23 +28,20 @@ class SimmValuationTest: IntegrationTestCategory { @Test fun `runs SIMM valuation demo`() { driver(isDebug = true) { startNode("Controller", setOf(ServiceInfo(SimpleNotaryService.type))).getOrThrow() - val nodeA = getSimmNodeApi(startNode(nodeALegalName)) - val nodeB = getSimmNodeApi(startNode(nodeBLegalName)) - val nodeBParty = getPartyWithName(nodeA, nodeBLegalName) - val nodeAParty = getPartyWithName(nodeB, nodeALegalName) + val (nodeA, nodeB) = Futures.allAsList(startNode(nodeALegalName), startNode(nodeBLegalName)).getOrThrow() + val (nodeAApi, nodeBApi) = Futures.allAsList(startWebserver(nodeA), startWebserver(nodeB)) + .getOrThrow() + .map { HttpApi.fromHostAndPort(it, "api/simmvaluationdemo") } + val nodeBParty = getPartyWithName(nodeAApi, nodeBLegalName) + val nodeAParty = getPartyWithName(nodeBApi, nodeALegalName) - assert(createTradeBetween(nodeA, nodeBParty, testTradeId)) - assert(tradeExists(nodeB, nodeAParty, testTradeId)) - assert(runValuationsBetween(nodeA, nodeBParty)) - assert(valuationExists(nodeB, nodeAParty)) + assert(createTradeBetween(nodeAApi, nodeBParty, testTradeId)) + assert(tradeExists(nodeBApi, nodeAParty, testTradeId)) + assert(runValuationsBetween(nodeAApi, nodeBParty)) + assert(valuationExists(nodeBApi, nodeAParty)) } } - private fun getSimmNodeApi(futureNode: Future): HttpApi { - val nodeAddr = futureNode.getOrThrow().configuration.webAddress - return HttpApi.fromHostAndPort(nodeAddr, "api/simmvaluationdemo") - } - private fun getPartyWithName(partyApi: HttpApi, countryparty: String): PortfolioApi.ApiParty = getAvailablePartiesFor(partyApi).counterparties.single { it.text == countryparty } diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/Main.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/Main.kt index a66be19642..9655903d5e 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/Main.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/Main.kt @@ -1,5 +1,7 @@ package net.corda.vega +import com.google.common.util.concurrent.Futures +import net.corda.core.getOrThrow import net.corda.core.node.services.ServiceInfo import net.corda.node.driver.driver import net.corda.node.services.transactions.SimpleNotaryService @@ -12,9 +14,16 @@ import net.corda.node.services.transactions.SimpleNotaryService fun main(args: Array) { driver(dsl = { startNode("Controller", setOf(ServiceInfo(SimpleNotaryService.type))) - startNode("Bank A") - startNode("Bank B") - startNode("Bank C") + val (nodeA, nodeB, nodeC) = Futures.allAsList( + startNode("Bank A"), + startNode("Bank B"), + startNode("Bank C") + ).getOrThrow() + + startWebserver(nodeA) + startWebserver(nodeB) + startWebserver(nodeC) + waitForAllNodesToFinish() }, isDebug = true) } diff --git a/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt b/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt index a99247b6ad..97607e55a4 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -155,6 +155,7 @@ open class MockTransactionStorage : TransactionStorage { @ThreadSafe class MockStorageService(override val attachments: AttachmentStorage = MockAttachmentStorage(), override val validatedTransactions: TransactionStorage = MockTransactionStorage(), + override val uploaders: List = listOf(), override val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage()) : SingletonSerializeAsToken(), TxWritableStorageService