Cleaned up the trader, irs and simm demos so that things like the driver are used in test so that the node module isn't a compile dependency.

This has resulted in some classes moving into core, such as ClockUtils and FiberBox.
This commit is contained in:
Shams Asari 2017-06-14 15:59:17 +01:00
parent 4195adfb7b
commit 155bb029da
29 changed files with 159 additions and 159 deletions

View File

@ -409,10 +409,10 @@ interface KeyManagementService {
fun sign(bytes: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey fun sign(bytes: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey
} }
// TODO: Move to a more appropriate location
/** /**
* An interface that denotes a service that can accept file uploads. * An interface that denotes a service that can accept file uploads.
*/ */
// TODO This is no longer used and can be removed
interface FileUploader { interface FileUploader {
/** /**
* Accepts the data in the given input stream, and returns some sort of useful return message that will be sent * Accepts the data in the given input stream, and returns some sort of useful return message that will be sent

View File

@ -4,9 +4,8 @@ import net.corda.core.node.services.FileUploader
/** /**
* A service that implements AcceptsFileUpload can have new binary data provided to it via an HTTP upload. * A service that implements AcceptsFileUpload can have new binary data provided to it via an HTTP upload.
*
* TODO: In future, also accept uploads over the MQ interface too.
*/ */
// TODO This is no longer used and can be removed
interface AcceptsFileUpload : FileUploader { 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

View File

@ -33,8 +33,8 @@ import kotlin.concurrent.withLock
* to be temporary. In addition, it's enitrely possible to envisage a time when we want public [net.corda.core.flows.FlowLogic] * to be temporary. In addition, it's enitrely possible to envisage a time when we want public [net.corda.core.flows.FlowLogic]
* implementations to be able to wait for some condition to become true outside of message send/receive. At that point * implementations to be able to wait for some condition to become true outside of message send/receive. At that point
* we may revisit this implementation and indeed the whole model for this, when we understand that requirement more fully. * we may revisit this implementation and indeed the whole model for this, when we understand that requirement more fully.
*
*/ */
// TODO This is no longer used and can be removed
class FiberBox<out T>(private val content: T, private val lock: Lock = ReentrantLock()) { class FiberBox<out T>(private val content: T, private val lock: Lock = ReentrantLock()) {
private var mutated: SettableFuture<Boolean>? = null private var mutated: SettableFuture<Boolean>? = null

View File

@ -26,8 +26,6 @@ configurations {
dependencies { dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:${assertj_version}"
// Corda integration dependencies // Corda integration dependencies
compile project(path: ":node:capsule", configuration: 'runtimeArtifacts') compile project(path: ":node:capsule", configuration: 'runtimeArtifacts')
@ -35,7 +33,6 @@ dependencies {
compile project(':core') compile project(':core')
compile project(':finance') compile project(':finance')
compile project(':webserver') compile project(':webserver')
compile project(':test-utils')
// Javax is required for webapis // Javax is required for webapis
compile "org.glassfish.jersey.core:jersey-server:${jersey_version}" compile "org.glassfish.jersey.core:jersey-server:${jersey_version}"
@ -43,6 +40,10 @@ dependencies {
// Cordapp dependencies // Cordapp dependencies
// Specify your cordapp's dependencies below, including dependent cordapps // Specify your cordapp's dependencies below, including dependent cordapps
compile "com.squareup.okhttp3:okhttp:$okhttp_version" compile "com.squareup.okhttp3:okhttp:$okhttp_version"
testCompile project(':test-utils')
testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:${assertj_version}"
} }
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {

View File

@ -4,7 +4,6 @@ import com.google.common.net.HostAndPort
import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.Futures
import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCClient
import net.corda.core.getOrThrow import net.corda.core.getOrThrow
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.toFuture import net.corda.core.toFuture
import net.corda.core.utilities.DUMMY_BANK_A import net.corda.core.utilities.DUMMY_BANK_A
@ -12,20 +11,17 @@ import net.corda.core.utilities.DUMMY_BANK_B
import net.corda.core.utilities.DUMMY_NOTARY import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.irs.api.NodeInterestRates import net.corda.irs.api.NodeInterestRates
import net.corda.irs.contract.InterestRateSwap import net.corda.irs.contract.InterestRateSwap
import net.corda.irs.utilities.postJson
import net.corda.irs.utilities.putJson
import net.corda.irs.utilities.uploadFile import net.corda.irs.utilities.uploadFile
import net.corda.testing.driver.driver
import net.corda.node.services.config.FullNodeConfiguration import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.services.transactions.SimpleNotaryService import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.nodeapi.User import net.corda.nodeapi.User
import net.corda.testing.IntegrationTestCategory import net.corda.testing.IntegrationTestCategory
import net.corda.testing.driver.driver
import net.corda.testing.http.HttpApi import net.corda.testing.http.HttpApi
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
import rx.Observable import rx.Observable
import rx.observables.BlockingObservable
import java.net.URL import java.net.URL
import java.time.Duration import java.time.Duration
import java.time.LocalDate import java.time.LocalDate
@ -56,7 +52,7 @@ class IRSDemoTest : IntegrationTestCategory {
println("All webservers started") println("All webservers started")
val (controllerApi, nodeAApi, nodeBApi) = listOf(controller, nodeA, nodeB).zip(listOf(controllerAddr, nodeAAddr, nodeBAddr)).map { val (_, nodeAApi, nodeBApi) = listOf(controller, nodeA, nodeB).zip(listOf(controllerAddr, nodeAAddr, nodeBAddr)).map {
val mapper = net.corda.jackson.JacksonSupport.createDefaultMapper(it.first.rpc) val mapper = net.corda.jackson.JacksonSupport.createDefaultMapper(it.first.rpc)
HttpApi.fromHostAndPort(it.second, "api/irs", mapper = mapper) HttpApi.fromHostAndPort(it.second, "api/irs", mapper = mapper)
} }
@ -108,7 +104,7 @@ class IRSDemoTest : IntegrationTestCategory {
private fun runUploadRates(host: HostAndPort) { private fun runUploadRates(host: HostAndPort) {
println("Running upload rates against $host") println("Running upload rates against $host")
val fileContents = loadResourceFile("net/corda/irs/simulation/example.rates.txt") val fileContents = loadResourceFile("net/corda/irs/simulation/example.rates.txt")
val url = URL("http://$host/upload/interest-rates") val url = URL("http://$host/api/irs/fixes")
assertThat(uploadFile(url, fileContents)).isTrue() assertThat(uploadFile(url, fileContents)).isTrue()
} }

View File

@ -8,11 +8,7 @@ import net.corda.core.messaging.startFlow
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.irs.contract.InterestRateSwap import net.corda.irs.contract.InterestRateSwap
import net.corda.irs.flows.AutoOfferFlow import net.corda.irs.flows.AutoOfferFlow
import net.corda.irs.flows.UpdateBusinessDayFlow
import java.net.URI import java.net.URI
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZoneId
import javax.ws.rs.* import javax.ws.rs.*
import javax.ws.rs.core.MediaType import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response import javax.ws.rs.core.Response
@ -28,8 +24,6 @@ import javax.ws.rs.core.Response
* *
* TODO: where we currently refer to singular external deal reference, of course this could easily be multiple identifiers e.g. CUSIP, ISIN. * TODO: where we currently refer to singular external deal reference, of course this could easily be multiple identifiers e.g. CUSIP, ISIN.
* *
* GET /api/irs/demodate - return the current date as viewed by the system in YYYY-MM-DD format.
* PUT /api/irs/demodate - put date in format YYYY-MM-DD to advance the current date as viewed by the system and
* simulate any associated business processing (currently fixing). * simulate any associated business processing (currently fixing).
* *
* TODO: replace simulated date advancement with business event based implementation * TODO: replace simulated date advancement with business event based implementation
@ -88,28 +82,4 @@ class InterestRateSwapAPI(val rpc: CordaRPCOps) {
return Response.ok().entity(deal).build() return Response.ok().entity(deal).build()
} }
} }
@PUT
@Path("demodate")
@Consumes(MediaType.APPLICATION_JSON)
fun storeDemoDate(newDemoDate: LocalDate): Response {
val priorDemoDate = fetchDemoDate()
// Can only move date forwards
if (newDemoDate.isAfter(priorDemoDate)) {
// TODO: Remove this suppress when we upgrade to kotlin 1.1 or when JetBrain fixes the bug.
@Suppress("UNSUPPORTED_FEATURE")
rpc.startFlow(UpdateBusinessDayFlow::Broadcast, newDemoDate).returnValue.getOrThrow()
return Response.ok().build()
}
val msg = "demodate is already $priorDemoDate and can only be updated with a later date"
logger.error("Attempt to set demodate to $newDemoDate but $msg")
return Response.status(Response.Status.CONFLICT).entity(msg).build()
}
@GET
@Path("demodate")
@Produces(MediaType.APPLICATION_JSON)
fun fetchDemoDate(): LocalDate {
return LocalDateTime.ofInstant(rpc.currentNodeTime(), ZoneId.systemDefault()).toLocalDate()
}
} }

View File

@ -5,17 +5,19 @@ import net.corda.contracts.BusinessCalendar
import net.corda.contracts.Fix import net.corda.contracts.Fix
import net.corda.contracts.FixOf import net.corda.contracts.FixOf
import net.corda.contracts.Tenor import net.corda.contracts.Tenor
import net.corda.contracts.math.CubicSplineInterpolator
import net.corda.contracts.math.Interpolator
import net.corda.contracts.math.InterpolatorFactory
import net.corda.core.RetryableException import net.corda.core.RetryableException
import net.corda.core.ThreadBox
import net.corda.core.contracts.Command import net.corda.core.contracts.Command
import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.MerkleTreeException import net.corda.core.crypto.MerkleTreeException
import net.corda.core.crypto.keys import net.corda.core.crypto.keys
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatedBy import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.contracts.math.CubicSplineInterpolator
import net.corda.contracts.math.Interpolator
import net.corda.contracts.math.InterpolatorFactory
import net.corda.core.node.PluginServiceHub import net.corda.core.node.PluginServiceHub
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.services.CordaService import net.corda.core.node.services.CordaService
@ -25,20 +27,12 @@ import net.corda.core.transactions.FilteredTransaction
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap import net.corda.core.utilities.unwrap
import net.corda.irs.flows.RatesFixFlow import net.corda.irs.flows.RatesFixFlow
import net.corda.node.services.api.AcceptsFileUpload
import net.corda.node.utilities.AbstractJDBCHashSet
import net.corda.node.utilities.FiberBox
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 java.io.InputStream
import java.math.BigDecimal import java.math.BigDecimal
import java.security.PublicKey import java.security.PublicKey
import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.util.* import java.util.*
import javax.annotation.concurrent.ThreadSafe import javax.annotation.concurrent.ThreadSafe
import kotlin.collections.HashSet
import kotlin.collections.component1 import kotlin.collections.component1
import kotlin.collections.component2 import kotlin.collections.component2
import kotlin.collections.set import kotlin.collections.set
@ -76,7 +70,7 @@ object NodeInterestRates {
val request = receive<RatesFixFlow.QueryRequest>(otherParty).unwrap { it } val request = receive<RatesFixFlow.QueryRequest>(otherParty).unwrap { it }
progressTracker.currentStep = RECEIVED progressTracker.currentStep = RECEIVED
val oracle = serviceHub.cordaService(Oracle::class.java) val oracle = serviceHub.cordaService(Oracle::class.java)
val answers = oracle.query(request.queries, request.deadline) val answers = oracle.query(request.queries)
progressTracker.currentStep = SENDING progressTracker.currentStep = SENDING
send(otherParty, answers) send(otherParty, answers)
} }
@ -91,7 +85,7 @@ object NodeInterestRates {
@ThreadSafe @ThreadSafe
// DOCSTART 3 // DOCSTART 3
@CordaService @CordaService
class Oracle(val identity: Party, private val signingKey: PublicKey, val services: ServiceHub) : AcceptsFileUpload, SingletonSerializeAsToken() { class Oracle(val identity: Party, private val signingKey: PublicKey, val services: ServiceHub) : SingletonSerializeAsToken() {
constructor(services: PluginServiceHub) : this( constructor(services: PluginServiceHub) : this(
services.myInfo.serviceIdentities(type).first(), services.myInfo.serviceIdentities(type).first(),
services.myInfo.serviceIdentities(type).first().owningKey.keys.first { services.keyManagementService.keys.contains(it) }, services.myInfo.serviceIdentities(type).first().owningKey.keys.first { services.keyManagementService.keys.contains(it) },
@ -104,56 +98,34 @@ object NodeInterestRates {
val type = ServiceType.corda.getSubType("interest_rates") val type = ServiceType.corda.getSubType("interest_rates")
} }
private object Table : JDBCHashedTable("demo_interest_rate_fixes") {
val name = varchar("index_name", length = 255)
val forDay = localDate("for_day")
val ofTenor = varchar("of_tenor", length = 16)
val value = decimal("value", scale = 20, precision = 16)
}
private class InnerState { private class InnerState {
val fixes = object : AbstractJDBCHashSet<Fix, Table>(Table) { // TODO Update this to use a database once we have an database API
override fun elementFromRow(row: ResultRow): Fix { val fixes = HashSet<Fix>()
return Fix(FixOf(row[table.name], row[table.forDay], Tenor(row[table.ofTenor])), row[table.value])
}
override fun addElementToInsert(insert: InsertStatement, entry: Fix, finalizables: MutableList<() -> Unit>) {
insert[table.name] = entry.of.name
insert[table.forDay] = entry.of.forDay
insert[table.ofTenor] = entry.of.ofTenor.name
insert[table.value] = entry.value
}
}
var container: FixContainer = FixContainer(fixes) var container: FixContainer = FixContainer(fixes)
} }
private val mutex = FiberBox(InnerState()) private val mutex = ThreadBox(InnerState())
var knownFixes: FixContainer var knownFixes: FixContainer
set(value) { set(value) {
require(value.size > 0) require(value.size > 0)
mutex.write { mutex.locked {
fixes.clear() fixes.clear()
fixes.addAll(value.fixes) fixes.addAll(value.fixes)
container = value container = value
} }
} }
get() = mutex.read { container } get() = mutex.locked { container }
// Make this the last bit of initialisation logic so fully constructed when entered into instances map // Make this the last bit of initialisation logic so fully constructed when entered into instances map
init { init {
require(signingKey in identity.owningKey.keys) require(signingKey in identity.owningKey.keys)
} }
/**
* This method will now wait until the given deadline if the fix for the given [FixOf] is not immediately
* available. To implement this, [FiberBox.readWithDeadline] will loop if the deadline is not reached and we throw
* [UnknownFix] as it implements [RetryableException] which has special meaning to this function.
*/
@Suspendable @Suspendable
fun query(queries: List<FixOf>, deadline: Instant): List<Fix> { fun query(queries: List<FixOf>): List<Fix> {
require(queries.isNotEmpty()) require(queries.isNotEmpty())
return mutex.readWithDeadline(services.clock, deadline) { return mutex.locked {
val answers: List<Fix?> = queries.map { container[it] } val answers: List<Fix?> = queries.map { container[it] }
val firstNull = answers.indexOf(null) val firstNull = answers.indexOf(null)
if (firstNull != -1) { if (firstNull != -1) {
@ -204,20 +176,21 @@ object NodeInterestRates {
} }
// DOCEND 1 // DOCEND 1
// File upload support fun uploadFixes(s: String) {
override val dataTypePrefix = "interest-rates" knownFixes = parseFile(s)
override val acceptableFileExtensions = listOf(".rates", ".txt")
override fun upload(file: InputStream): String {
val fixes = parseFile(file.bufferedReader().readText())
knownFixes = fixes
return "Interest rates oracle accepted ${fixes.size} new interest rate fixes"
} }
} }
// TODO: can we split into two? Fix not available (retryable/transient) and unknown (permanent) // TODO: can we split into two? Fix not available (retryable/transient) and unknown (permanent)
class UnknownFix(val fix: FixOf) : RetryableException("Unknown fix: $fix") class UnknownFix(val fix: FixOf) : RetryableException("Unknown fix: $fix")
// Upload the raw fix data via RPC. In a real system the oracle data would be taken from a database.
@StartableByRPC
class UploadFixesFlow(val s: String) : FlowLogic<Unit>() {
@Suspendable
override fun call() = serviceHub.cordaService(Oracle::class.java).uploadFixes(s)
}
/** Fix container, for every fix name & date pair stores a tenor to interest rate map - [InterpolatingRateMap] */ /** Fix container, for every fix name & date pair stores a tenor to interest rate map - [InterpolatingRateMap] */
class FixContainer(val fixes: Set<Fix>, val factory: InterpolatorFactory = CubicSplineInterpolator) { class FixContainer(val fixes: Set<Fix>, val factory: InterpolatorFactory = CubicSplineInterpolator) {
private val container = buildContainer(fixes) private val container = buildContainer(fixes)
@ -298,17 +271,21 @@ object NodeInterestRates {
map(String::trim). map(String::trim).
// Filter out comment and empty lines. // Filter out comment and empty lines.
filterNot { it.startsWith("#") || it.isBlank() }. filterNot { it.startsWith("#") || it.isBlank() }.
map { parseFix(it) }. map(this::parseFix).
toSet() toSet()
return FixContainer(fixes) return FixContainer(fixes)
} }
/** Parses a string of the form "LIBOR 16-March-2016 1M = 0.678" into a [Fix] */ /** Parses a string of the form "LIBOR 16-March-2016 1M = 0.678" into a [Fix] */
fun parseFix(s: String): Fix { private fun parseFix(s: String): Fix {
val (key, value) = s.split('=').map(String::trim) try {
val of = parseFixOf(key) val (key, value) = s.split('=').map(String::trim)
val rate = BigDecimal(value) val of = parseFixOf(key)
return Fix(of, rate) val rate = BigDecimal(value)
return Fix(of, rate)
} catch (e: Exception) {
throw IllegalArgumentException("Unable to parse fix $s: ${e.message}", e)
}
} }
/** Parses a string of the form "LIBOR 16-March-2016 1M" into a [FixOf] */ /** Parses a string of the form "LIBOR 16-March-2016 1M" into a [FixOf] */

View File

@ -13,9 +13,7 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap import net.corda.core.utilities.unwrap
import net.corda.irs.flows.RatesFixFlow.FixOutOfRange import net.corda.irs.flows.RatesFixFlow.FixOutOfRange
import net.corda.irs.utilities.suggestInterestRateAnnouncementTimeWindow
import java.math.BigDecimal import java.math.BigDecimal
import java.time.Instant
import java.util.* import java.util.*
import java.util.function.Predicate import java.util.function.Predicate
@ -48,7 +46,7 @@ open class RatesFixFlow(protected val tx: TransactionBuilder,
class FixOutOfRange(@Suppress("unused") val byAmount: BigDecimal) : Exception("Fix out of range by $byAmount") class FixOutOfRange(@Suppress("unused") val byAmount: BigDecimal) : Exception("Fix out of range by $byAmount")
@CordaSerializable @CordaSerializable
data class QueryRequest(val queries: List<FixOf>, val deadline: Instant) data class QueryRequest(val queries: List<FixOf>)
@CordaSerializable @CordaSerializable
data class SignRequest(val ftx: FilteredTransaction) data class SignRequest(val ftx: FilteredTransaction)
@ -99,9 +97,8 @@ open class RatesFixFlow(protected val tx: TransactionBuilder,
class FixQueryFlow(val fixOf: FixOf, val oracle: Party) : FlowLogic<Fix>() { class FixQueryFlow(val fixOf: FixOf, val oracle: Party) : FlowLogic<Fix>() {
@Suspendable @Suspendable
override fun call(): Fix { override fun call(): Fix {
val deadline = suggestInterestRateAnnouncementTimeWindow(fixOf.name, oracle.name.toString(), fixOf.forDay).untilTime!!
// TODO: add deadline to receive // TODO: add deadline to receive
val resp = sendAndReceive<ArrayList<Fix>>(oracle, QueryRequest(listOf(fixOf), deadline)) val resp = sendAndReceive<ArrayList<Fix>>(oracle, QueryRequest(listOf(fixOf)))
return resp.unwrap { return resp.unwrap {
val fix = it.first() val fix = it.first()

View File

@ -1,6 +1,9 @@
package net.corda.irs.utilities package net.corda.irs.utilities
import okhttp3.* import okhttp3.MediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import java.net.URL import java.net.URL
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -24,10 +27,7 @@ fun postJson(url: URL, data: String): Boolean {
} }
fun uploadFile(url: URL, file: String): Boolean { fun uploadFile(url: URL, file: String): Boolean {
val body = MultipartBody.Builder() val body = RequestBody.create(MediaType.parse("text/plain; charset=utf-8"), file)
.setType(MultipartBody.FORM)
.addFormDataPart("rates", "net/corda/irs/simulation/example.rates.txt", RequestBody.create(MediaType.parse("text/plain"), file))
.build()
return makeRequest(Request.Builder().url(url).post(body).build()) return makeRequest(Request.Builder().url(url).post(body).build())
} }

View File

@ -4,7 +4,6 @@ package net.corda.irs
import com.google.common.net.HostAndPort import com.google.common.net.HostAndPort
import joptsimple.OptionParser import joptsimple.OptionParser
import net.corda.irs.api.IRSDemoClientApi
import kotlin.system.exitProcess import kotlin.system.exitProcess
enum class Role { enum class Role {

View File

@ -1,4 +1,4 @@
package net.corda.irs.api package net.corda.irs
import com.google.common.net.HostAndPort import com.google.common.net.HostAndPort
import net.corda.irs.utilities.uploadFile import net.corda.irs.utilities.uploadFile
@ -25,7 +25,7 @@ class IRSDemoClientApi(private val hostAndPort: HostAndPort) {
// TODO: Add uploading of files to the HTTP API // TODO: Add uploading of files to the HTTP API
fun runUploadRates() { fun runUploadRates() {
val fileContents = IOUtils.toString(Thread.currentThread().contextClassLoader.getResourceAsStream("net/corda/irs/simulation/example.rates.txt"), Charsets.UTF_8.name()) val fileContents = IOUtils.toString(Thread.currentThread().contextClassLoader.getResourceAsStream("net/corda/irs/simulation/example.rates.txt"), Charsets.UTF_8.name())
val url = URL("http://$hostAndPort/upload/interest-rates") val url = URL("http://$hostAndPort/api/irs/fixes")
check(uploadFile(url, fileContents)) check(uploadFile(url, fileContents))
println("Rates successfully uploaded!") println("Rates successfully uploaded!")
} }

View File

@ -0,0 +1,55 @@
package net.corda.irs.api
import net.corda.core.getOrThrow
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.loggerFor
import net.corda.irs.flows.UpdateBusinessDayFlow
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZoneId
import javax.ws.rs.*
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
/**
* GET /api/irs/demodate - return the current date as viewed by the system in YYYY-MM-DD format.
* PUT /api/irs/demodate - put date in format YYYY-MM-DD to advance the current date as viewed by the system and
* POST /api/irs/fixes - store the fixing data as a text file
*/
@Path("irs")
class InterestRatesSwapDemoAPI(val rpc: CordaRPCOps) {
companion object {
private val logger = loggerFor<InterestRatesSwapDemoAPI>()
}
@PUT
@Path("demodate")
@Consumes(MediaType.APPLICATION_JSON)
fun storeDemoDate(newDemoDate: LocalDate): Response {
val priorDemoDate = fetchDemoDate()
// Can only move date forwards
if (newDemoDate.isAfter(priorDemoDate)) {
rpc.startFlow(UpdateBusinessDayFlow::Broadcast, newDemoDate).returnValue.getOrThrow()
return Response.ok().build()
}
val msg = "demodate is already $priorDemoDate and can only be updated with a later date"
logger.error("Attempt to set demodate to $newDemoDate but $msg")
return Response.status(Response.Status.CONFLICT).entity(msg).build()
}
@GET
@Path("demodate")
@Produces(MediaType.APPLICATION_JSON)
fun fetchDemoDate(): LocalDate {
return LocalDateTime.ofInstant(rpc.currentNodeTime(), ZoneId.systemDefault()).toLocalDate()
}
@POST
@Path("fixes")
@Consumes(MediaType.TEXT_PLAIN)
fun storeFixes(file: String): Response {
rpc.startFlow(NodeInterestRates::UploadFixesFlow, file).returnValue.getOrThrow()
return Response.ok().build()
}
}

View File

@ -1,4 +1,4 @@
package net.corda.irs.testing package net.corda.irs.api
import net.corda.contracts.Fix import net.corda.contracts.Fix
import net.corda.contracts.FixOf import net.corda.contracts.FixOf
@ -18,7 +18,6 @@ import net.corda.core.utilities.ALICE
import net.corda.core.utilities.DUMMY_NOTARY import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.core.utilities.LogHelper import net.corda.core.utilities.LogHelper
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.irs.api.NodeInterestRates
import net.corda.irs.flows.RatesFixFlow import net.corda.irs.flows.RatesFixFlow
import net.corda.node.utilities.configureDatabase import net.corda.node.utilities.configureDatabase
import net.corda.node.utilities.transaction import net.corda.node.utilities.transaction
@ -54,8 +53,6 @@ class NodeInterestRatesTest {
val DUMMY_CASH_ISSUER_KEY = generateKeyPair() val DUMMY_CASH_ISSUER_KEY = generateKeyPair()
val DUMMY_CASH_ISSUER = Party(X500Name("CN=Cash issuer,O=R3,OU=corda,L=London,C=UK"), DUMMY_CASH_ISSUER_KEY.public) val DUMMY_CASH_ISSUER = Party(X500Name("CN=Cash issuer,O=R3,OU=corda,L=London,C=UK"), DUMMY_CASH_ISSUER_KEY.public)
val dummyServices = MockServices(DUMMY_CASH_ISSUER_KEY, MEGA_CORP_KEY)
val clock get() = dummyServices.clock
lateinit var oracle: NodeInterestRates.Oracle lateinit var oracle: NodeInterestRates.Oracle
lateinit var dataSource: Closeable lateinit var dataSource: Closeable
lateinit var database: Database lateinit var database: Database
@ -75,7 +72,11 @@ class NodeInterestRatesTest {
dataSource = dataSourceAndDatabase.first dataSource = dataSourceAndDatabase.first
database = dataSourceAndDatabase.second database = dataSourceAndDatabase.second
database.transaction { database.transaction {
oracle = NodeInterestRates.Oracle(MEGA_CORP, MEGA_CORP_KEY.public, dummyServices).apply { knownFixes = TEST_DATA } oracle = NodeInterestRates.Oracle(
MEGA_CORP,
MEGA_CORP_KEY.public,
MockServices(DUMMY_CASH_ISSUER_KEY, MEGA_CORP_KEY)
).apply { knownFixes = TEST_DATA }
} }
} }
@ -88,7 +89,7 @@ class NodeInterestRatesTest {
fun `query successfully`() { fun `query successfully`() {
database.transaction { database.transaction {
val q = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M") val q = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val res = oracle.query(listOf(q), clock.instant()) val res = oracle.query(listOf(q))
assertEquals(1, res.size) assertEquals(1, res.size)
assertEquals("0.678".bd, res[0].value) assertEquals("0.678".bd, res[0].value)
assertEquals(q, res[0].of) assertEquals(q, res[0].of)
@ -100,7 +101,7 @@ class NodeInterestRatesTest {
database.transaction { database.transaction {
val q1 = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M") val q1 = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val q2 = NodeInterestRates.parseFixOf("LIBOR 2016-03-15 1M") val q2 = NodeInterestRates.parseFixOf("LIBOR 2016-03-15 1M")
val e = assertFailsWith<NodeInterestRates.UnknownFix> { oracle.query(listOf(q1, q2), clock.instant()) } val e = assertFailsWith<NodeInterestRates.UnknownFix> { oracle.query(listOf(q1, q2)) }
assertEquals(e.fix, q2) assertEquals(e.fix, q2)
} }
} }
@ -109,7 +110,7 @@ class NodeInterestRatesTest {
fun `query successfully with interpolated rate`() { fun `query successfully with interpolated rate`() {
database.transaction { database.transaction {
val q = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 5M") val q = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 5M")
val res = oracle.query(listOf(q), clock.instant()) val res = oracle.query(listOf(q))
assertEquals(1, res.size) assertEquals(1, res.size)
Assert.assertEquals(0.7316228, res[0].value.toDouble(), 0.0000001) Assert.assertEquals(0.7316228, res[0].value.toDouble(), 0.0000001)
assertEquals(q, res[0].of) assertEquals(q, res[0].of)
@ -120,14 +121,14 @@ class NodeInterestRatesTest {
fun `rate missing and unable to interpolate`() { fun `rate missing and unable to interpolate`() {
database.transaction { database.transaction {
val q = NodeInterestRates.parseFixOf("EURIBOR 2016-03-15 3M") val q = NodeInterestRates.parseFixOf("EURIBOR 2016-03-15 3M")
assertFailsWith<NodeInterestRates.UnknownFix> { oracle.query(listOf(q), clock.instant()) } assertFailsWith<NodeInterestRates.UnknownFix> { oracle.query(listOf(q)) }
} }
} }
@Test @Test
fun `empty query`() { fun `empty query`() {
database.transaction { database.transaction {
assertFailsWith<IllegalArgumentException> { oracle.query(emptyList(), clock.instant()) } assertFailsWith<IllegalArgumentException> { oracle.query(emptyList()) }
} }
} }
@ -157,7 +158,7 @@ class NodeInterestRatesTest {
fun `sign successfully`() { fun `sign successfully`() {
database.transaction { database.transaction {
val tx = makeTX() val tx = makeTX()
val fix = oracle.query(listOf(NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")), clock.instant()).first() val fix = oracle.query(listOf(NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M"))).first()
tx.addCommand(fix, oracle.identity.owningKey) tx.addCommand(fix, oracle.identity.owningKey)
// Sign successfully. // Sign successfully.
val wtx = tx.toWireTransaction() val wtx = tx.toWireTransaction()
@ -185,7 +186,7 @@ class NodeInterestRatesTest {
fun `do not sign too many leaves`() { fun `do not sign too many leaves`() {
database.transaction { database.transaction {
val tx = makeTX() val tx = makeTX()
val fix = oracle.query(listOf(NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")), clock.instant()).first() val fix = oracle.query(listOf(NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M"))).first()
fun filtering(elem: Any): Boolean { fun filtering(elem: Any): Boolean {
return when (elem) { return when (elem) {
is Command -> oracle.identity.owningKey in elem.signers && elem.value is Fix is Command -> oracle.identity.owningKey in elem.signers && elem.value is Fix

View File

@ -1,4 +1,4 @@
package net.corda.irs.testing package net.corda.irs.contract
import net.corda.contracts.* import net.corda.contracts.*
import net.corda.core.contracts.* import net.corda.core.contracts.*
@ -7,7 +7,6 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.DUMMY_NOTARY import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.core.utilities.DUMMY_NOTARY_KEY import net.corda.core.utilities.DUMMY_NOTARY_KEY
import net.corda.core.utilities.TEST_TX_TIME import net.corda.core.utilities.TEST_TX_TIME
import net.corda.irs.contract.*
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import org.junit.Test import org.junit.Test

View File

@ -11,7 +11,6 @@ import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap import net.corda.core.utilities.unwrap
import net.corda.node.utilities.TestClock import net.corda.node.utilities.TestClock
import net.corda.testing.node.MockNetworkMapCache
import java.time.LocalDate import java.time.LocalDate
/** /**
@ -19,8 +18,6 @@ import java.time.LocalDate
*/ */
object UpdateBusinessDayFlow { object UpdateBusinessDayFlow {
// This is not really a HandshakeMessage but needs to be so that the send uses the default session ID. This will
// resolve itself when the flow session stuff is done.
@CordaSerializable @CordaSerializable
data class UpdateBusinessDayMessage(val date: LocalDate) data class UpdateBusinessDayMessage(val date: LocalDate)
@ -32,7 +29,6 @@ object UpdateBusinessDayFlow {
} }
} }
@InitiatingFlow @InitiatingFlow
@StartableByRPC @StartableByRPC
class Broadcast(val date: LocalDate, override val progressTracker: ProgressTracker) : FlowLogic<Unit>() { class Broadcast(val date: LocalDate, override val progressTracker: ProgressTracker) : FlowLogic<Unit>() {
@ -65,12 +61,7 @@ object UpdateBusinessDayFlow {
@Suspendable @Suspendable
private fun doNextRecipient(recipient: NodeInfo) { private fun doNextRecipient(recipient: NodeInfo) {
if (recipient.address is MockNetworkMapCache.MockAddress) { send(recipient.legalIdentity, UpdateBusinessDayMessage(date))
// Ignore
} else {
send(recipient.legalIdentity, UpdateBusinessDayMessage(date))
}
} }
} }
} }

View File

@ -0,0 +1,9 @@
package net.corda.irs.plugin
import net.corda.irs.api.InterestRatesSwapDemoAPI
import net.corda.webserver.services.WebServerPluginRegistry
import java.util.function.Function
class IRSDemoPlugin : WebServerPluginRegistry {
override val webApis = listOf(Function(::InterestRatesSwapDemoAPI))
}

View File

@ -0,0 +1,3 @@
# Register a ServiceLoader service extending from net.corda.core.node.CordaPluginRegistry
net.corda.irs.plugin.IRSPlugin
net.corda.irs.plugin.IRSDemoPlugin

View File

@ -7,6 +7,7 @@ apply plugin: 'us.kirchmeier.capsule'
dependencies { dependencies {
compile project(':samples:irs-demo') compile project(':samples:irs-demo')
compile project(':test-utils')
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
testCompile "junit:junit:$junit_version" testCompile "junit:junit:$junit_version"
@ -16,7 +17,6 @@ dependencies {
compile project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts') compile project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts')
compile project(':core') compile project(':core')
compile project(':finance') compile project(':finance')
testCompile project(':test-utils')
// Javax is required for webapis // Javax is required for webapis
compile "org.glassfish.jersey.core:jersey-server:${jersey_version}" compile "org.glassfish.jersey.core:jersey-server:${jersey_version}"

View File

@ -15,9 +15,9 @@ import net.corda.core.crypto.commonName
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.then import net.corda.core.then
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.irs.simulation.IRSSimulation
import net.corda.irs.simulation.Simulation
import net.corda.netmap.VisualiserViewModel.Style import net.corda.netmap.VisualiserViewModel.Style
import net.corda.netmap.simulation.IRSSimulation
import net.corda.netmap.simulation.Simulation
import net.corda.node.services.network.NetworkMapService import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.statemachine.SessionConfirm import net.corda.node.services.statemachine.SessionConfirm
import net.corda.node.services.statemachine.SessionEnd import net.corda.node.services.statemachine.SessionEnd

View File

@ -9,7 +9,7 @@ import javafx.scene.shape.Line
import javafx.util.Duration import javafx.util.Duration
import net.corda.core.crypto.commonName import net.corda.core.crypto.commonName
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.irs.simulation.IRSSimulation import net.corda.netmap.simulation.IRSSimulation
import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import java.util.* import java.util.*
@ -50,7 +50,7 @@ class VisualiserViewModel {
var bankCount: Int = 0 var bankCount: Int = 0
var serviceCount: Int = 0 var serviceCount: Int = 0
var stepDuration = Duration.millis(500.0) var stepDuration: Duration = Duration.millis(500.0)
var runningPausedState: NetworkMapVisualiser.RunningPausedState = NetworkMapVisualiser.RunningPausedState.Paused() var runningPausedState: NetworkMapVisualiser.RunningPausedState = NetworkMapVisualiser.RunningPausedState.Paused()
var displayStyle: Style = Style.MAP var displayStyle: Style = Style.MAP

View File

@ -1,4 +1,4 @@
package net.corda.irs.simulation package net.corda.netmap.simulation
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper

View File

@ -1,4 +1,4 @@
package net.corda.irs.simulation package net.corda.netmap.simulation
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
@ -133,7 +133,7 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java) registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java)
javaClass.classLoader.getResourceAsStream("net/corda/irs/simulation/example.rates.txt").use { javaClass.classLoader.getResourceAsStream("net/corda/irs/simulation/example.rates.txt").use {
database.transaction { database.transaction {
installCordaService(NodeInterestRates.Oracle::class.java).upload(it) installCordaService(NodeInterestRates.Oracle::class.java).uploadFixes(it.reader().readText())
} }
} }
return this return this

View File

@ -1,8 +1,7 @@
package net.corda.irs.testing package net.corda.netmap.simulation
import net.corda.core.getOrThrow import net.corda.core.getOrThrow
import net.corda.core.utilities.LogHelper import net.corda.core.utilities.LogHelper
import net.corda.irs.simulation.IRSSimulation
import org.junit.Test import org.junit.Test
class IRSSimulationTest { class IRSSimulationTest {

View File

@ -27,8 +27,6 @@ configurations {
dependencies { dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:${assertj_version}"
// Corda integration dependencies // Corda integration dependencies
compile project(path: ":node:capsule", configuration: 'runtimeArtifacts') compile project(path: ":node:capsule", configuration: 'runtimeArtifacts')
@ -36,7 +34,6 @@ dependencies {
compile project(':core') compile project(':core')
compile project(':webserver') compile project(':webserver')
compile project(':finance') compile project(':finance')
compile project(':test-utils')
// Javax is required for webapis // Javax is required for webapis
compile "org.glassfish.jersey.core:jersey-server:${jersey_version}" compile "org.glassfish.jersey.core:jersey-server:${jersey_version}"
@ -53,6 +50,10 @@ dependencies {
compile "com.opengamma.strata:strata-collect:${strata_version}" compile "com.opengamma.strata:strata-collect:${strata_version}"
compile "com.opengamma.strata:strata-loader:${strata_version}" compile "com.opengamma.strata:strata-loader:${strata_version}"
compile "com.opengamma.strata:strata-math:${strata_version}" compile "com.opengamma.strata:strata-math:${strata_version}"
testCompile project(':test-utils')
testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:${assertj_version}"
} }
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {

View File

@ -22,7 +22,6 @@ import net.corda.core.utilities.unwrap
import net.corda.flows.AbstractStateReplacementFlow.Proposal import net.corda.flows.AbstractStateReplacementFlow.Proposal
import net.corda.flows.StateReplacementException import net.corda.flows.StateReplacementException
import net.corda.flows.TwoPartyDealFlow import net.corda.flows.TwoPartyDealFlow
import net.corda.node.services.messaging.Ack
import net.corda.vega.analytics.* import net.corda.vega.analytics.*
import net.corda.vega.contracts.* import net.corda.vega.contracts.*
import net.corda.vega.portfolio.Portfolio import net.corda.vega.portfolio.Portfolio
@ -320,4 +319,6 @@ object SimmFlow {
}) })
} }
} }
private object Ack
} }

View File

@ -1,14 +1,14 @@
package net.corda.vega package net.corda.vega
import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.Futures
import net.corda.core.crypto.X509Utilities
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.core.utilities.DUMMY_BANK_A import net.corda.core.utilities.DUMMY_BANK_A
import net.corda.core.utilities.DUMMY_BANK_B import net.corda.core.utilities.DUMMY_BANK_B
import net.corda.core.utilities.DUMMY_BANK_C import net.corda.core.utilities.DUMMY_BANK_C
import net.corda.testing.driver.driver import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.node.services.transactions.SimpleNotaryService import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.testing.driver.driver
/** /**
* Sample main used for running within an IDE. Starts 4 nodes (A, B, C and Notary/Controller) as an alternative to running via gradle * Sample main used for running within an IDE. Starts 4 nodes (A, B, C and Notary/Controller) as an alternative to running via gradle
@ -17,7 +17,7 @@ import net.corda.node.services.transactions.SimpleNotaryService
*/ */
fun main(args: Array<String>) { fun main(args: Array<String>) {
driver(dsl = { driver(dsl = {
startNode(X509Utilities.getDevX509Name("Controller"), setOf(ServiceInfo(SimpleNotaryService.type))) startNode(DUMMY_NOTARY.name, setOf(ServiceInfo(SimpleNotaryService.type)))
val (nodeA, nodeB, nodeC) = Futures.allAsList( val (nodeA, nodeB, nodeC) = Futures.allAsList(
startNode(DUMMY_BANK_A.name), startNode(DUMMY_BANK_A.name),
startNode(DUMMY_BANK_B.name), startNode(DUMMY_BANK_B.name),

View File

@ -23,15 +23,12 @@ configurations {
dependencies { dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:${assertj_version}"
// Corda integration dependencies // Corda integration dependencies
compile project(path: ":node:capsule", configuration: 'runtimeArtifacts') compile project(path: ":node:capsule", configuration: 'runtimeArtifacts')
compile project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts') compile project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts')
compile project(':core') compile project(':core')
compile project(':finance') compile project(':finance')
compile project(':test-utils')
// Corda Plugins: dependent flows and services // Corda Plugins: dependent flows and services
compile project(':samples:bank-of-corda-demo') compile project(':samples:bank-of-corda-demo')
@ -45,6 +42,10 @@ dependencies {
exclude group: "bouncycastle" exclude group: "bouncycastle"
} }
testCompile project(':test-utils')
testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:${assertj_version}"
// Cordapp dependencies // Cordapp dependencies
// Specify your cordapp's dependencies below, including dependent cordapps // Specify your cordapp's dependencies below, including dependent cordapps
} }

View File

@ -11,6 +11,7 @@ import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.nodeapi.User import net.corda.nodeapi.User
import net.corda.testing.BOC import net.corda.testing.BOC
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import net.corda.traderdemo.flow.SellerFlow
/** /**
* This file is exclusively for being able to run your nodes through an IDE (as opposed to running deployNodes) * This file is exclusively for being able to run your nodes through an IDE (as opposed to running deployNodes)
@ -19,7 +20,7 @@ import net.corda.testing.driver.driver
fun main(args: Array<String>) { fun main(args: Array<String>) {
val permissions = setOf( val permissions = setOf(
startFlowPermission<IssuerFlow.IssuanceRequester>(), startFlowPermission<IssuerFlow.IssuanceRequester>(),
startFlowPermission<net.corda.traderdemo.flow.SellerFlow>()) startFlowPermission<SellerFlow>())
val demoUser = listOf(User("demo", "demo", permissions)) val demoUser = listOf(User("demo", "demo", permissions))
driver(driverDirectory = "build" / "trader-demo-nodes", isDebug = true) { driver(driverDirectory = "build" / "trader-demo-nodes", isDebug = true) {
val user = User("user1", "test", permissions = setOf(startFlowPermission<IssuerFlow.IssuanceRequester>())) val user = User("user1", "test", permissions = setOf(startFlowPermission<IssuerFlow.IssuanceRequester>()))