mirror of
https://github.com/corda/corda.git
synced 2025-06-18 15:18:16 +00:00
Moved file uploading to RPC interface.
This commit is contained in:
committed by
Clinton Alexander
parent
d4b6e32682
commit
537ffae113
@ -107,6 +107,11 @@ interface CordaRPCOps : RPCOps {
|
|||||||
*/
|
*/
|
||||||
fun uploadAttachment(jar: InputStream): SecureHash
|
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.
|
* Returns the node-local current time.
|
||||||
*/
|
*/
|
||||||
|
@ -10,6 +10,8 @@ import net.corda.core.toFuture
|
|||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.PrivateKey
|
import java.security.PrivateKey
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
@ -216,6 +218,16 @@ interface KeyManagementService {
|
|||||||
fun freshKey(): KeyPair
|
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
|
* 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
|
* 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). */
|
/** Provides access to storage of arbitrary JAR files (which may contain only data, no code). */
|
||||||
val attachments: AttachmentStorage
|
val attachments: AttachmentStorage
|
||||||
|
|
||||||
|
/** Provides file uploads of arbitrary files to services **/
|
||||||
|
val uploaders: List<FileUploader>
|
||||||
|
|
||||||
val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage
|
val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ class ArgsParser {
|
|||||||
.withRequiredArg()
|
.withRequiredArg()
|
||||||
.defaultsTo("node.conf")
|
.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 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()
|
private val helpArg = optionParser.accepts("help").forHelp()
|
||||||
|
|
||||||
fun parse(vararg args: String): CmdLineOptions {
|
fun parse(vararg args: String): CmdLineOptions {
|
||||||
@ -30,13 +31,14 @@ class ArgsParser {
|
|||||||
}
|
}
|
||||||
val baseDirectory = Paths.get(optionSet.valueOf(baseDirectoryArg)).normalize().toAbsolutePath()
|
val baseDirectory = Paths.get(optionSet.valueOf(baseDirectoryArg)).normalize().toAbsolutePath()
|
||||||
val configFile = baseDirectory / optionSet.valueOf(configFileArg)
|
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)
|
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<String, Any?> = emptyMap()): Config {
|
fun loadConfig(allowMissingConfig: Boolean = false, configOverrides: Map<String, Any?> = emptyMap()): Config {
|
||||||
return ConfigHelper.loadConfig(baseDirectory, configFile, allowMissingConfig, configOverrides)
|
return ConfigHelper.loadConfig(baseDirectory, configFile, allowMissingConfig, configOverrides)
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import net.corda.core.utilities.Emoji
|
|||||||
import net.corda.node.internal.Node
|
import net.corda.node.internal.Node
|
||||||
import net.corda.node.services.config.FullNodeConfiguration
|
import net.corda.node.services.config.FullNodeConfiguration
|
||||||
import net.corda.node.utilities.ANSIProgressObserver
|
import net.corda.node.utilities.ANSIProgressObserver
|
||||||
|
import net.corda.node.webserver.WebServer
|
||||||
import org.fusesource.jansi.Ansi
|
import org.fusesource.jansi.Ansi
|
||||||
import org.fusesource.jansi.AnsiConsole
|
import org.fusesource.jansi.AnsiConsole
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@ -78,33 +79,30 @@ fun main(args: Array<String>) {
|
|||||||
log.info("VM ${info.vmName} ${info.vmVendor} ${info.vmVersion}")
|
log.info("VM ${info.vmName} ${info.vmVendor} ${info.vmVersion}")
|
||||||
log.info("Machine: ${InetAddress.getLocalHost().hostName}")
|
log.info("Machine: ${InetAddress.getLocalHost().hostName}")
|
||||||
log.info("Working Directory: ${cmdlineOptions.baseDirectory}")
|
log.info("Working Directory: ${cmdlineOptions.baseDirectory}")
|
||||||
|
log.info("Started as webserver: ${cmdlineOptions.isWebserver}")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cmdlineOptions.baseDirectory.createDirectories()
|
cmdlineOptions.baseDirectory.createDirectories()
|
||||||
|
|
||||||
val node = conf.createNode()
|
if(!cmdlineOptions.isWebserver) {
|
||||||
node.start()
|
val node = conf.createNode()
|
||||||
printPluginsAndServices(node)
|
node.start()
|
||||||
|
printPluginsAndServices(node)
|
||||||
|
|
||||||
thread {
|
node.networkMapRegistrationFuture.success {
|
||||||
Thread.sleep(30.seconds.toMillis())
|
val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0
|
||||||
while (!node.networkMapRegistrationFuture.isDone) {
|
printBasicNodeInfo("Node started up and registered in $elapsed sec")
|
||||||
printBasicNodeInfo("Waiting for response from network map ...")
|
|
||||||
Thread.sleep(30.seconds.toMillis())
|
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) {
|
} catch (e: Exception) {
|
||||||
log.error("Exception during node startup", e)
|
log.error("Exception during node startup", e)
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
package net.corda.node.driver
|
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.net.HostAndPort
|
||||||
import com.google.common.util.concurrent.Futures
|
import com.google.common.util.concurrent.Futures
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
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.NodeInfo
|
||||||
import net.corda.core.node.services.ServiceInfo
|
import net.corda.core.node.services.ServiceInfo
|
||||||
import net.corda.core.node.services.ServiceType
|
import net.corda.core.node.services.ServiceType
|
||||||
import net.corda.core.utilities.ApiUtils
|
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
import net.corda.node.services.User
|
import net.corda.node.services.User
|
||||||
import net.corda.node.services.config.ConfigHelper
|
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.messaging.NodeMessagingClient
|
||||||
import net.corda.node.services.network.NetworkMapService
|
import net.corda.node.services.network.NetworkMapService
|
||||||
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
||||||
import net.corda.node.utilities.JsonSupport
|
|
||||||
import net.corda.node.utilities.ServiceIdentityGenerator
|
import net.corda.node.utilities.ServiceIdentityGenerator
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.*
|
import java.net.*
|
||||||
@ -44,9 +41,6 @@ import java.util.concurrent.*
|
|||||||
import java.util.concurrent.TimeUnit.MILLISECONDS
|
import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||||
import java.util.concurrent.TimeUnit.SECONDS
|
import java.util.concurrent.TimeUnit.SECONDS
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
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 <DI : DriverDSLExposedInterface, D : DriverDSLInternalInterface, A> genericD
|
|||||||
return returnValue
|
return returnValue
|
||||||
} catch (exception: Throwable) {
|
} catch (exception: Throwable) {
|
||||||
println("Driver shutting down because of exception $exception")
|
println("Driver shutting down because of exception $exception")
|
||||||
|
exception.printStackTrace()
|
||||||
throw exception
|
throw exception
|
||||||
} finally {
|
} finally {
|
||||||
driverDsl.shutdown()
|
driverDsl.shutdown()
|
||||||
@ -423,19 +418,23 @@ open class DriverDSL(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun queryWebserver(configuration: FullNodeConfiguration): HostAndPort? {
|
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 url = URL(protocol + configuration.webAddress.toString() + "/api/status")
|
||||||
val client = OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS).build()
|
val client = OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS).build()
|
||||||
val retries = 5
|
val retries = 5
|
||||||
|
|
||||||
for(i in 0..retries) {
|
for (i in 0..retries) {
|
||||||
try {
|
try {
|
||||||
val response = client.newCall(Request.Builder().url(url).build()).execute()
|
val response = client.newCall(Request.Builder().url(url).build()).execute()
|
||||||
if (response.isSuccessful && (response.body().string() == "started")) {
|
if (response.isSuccessful && (response.body().string() == "started")) {
|
||||||
return configuration.webAddress
|
return configuration.webAddress
|
||||||
}
|
}
|
||||||
} catch(e: ConnectException) {
|
} 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(
|
configOverrides = mapOf(
|
||||||
"myLegalName" to networkMapLegalName,
|
"myLegalName" to networkMapLegalName,
|
||||||
"artemisAddress" to networkMapAddress.toString(),
|
"artemisAddress" to networkMapAddress.toString(),
|
||||||
"webAddress" to apiAddress.toString(),
|
|
||||||
"extraAdvertisedServiceIds" to "",
|
"extraAdvertisedServiceIds" to "",
|
||||||
"useTestClock" to useTestClock
|
"useTestClock" to useTestClock
|
||||||
)
|
)
|
||||||
@ -533,7 +531,7 @@ open class DriverDSL(
|
|||||||
executorService: ScheduledExecutorService,
|
executorService: ScheduledExecutorService,
|
||||||
nodeConf: FullNodeConfiguration,
|
nodeConf: FullNodeConfiguration,
|
||||||
debugPort: Int?): ListenableFuture<Process> {
|
debugPort: Int?): ListenableFuture<Process> {
|
||||||
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 separator = System.getProperty("file.separator")
|
||||||
val classpath = System.getProperty("java.class.path")
|
val classpath = System.getProperty("java.class.path")
|
||||||
val path = System.getProperty("java.home") + separator + "bin" + separator + "java"
|
val path = System.getProperty("java.home") + separator + "bin" + separator + "java"
|
||||||
@ -548,7 +546,7 @@ open class DriverDSL(
|
|||||||
listOf(
|
listOf(
|
||||||
"-cp", classpath, className,
|
"-cp", classpath, className,
|
||||||
"--base-directory", nodeConf.baseDirectory.toString(),
|
"--base-directory", nodeConf.baseDirectory.toString(),
|
||||||
"--web-address", nodeConf.webAddress.toString())
|
"--webserver")
|
||||||
val builder = ProcessBuilder(javaArgs)
|
val builder = ProcessBuilder(javaArgs)
|
||||||
builder.redirectError(Paths.get("error.$className.log").toFile())
|
builder.redirectError(Paths.get("error.$className.log").toFile())
|
||||||
builder.inheritIO()
|
builder.inheritIO()
|
||||||
|
@ -51,6 +51,7 @@ import net.corda.node.utilities.databaseTransaction
|
|||||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.Database
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
|
import java.io.File
|
||||||
import java.nio.file.FileAlreadyExistsException
|
import java.nio.file.FileAlreadyExistsException
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
@ -103,11 +104,6 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
// low-performance prototyping period.
|
// low-performance prototyping period.
|
||||||
protected abstract val serverThread: AffinityExecutor
|
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<AcceptsFileUpload>()
|
|
||||||
val servicesThatAcceptUploads: List<AcceptsFileUpload> = _servicesThatAcceptUploads
|
|
||||||
|
|
||||||
private val flowFactories = ConcurrentHashMap<Class<*>, (Party) -> FlowLogic<*>>()
|
private val flowFactories = ConcurrentHashMap<Class<*>, (Party) -> FlowLogic<*>>()
|
||||||
protected val partyKeys = mutableSetOf<KeyPair>()
|
protected val partyKeys = mutableSetOf<KeyPair>()
|
||||||
|
|
||||||
@ -225,6 +221,10 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
customServices.clear()
|
customServices.clear()
|
||||||
customServices.addAll(buildPluginServices(tokenizableServices))
|
customServices.addAll(buildPluginServices(tokenizableServices))
|
||||||
|
|
||||||
|
val uploaders: List<FileUploader> = 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
|
// TODO: uniquenessProvider creation should be inside makeNotaryService(), but notary service initialisation
|
||||||
// depends on smm, while smm depends on tokenizableServices, which uniquenessProvider is part of
|
// depends on smm, while smm depends on tokenizableServices, which uniquenessProvider is part of
|
||||||
advertisedServices.singleOrNull { it.type.isNotary() }?.let {
|
advertisedServices.singleOrNull { it.type.isNotary() }?.let {
|
||||||
@ -350,9 +350,6 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
val service = serviceConstructor.apply(services)
|
val service = serviceConstructor.apply(services)
|
||||||
serviceList.add(service)
|
serviceList.add(service)
|
||||||
tokenizableServices.add(service)
|
tokenizableServices.add(service)
|
||||||
if (service is AcceptsFileUpload) {
|
|
||||||
_servicesThatAcceptUploads += service
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return serviceList
|
return serviceList
|
||||||
}
|
}
|
||||||
@ -482,7 +479,6 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
val attachments = makeAttachmentStorage(dir)
|
val attachments = makeAttachmentStorage(dir)
|
||||||
val checkpointStorage = DBCheckpointStorage()
|
val checkpointStorage = DBCheckpointStorage()
|
||||||
val transactionStorage = DBTransactionStorage()
|
val transactionStorage = DBTransactionStorage()
|
||||||
_servicesThatAcceptUploads += attachments
|
|
||||||
val stateMachineTransactionMappingStorage = DBTransactionMappingStorage()
|
val stateMachineTransactionMappingStorage = DBTransactionMappingStorage()
|
||||||
return Pair(
|
return Pair(
|
||||||
constructStorageService(attachments, transactionStorage, stateMachineTransactionMappingStorage),
|
constructStorageService(attachments, transactionStorage, stateMachineTransactionMappingStorage),
|
||||||
|
@ -108,6 +108,10 @@ class CordaRPCOpsImpl(
|
|||||||
override fun attachmentExists(id: SecureHash) = services.storageService.attachments.openAttachment(id) != null
|
override fun attachmentExists(id: SecureHash) = services.storageService.attachments.openAttachment(id) != null
|
||||||
override fun uploadAttachment(jar: InputStream) = services.storageService.attachments.importAttachment(jar)
|
override fun uploadAttachment(jar: InputStream) = services.storageService.attachments.importAttachment(jar)
|
||||||
override fun currentNodeTime(): Instant = Instant.now(services.clock)
|
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 partyFromKey(key: CompositeKey) = services.identityService.partyFromKey(key)
|
||||||
override fun partyFromName(name: String) = services.identityService.partyFromName(name)
|
override fun partyFromName(name: String) = services.identityService.partyFromName(name)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package net.corda.node.services.api
|
package net.corda.node.services.api
|
||||||
|
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.node.services.FileUploader
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -7,16 +9,12 @@ import java.io.InputStream
|
|||||||
*
|
*
|
||||||
* TODO: In future, also accept uploads over the MQ interface too.
|
* 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. */
|
/** A string that prefixes the URLs, e.g. "attachments" or "interest-rates". Should be OK for URLs. */
|
||||||
val dataTypePrefix: String
|
val dataTypePrefix: String
|
||||||
|
|
||||||
/** What file extensions are acceptable for the file to be handed to upload() */
|
/** What file extensions are acceptable for the file to be handed to upload() */
|
||||||
val acceptableFileExtensions: List<String>
|
val acceptableFileExtensions: List<String>
|
||||||
|
|
||||||
/**
|
override fun accepts(prefix: String) = prefix == dataTypePrefix
|
||||||
* 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
|
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ import net.corda.node.services.messaging.ArtemisMessagingComponent.NetworkMapAdd
|
|||||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
||||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||||
import org.apache.activemq.artemis.api.core.SimpleString
|
import org.apache.activemq.artemis.api.core.SimpleString
|
||||||
|
import org.apache.commons.fileupload.MultipartStream
|
||||||
import org.objenesis.strategy.StdInstantiatorStrategy
|
import org.objenesis.strategy.StdInstantiatorStrategy
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@ -146,6 +147,7 @@ private class RPCKryo(observableSerializer: Serializer<Observable<Any>>? = null)
|
|||||||
|
|
||||||
register(BufferedInputStream::class.java, InputStreamSerializer)
|
register(BufferedInputStream::class.java, InputStreamSerializer)
|
||||||
register(Class.forName("sun.net.www.protocol.jar.JarURLConnection\$JarURLInputStream"), InputStreamSerializer)
|
register(Class.forName("sun.net.www.protocol.jar.JarURLConnection\$JarURLInputStream"), InputStreamSerializer)
|
||||||
|
register(MultipartStream.ItemInputStream::class.java, InputStreamSerializer)
|
||||||
|
|
||||||
noReferencesWithin<WireTransaction>()
|
noReferencesWithin<WireTransaction>()
|
||||||
|
|
||||||
@ -208,6 +210,7 @@ private class RPCKryo(observableSerializer: Serializer<Observable<Any>>? = null)
|
|||||||
register(SimpleString::class.java)
|
register(SimpleString::class.java)
|
||||||
register(ServiceEntry::class.java)
|
register(ServiceEntry::class.java)
|
||||||
// Exceptions. We don't bother sending the stack traces as the client will fill in its own anyway.
|
// 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(IllegalArgumentException::class.java)
|
||||||
register(ArrayIndexOutOfBoundsException::class.java)
|
register(ArrayIndexOutOfBoundsException::class.java)
|
||||||
register(IndexOutOfBoundsException::class.java)
|
register(IndexOutOfBoundsException::class.java)
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
package net.corda.node.services.persistence
|
package net.corda.node.services.persistence
|
||||||
|
|
||||||
import net.corda.core.node.services.AttachmentStorage
|
import net.corda.core.node.services.*
|
||||||
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.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
|
|
||||||
open class StorageServiceImpl(override val attachments: AttachmentStorage,
|
open class StorageServiceImpl(override val attachments: AttachmentStorage,
|
||||||
override val validatedTransactions: TransactionStorage,
|
override val validatedTransactions: TransactionStorage,
|
||||||
override val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage)
|
override val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage)
|
||||||
: SingletonSerializeAsToken(), TxWritableStorageService
|
: SingletonSerializeAsToken(), TxWritableStorageService {
|
||||||
|
lateinit override var uploaders: List<FileUploader>
|
||||||
|
|
||||||
|
fun initUploaders(uploadersList: List<FileUploader>) {
|
||||||
|
uploaders = uploadersList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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<String>) {
|
|
||||||
// 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")
|
|
||||||
}
|
|
@ -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<String>) {
|
|
||||||
// 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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -102,6 +102,7 @@ class WebServer(val config: FullNodeConfiguration) {
|
|||||||
private fun buildServletContextHandler(localRpc: CordaRPCOps): ServletContextHandler {
|
private fun buildServletContextHandler(localRpc: CordaRPCOps): ServletContextHandler {
|
||||||
return ServletContextHandler().apply {
|
return ServletContextHandler().apply {
|
||||||
contextPath = "/"
|
contextPath = "/"
|
||||||
|
setAttribute("rpc", localRpc)
|
||||||
addServlet(DataUploadServlet::class.java, "/upload/*")
|
addServlet(DataUploadServlet::class.java, "/upload/*")
|
||||||
addServlet(AttachmentDownloadServlet::class.java, "/attachments/*")
|
addServlet(AttachmentDownloadServlet::class.java, "/attachments/*")
|
||||||
|
|
||||||
@ -111,8 +112,6 @@ class WebServer(val config: FullNodeConfiguration) {
|
|||||||
resourceConfig.register(APIServerImpl(localRpc))
|
resourceConfig.register(APIServerImpl(localRpc))
|
||||||
|
|
||||||
val webAPIsOnClasspath = pluginRegistries.flatMap { x -> x.webApis }
|
val webAPIsOnClasspath = pluginRegistries.flatMap { x -> x.webApis }
|
||||||
println("NUM PLUGINS: ${pluginRegistries.size}")
|
|
||||||
println("NUM WEBAPIS: ${webAPIsOnClasspath.size}")
|
|
||||||
for (webapi in webAPIsOnClasspath) {
|
for (webapi in webAPIsOnClasspath) {
|
||||||
log.info("Add plugin web API from attachment $webapi")
|
log.info("Add plugin web API from attachment $webapi")
|
||||||
val customAPI = try {
|
val customAPI = try {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.node.webserver.servlets
|
package net.corda.node.webserver.servlets
|
||||||
|
|
||||||
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
import net.corda.node.internal.Node
|
import net.corda.node.internal.Node
|
||||||
import net.corda.node.services.api.AcceptsFileUpload
|
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.
|
* 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<DataUploadServlet>()
|
private val log = loggerFor<DataUploadServlet>()
|
||||||
|
|
||||||
override fun doPost(req: HttpServletRequest, resp: HttpServletResponse) {
|
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.
|
@Suppress("DEPRECATION") // Bogus warning due to superclass static method being deprecated.
|
||||||
val isMultipart = ServletFileUpload.isMultipartContent(req)
|
val isMultipart = ServletFileUpload.isMultipartContent(req)
|
||||||
|
val rpc = servletContext.getAttribute("rpc") as CordaRPCOps
|
||||||
|
|
||||||
if (!isMultipart) {
|
if (!isMultipart) {
|
||||||
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "This end point is for data uploads only.")
|
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "This end point is for data uploads only.")
|
||||||
return
|
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 upload = ServletFileUpload()
|
||||||
val iterator = upload.getItemIterator(req)
|
val iterator = upload.getItemIterator(req)
|
||||||
val messages = ArrayList<String>()
|
val messages = ArrayList<String>()
|
||||||
@ -43,18 +37,15 @@ class DataUploadServlet : HttpServlet() {
|
|||||||
|
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
val item = iterator.next()
|
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}")
|
log.info("Receiving ${item.name}")
|
||||||
|
|
||||||
item.openStream().use {
|
try {
|
||||||
val message = acceptor.upload(it)
|
val dataType = req.pathInfo.substring(1).substringBefore('/')
|
||||||
log.info("${item.name} successfully accepted: $message")
|
messages += rpc.uploadFile(dataType, item.name, item.openStream())
|
||||||
messages += message
|
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
|
val writer = resp.writer
|
||||||
messages.forEach { writer.println(it) }
|
messages.forEach { writer.println(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findAcceptor(node: Node, req: HttpServletRequest): AcceptsFileUpload? {
|
|
||||||
return node.servicesThatAcceptUploads.firstOrNull { req.pathInfo.substring(1).substringBefore('/') == it.dataTypePrefix }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,8 @@ class ArgsParserTest {
|
|||||||
baseDirectory = workingDirectory,
|
baseDirectory = workingDirectory,
|
||||||
configFile = workingDirectory / "node.conf",
|
configFile = workingDirectory / "node.conf",
|
||||||
help = false,
|
help = false,
|
||||||
logToConsole = false))
|
logToConsole = false,
|
||||||
|
isWebserver = false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -28,7 +29,8 @@ class ArgsParserTest {
|
|||||||
baseDirectory = expectedBaseDir,
|
baseDirectory = expectedBaseDir,
|
||||||
configFile = expectedBaseDir / "node.conf",
|
configFile = expectedBaseDir / "node.conf",
|
||||||
help = false,
|
help = false,
|
||||||
logToConsole = false))
|
logToConsole = false,
|
||||||
|
isWebserver = false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -39,7 +41,8 @@ class ArgsParserTest {
|
|||||||
baseDirectory = baseDirectory,
|
baseDirectory = baseDirectory,
|
||||||
configFile = baseDirectory / "node.conf",
|
configFile = baseDirectory / "node.conf",
|
||||||
help = false,
|
help = false,
|
||||||
logToConsole = false))
|
logToConsole = false,
|
||||||
|
isWebserver = false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -49,7 +52,8 @@ class ArgsParserTest {
|
|||||||
baseDirectory = workingDirectory,
|
baseDirectory = workingDirectory,
|
||||||
configFile = workingDirectory / "different.conf",
|
configFile = workingDirectory / "different.conf",
|
||||||
help = false,
|
help = false,
|
||||||
logToConsole = false))
|
logToConsole = false,
|
||||||
|
isWebserver = false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -60,7 +64,19 @@ class ArgsParserTest {
|
|||||||
baseDirectory = workingDirectory,
|
baseDirectory = workingDirectory,
|
||||||
configFile = configFile,
|
configFile = configFile,
|
||||||
help = false,
|
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
|
@Test
|
||||||
|
@ -50,8 +50,9 @@ private class BankOfCordaDriver {
|
|||||||
driver(dsl = {
|
driver(dsl = {
|
||||||
val user = User("user1", "test", permissions = setOf(startFlowPermission<CashFlow>(), startFlowPermission<IssuerFlow.IssuanceRequester>()))
|
val user = User("user1", "test", permissions = setOf(startFlowPermission<CashFlow>(), startFlowPermission<IssuerFlow.IssuanceRequester>()))
|
||||||
startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type)))
|
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))
|
startNode("BigCorporation", rpcUsers = listOf(user))
|
||||||
|
startWebserver(bankOfCorda.get())
|
||||||
waitForAllNodesToFinish()
|
waitForAllNodesToFinish()
|
||||||
}, isDebug = true)
|
}, isDebug = true)
|
||||||
}
|
}
|
||||||
@ -75,7 +76,8 @@ private class BankOfCordaDriver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
printHelp(parser)
|
println("Exception occurred: $e \n ${e.printStackTrace()}")
|
||||||
|
exitProcess(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package net.corda.irs
|
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.core.node.services.ServiceInfo
|
||||||
import net.corda.irs.api.NodeInterestRates
|
import net.corda.irs.api.NodeInterestRates
|
||||||
import net.corda.node.driver.driver
|
import net.corda.node.driver.driver
|
||||||
@ -11,9 +13,16 @@ import net.corda.node.services.transactions.SimpleNotaryService
|
|||||||
*/
|
*/
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
driver(dsl = {
|
driver(dsl = {
|
||||||
startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type), ServiceInfo(NodeInterestRates.type))).get()
|
val (controller, nodeA, nodeB) = Futures.allAsList(
|
||||||
startNode("Bank A")
|
startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type), ServiceInfo(NodeInterestRates.type))),
|
||||||
startNode("Bank B")
|
startNode("Bank A"),
|
||||||
|
startNode("Bank B")
|
||||||
|
).getOrThrow()
|
||||||
|
|
||||||
|
startWebserver(controller)
|
||||||
|
startWebserver(nodeA)
|
||||||
|
startWebserver(nodeB)
|
||||||
|
|
||||||
waitForAllNodesToFinish()
|
waitForAllNodesToFinish()
|
||||||
}, useTestClock = true, isDebug = true)
|
}, useTestClock = true, isDebug = true)
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import net.corda.node.utilities.JDBCHashedTable
|
|||||||
import net.corda.node.utilities.localDate
|
import net.corda.node.utilities.localDate
|
||||||
import org.jetbrains.exposed.sql.ResultRow
|
import org.jetbrains.exposed.sql.ResultRow
|
||||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||||
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
@ -110,7 +111,10 @@ object NodeInterestRates {
|
|||||||
|
|
||||||
override fun upload(data: InputStream): String {
|
override fun upload(data: InputStream): String {
|
||||||
val fixes = parseFile(data.bufferedReader().readText())
|
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"
|
val msg = "Interest rates oracle accepted ${fixes.size} new interest rate fixes"
|
||||||
println(msg)
|
println(msg)
|
||||||
return msg
|
return msg
|
||||||
|
@ -24,7 +24,6 @@ fun main(args: Array<String>) {
|
|||||||
|
|
||||||
/** Interface for using the notary demo API from a client. */
|
/** Interface for using the notary demo API from a client. */
|
||||||
private class NotaryDemoClientApi(val rpc: CordaRPCOps) {
|
private class NotaryDemoClientApi(val rpc: CordaRPCOps) {
|
||||||
|
|
||||||
private val notary by lazy {
|
private val notary by lazy {
|
||||||
rpc.networkMapUpdates().first.first { it.advertisedServices.any { it.info.type.isNotary() } }.notaryIdentity
|
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
|
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() {
|
fun startNotarisation() {
|
||||||
val response = notarise(TRANSACTION_COUNT)
|
notarise(TRANSACTION_COUNT)
|
||||||
println(response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun notarise(count: Int): String {
|
fun notarise(count: Int) {
|
||||||
val transactions = buildTransactions(count)
|
val transactions = buildTransactions(count)
|
||||||
val signers = notariseTransactions(transactions)
|
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 {
|
val signatureFutures = transactions.map {
|
||||||
rpc.startFlow(NotaryFlow::Client, it).returnValue.toBlocking().toFuture()
|
rpc.startFlow(NotaryFlow::Client, it).returnValue.toBlocking().toFuture()
|
||||||
}
|
}
|
||||||
val signers = signatureFutures.map { it.get().by.toStringShort() }
|
return 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<SignedTransaction>, signers: List<String>): 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package net.corda.vega
|
package net.corda.vega
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.Futures
|
||||||
import com.opengamma.strata.product.common.BuySell
|
import com.opengamma.strata.product.common.BuySell
|
||||||
import net.corda.core.getOrThrow
|
import net.corda.core.getOrThrow
|
||||||
import net.corda.core.node.services.ServiceInfo
|
import net.corda.core.node.services.ServiceInfo
|
||||||
import net.corda.node.driver.NodeHandle
|
|
||||||
import net.corda.node.driver.driver
|
import net.corda.node.driver.driver
|
||||||
import net.corda.node.services.transactions.SimpleNotaryService
|
import net.corda.node.services.transactions.SimpleNotaryService
|
||||||
import net.corda.testing.IntegrationTestCategory
|
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.PortfolioApiUtils
|
||||||
import net.corda.vega.api.SwapDataModel
|
import net.corda.vega.api.SwapDataModel
|
||||||
import net.corda.vega.api.SwapDataView
|
import net.corda.vega.api.SwapDataView
|
||||||
import net.corda.vega.portfolio.Portfolio
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.Future
|
|
||||||
|
|
||||||
class SimmValuationTest: IntegrationTestCategory {
|
class SimmValuationTest : IntegrationTestCategory {
|
||||||
private companion object {
|
private companion object {
|
||||||
// SIMM demo can only currently handle one valuation date due to a lack of market data or a market data source.
|
// 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")
|
val valuationDate = LocalDate.parse("2016-06-06")
|
||||||
@ -31,23 +28,20 @@ class SimmValuationTest: IntegrationTestCategory {
|
|||||||
@Test fun `runs SIMM valuation demo`() {
|
@Test fun `runs SIMM valuation demo`() {
|
||||||
driver(isDebug = true) {
|
driver(isDebug = true) {
|
||||||
startNode("Controller", setOf(ServiceInfo(SimpleNotaryService.type))).getOrThrow()
|
startNode("Controller", setOf(ServiceInfo(SimpleNotaryService.type))).getOrThrow()
|
||||||
val nodeA = getSimmNodeApi(startNode(nodeALegalName))
|
val (nodeA, nodeB) = Futures.allAsList(startNode(nodeALegalName), startNode(nodeBLegalName)).getOrThrow()
|
||||||
val nodeB = getSimmNodeApi(startNode(nodeBLegalName))
|
val (nodeAApi, nodeBApi) = Futures.allAsList(startWebserver(nodeA), startWebserver(nodeB))
|
||||||
val nodeBParty = getPartyWithName(nodeA, nodeBLegalName)
|
.getOrThrow()
|
||||||
val nodeAParty = getPartyWithName(nodeB, nodeALegalName)
|
.map { HttpApi.fromHostAndPort(it, "api/simmvaluationdemo") }
|
||||||
|
val nodeBParty = getPartyWithName(nodeAApi, nodeBLegalName)
|
||||||
|
val nodeAParty = getPartyWithName(nodeBApi, nodeALegalName)
|
||||||
|
|
||||||
assert(createTradeBetween(nodeA, nodeBParty, testTradeId))
|
assert(createTradeBetween(nodeAApi, nodeBParty, testTradeId))
|
||||||
assert(tradeExists(nodeB, nodeAParty, testTradeId))
|
assert(tradeExists(nodeBApi, nodeAParty, testTradeId))
|
||||||
assert(runValuationsBetween(nodeA, nodeBParty))
|
assert(runValuationsBetween(nodeAApi, nodeBParty))
|
||||||
assert(valuationExists(nodeB, nodeAParty))
|
assert(valuationExists(nodeBApi, nodeAParty))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSimmNodeApi(futureNode: Future<NodeHandle>): HttpApi {
|
|
||||||
val nodeAddr = futureNode.getOrThrow().configuration.webAddress
|
|
||||||
return HttpApi.fromHostAndPort(nodeAddr, "api/simmvaluationdemo")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getPartyWithName(partyApi: HttpApi, countryparty: String): PortfolioApi.ApiParty =
|
private fun getPartyWithName(partyApi: HttpApi, countryparty: String): PortfolioApi.ApiParty =
|
||||||
getAvailablePartiesFor(partyApi).counterparties.single { it.text == countryparty }
|
getAvailablePartiesFor(partyApi).counterparties.single { it.text == countryparty }
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package net.corda.vega
|
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.core.node.services.ServiceInfo
|
||||||
import net.corda.node.driver.driver
|
import net.corda.node.driver.driver
|
||||||
import net.corda.node.services.transactions.SimpleNotaryService
|
import net.corda.node.services.transactions.SimpleNotaryService
|
||||||
@ -12,9 +14,16 @@ import net.corda.node.services.transactions.SimpleNotaryService
|
|||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
driver(dsl = {
|
driver(dsl = {
|
||||||
startNode("Controller", setOf(ServiceInfo(SimpleNotaryService.type)))
|
startNode("Controller", setOf(ServiceInfo(SimpleNotaryService.type)))
|
||||||
startNode("Bank A")
|
val (nodeA, nodeB, nodeC) = Futures.allAsList(
|
||||||
startNode("Bank B")
|
startNode("Bank A"),
|
||||||
startNode("Bank C")
|
startNode("Bank B"),
|
||||||
|
startNode("Bank C")
|
||||||
|
).getOrThrow()
|
||||||
|
|
||||||
|
startWebserver(nodeA)
|
||||||
|
startWebserver(nodeB)
|
||||||
|
startWebserver(nodeC)
|
||||||
|
|
||||||
waitForAllNodesToFinish()
|
waitForAllNodesToFinish()
|
||||||
}, isDebug = true)
|
}, isDebug = true)
|
||||||
}
|
}
|
||||||
|
@ -155,6 +155,7 @@ open class MockTransactionStorage : TransactionStorage {
|
|||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
class MockStorageService(override val attachments: AttachmentStorage = MockAttachmentStorage(),
|
class MockStorageService(override val attachments: AttachmentStorage = MockAttachmentStorage(),
|
||||||
override val validatedTransactions: TransactionStorage = MockTransactionStorage(),
|
override val validatedTransactions: TransactionStorage = MockTransactionStorage(),
|
||||||
|
override val uploaders: List<FileUploader> = listOf<FileUploader>(),
|
||||||
override val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage())
|
override val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage())
|
||||||
: SingletonSerializeAsToken(), TxWritableStorageService
|
: SingletonSerializeAsToken(), TxWritableStorageService
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user