diff --git a/core/src/main/kotlin/net/corda/core/node/services/Services.kt b/core/src/main/kotlin/net/corda/core/node/services/Services.kt index 9394a6cf81..7899a50094 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/Services.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/Services.kt @@ -409,10 +409,10 @@ interface KeyManagementService { 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. */ +// TODO This is no longer used and can be removed interface FileUploader { /** * Accepts the data in the given input stream, and returns some sort of useful return message that will be sent diff --git a/node/src/main/kotlin/net/corda/node/services/api/AcceptsFileUpload.kt b/node/src/main/kotlin/net/corda/node/services/api/AcceptsFileUpload.kt index 24d787f27c..a3967ec2ba 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/AcceptsFileUpload.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/AcceptsFileUpload.kt @@ -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. - * - * TODO: In future, also accept uploads over the MQ interface too. */ +// TODO This is no longer used and can be removed interface AcceptsFileUpload : FileUploader { /** A string that prefixes the URLs, e.g. "attachments" or "interest-rates". Should be OK for URLs. */ val dataTypePrefix: String diff --git a/node/src/main/kotlin/net/corda/node/utilities/FiberBox.kt b/node/src/main/kotlin/net/corda/node/utilities/FiberBox.kt index 97f827239a..d930fe206d 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/FiberBox.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/FiberBox.kt @@ -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] * 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. - * */ +// TODO This is no longer used and can be removed class FiberBox(private val content: T, private val lock: Lock = ReentrantLock()) { private var mutated: SettableFuture? = null diff --git a/samples/irs-demo/build.gradle b/samples/irs-demo/build.gradle index 00f36bf82f..cb749e84af 100644 --- a/samples/irs-demo/build.gradle +++ b/samples/irs-demo/build.gradle @@ -26,8 +26,6 @@ configurations { dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" - testCompile "junit:junit:$junit_version" - testCompile "org.assertj:assertj-core:${assertj_version}" // Corda integration dependencies compile project(path: ":node:capsule", configuration: 'runtimeArtifacts') @@ -35,7 +33,6 @@ dependencies { compile project(':core') compile project(':finance') compile project(':webserver') - compile project(':test-utils') // Javax is required for webapis compile "org.glassfish.jersey.core:jersey-server:${jersey_version}" @@ -43,6 +40,10 @@ dependencies { // Cordapp dependencies // Specify your cordapp's dependencies below, including dependent cordapps 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']) { diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt index e4175eaf1a..c68008daf8 100644 --- a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt @@ -4,7 +4,6 @@ import com.google.common.net.HostAndPort import com.google.common.util.concurrent.Futures import net.corda.client.rpc.CordaRPCClient import net.corda.core.getOrThrow -import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.services.ServiceInfo import net.corda.core.toFuture 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.irs.api.NodeInterestRates 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.testing.driver.driver import net.corda.node.services.config.FullNodeConfiguration import net.corda.node.services.transactions.SimpleNotaryService import net.corda.nodeapi.User import net.corda.testing.IntegrationTestCategory +import net.corda.testing.driver.driver import net.corda.testing.http.HttpApi import org.apache.commons.io.IOUtils import org.assertj.core.api.Assertions.assertThat import org.junit.Test import rx.Observable -import rx.observables.BlockingObservable import java.net.URL import java.time.Duration import java.time.LocalDate @@ -56,7 +52,7 @@ class IRSDemoTest : IntegrationTestCategory { 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) HttpApi.fromHostAndPort(it.second, "api/irs", mapper = mapper) } @@ -108,7 +104,7 @@ class IRSDemoTest : IntegrationTestCategory { private fun runUploadRates(host: HostAndPort) { println("Running upload rates against $host") 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() } diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/InterestRateSwapAPI.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/InterestRateSwapAPI.kt index fcbbf31c21..29821de3c9 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/InterestRateSwapAPI.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/InterestRateSwapAPI.kt @@ -8,11 +8,7 @@ import net.corda.core.messaging.startFlow import net.corda.core.utilities.loggerFor import net.corda.irs.contract.InterestRateSwap import net.corda.irs.flows.AutoOfferFlow -import net.corda.irs.flows.UpdateBusinessDayFlow import java.net.URI -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 @@ -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. * - * 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). * * 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() } } - - @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() - } } diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt index 3698a3071b..f3791bbe64 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt @@ -5,17 +5,19 @@ import net.corda.contracts.BusinessCalendar import net.corda.contracts.Fix import net.corda.contracts.FixOf 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.ThreadBox import net.corda.core.contracts.Command import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.MerkleTreeException import net.corda.core.crypto.keys import net.corda.core.flows.FlowLogic import net.corda.core.flows.InitiatedBy +import net.corda.core.flows.StartableByRPC 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.ServiceHub 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.unwrap 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.security.PublicKey -import java.time.Instant import java.time.LocalDate import java.util.* import javax.annotation.concurrent.ThreadSafe +import kotlin.collections.HashSet import kotlin.collections.component1 import kotlin.collections.component2 import kotlin.collections.set @@ -76,7 +70,7 @@ object NodeInterestRates { val request = receive(otherParty).unwrap { it } progressTracker.currentStep = RECEIVED val oracle = serviceHub.cordaService(Oracle::class.java) - val answers = oracle.query(request.queries, request.deadline) + val answers = oracle.query(request.queries) progressTracker.currentStep = SENDING send(otherParty, answers) } @@ -91,7 +85,7 @@ object NodeInterestRates { @ThreadSafe // DOCSTART 3 @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( services.myInfo.serviceIdentities(type).first(), 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") } - 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 { - val fixes = object : AbstractJDBCHashSet(Table) { - override fun elementFromRow(row: ResultRow): 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 - } - } + // TODO Update this to use a database once we have an database API + val fixes = HashSet() var container: FixContainer = FixContainer(fixes) } - private val mutex = FiberBox(InnerState()) + private val mutex = ThreadBox(InnerState()) var knownFixes: FixContainer set(value) { require(value.size > 0) - mutex.write { + mutex.locked { fixes.clear() fixes.addAll(value.fixes) 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 init { 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 - fun query(queries: List, deadline: Instant): List { + fun query(queries: List): List { require(queries.isNotEmpty()) - return mutex.readWithDeadline(services.clock, deadline) { + return mutex.locked { val answers: List = queries.map { container[it] } val firstNull = answers.indexOf(null) if (firstNull != -1) { @@ -204,20 +176,21 @@ object NodeInterestRates { } // DOCEND 1 - // File upload support - override val dataTypePrefix = "interest-rates" - 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" + fun uploadFixes(s: String) { + knownFixes = parseFile(s) } } // TODO: can we split into two? Fix not available (retryable/transient) and unknown (permanent) 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() { + @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] */ class FixContainer(val fixes: Set, val factory: InterpolatorFactory = CubicSplineInterpolator) { private val container = buildContainer(fixes) @@ -298,17 +271,21 @@ object NodeInterestRates { map(String::trim). // Filter out comment and empty lines. filterNot { it.startsWith("#") || it.isBlank() }. - map { parseFix(it) }. + map(this::parseFix). toSet() return FixContainer(fixes) } /** Parses a string of the form "LIBOR 16-March-2016 1M = 0.678" into a [Fix] */ - fun parseFix(s: String): Fix { - val (key, value) = s.split('=').map(String::trim) - val of = parseFixOf(key) - val rate = BigDecimal(value) - return Fix(of, rate) + private fun parseFix(s: String): Fix { + try { + val (key, value) = s.split('=').map(String::trim) + val of = parseFixOf(key) + 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] */ diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt index c708c4e4af..61f4256ce4 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt @@ -13,9 +13,7 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.unwrap import net.corda.irs.flows.RatesFixFlow.FixOutOfRange -import net.corda.irs.utilities.suggestInterestRateAnnouncementTimeWindow import java.math.BigDecimal -import java.time.Instant import java.util.* 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") @CordaSerializable - data class QueryRequest(val queries: List, val deadline: Instant) + data class QueryRequest(val queries: List) @CordaSerializable 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() { @Suspendable override fun call(): Fix { - val deadline = suggestInterestRateAnnouncementTimeWindow(fixOf.name, oracle.name.toString(), fixOf.forDay).untilTime!! // TODO: add deadline to receive - val resp = sendAndReceive>(oracle, QueryRequest(listOf(fixOf), deadline)) + val resp = sendAndReceive>(oracle, QueryRequest(listOf(fixOf))) return resp.unwrap { val fix = it.first() diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/utilities/HttpUtils.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/utilities/HttpUtils.kt index 1d6687d4f5..3337730462 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/utilities/HttpUtils.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/utilities/HttpUtils.kt @@ -1,6 +1,9 @@ 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.util.concurrent.TimeUnit @@ -24,10 +27,7 @@ fun postJson(url: URL, data: String): Boolean { } fun uploadFile(url: URL, file: String): Boolean { - val body = MultipartBody.Builder() - .setType(MultipartBody.FORM) - .addFormDataPart("rates", "net/corda/irs/simulation/example.rates.txt", RequestBody.create(MediaType.parse("text/plain"), file)) - .build() + val body = RequestBody.create(MediaType.parse("text/plain; charset=utf-8"), file) return makeRequest(Request.Builder().url(url).post(body).build()) } diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/IRSDemo.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/IRSDemo.kt similarity index 97% rename from samples/irs-demo/src/main/kotlin/net/corda/irs/IRSDemo.kt rename to samples/irs-demo/src/test/kotlin/net/corda/irs/IRSDemo.kt index cf61d9d9f2..fa4a2f020a 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/IRSDemo.kt +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/IRSDemo.kt @@ -4,7 +4,6 @@ package net.corda.irs import com.google.common.net.HostAndPort import joptsimple.OptionParser -import net.corda.irs.api.IRSDemoClientApi import kotlin.system.exitProcess enum class Role { diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/IrsDemoClientApi.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/IrsDemoClientApi.kt similarity index 93% rename from samples/irs-demo/src/main/kotlin/net/corda/irs/api/IrsDemoClientApi.kt rename to samples/irs-demo/src/test/kotlin/net/corda/irs/IrsDemoClientApi.kt index 0d70e065ed..c983115559 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/IrsDemoClientApi.kt +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/IrsDemoClientApi.kt @@ -1,4 +1,4 @@ -package net.corda.irs.api +package net.corda.irs import com.google.common.net.HostAndPort 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 fun runUploadRates() { 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)) println("Rates successfully uploaded!") } diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/Main.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/Main.kt similarity index 100% rename from samples/irs-demo/src/main/kotlin/net/corda/irs/Main.kt rename to samples/irs-demo/src/test/kotlin/net/corda/irs/Main.kt diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/InterestRatesSwapDemoAPI.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/InterestRatesSwapDemoAPI.kt new file mode 100644 index 0000000000..8e9cb6045b --- /dev/null +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/InterestRatesSwapDemoAPI.kt @@ -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() + } + + @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() + } +} \ No newline at end of file diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/testing/NodeInterestRatesTest.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt similarity index 93% rename from samples/irs-demo/src/test/kotlin/net/corda/irs/testing/NodeInterestRatesTest.kt rename to samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt index f8ac8ea194..b1ad326455 100644 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/testing/NodeInterestRatesTest.kt +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt @@ -1,4 +1,4 @@ -package net.corda.irs.testing +package net.corda.irs.api import net.corda.contracts.Fix 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.LogHelper import net.corda.core.utilities.ProgressTracker -import net.corda.irs.api.NodeInterestRates import net.corda.irs.flows.RatesFixFlow import net.corda.node.utilities.configureDatabase import net.corda.node.utilities.transaction @@ -54,8 +53,6 @@ class NodeInterestRatesTest { 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 dummyServices = MockServices(DUMMY_CASH_ISSUER_KEY, MEGA_CORP_KEY) - val clock get() = dummyServices.clock lateinit var oracle: NodeInterestRates.Oracle lateinit var dataSource: Closeable lateinit var database: Database @@ -75,7 +72,11 @@ class NodeInterestRatesTest { dataSource = dataSourceAndDatabase.first database = dataSourceAndDatabase.second 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`() { database.transaction { 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("0.678".bd, res[0].value) assertEquals(q, res[0].of) @@ -100,7 +101,7 @@ class NodeInterestRatesTest { database.transaction { val q1 = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M") val q2 = NodeInterestRates.parseFixOf("LIBOR 2016-03-15 1M") - val e = assertFailsWith { oracle.query(listOf(q1, q2), clock.instant()) } + val e = assertFailsWith { oracle.query(listOf(q1, q2)) } assertEquals(e.fix, q2) } } @@ -109,7 +110,7 @@ class NodeInterestRatesTest { fun `query successfully with interpolated rate`() { database.transaction { 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) Assert.assertEquals(0.7316228, res[0].value.toDouble(), 0.0000001) assertEquals(q, res[0].of) @@ -120,14 +121,14 @@ class NodeInterestRatesTest { fun `rate missing and unable to interpolate`() { database.transaction { val q = NodeInterestRates.parseFixOf("EURIBOR 2016-03-15 3M") - assertFailsWith { oracle.query(listOf(q), clock.instant()) } + assertFailsWith { oracle.query(listOf(q)) } } } @Test fun `empty query`() { database.transaction { - assertFailsWith { oracle.query(emptyList(), clock.instant()) } + assertFailsWith { oracle.query(emptyList()) } } } @@ -157,7 +158,7 @@ class NodeInterestRatesTest { fun `sign successfully`() { database.transaction { 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) // Sign successfully. val wtx = tx.toWireTransaction() @@ -185,7 +186,7 @@ class NodeInterestRatesTest { fun `do not sign too many leaves`() { database.transaction { 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 { return when (elem) { is Command -> oracle.identity.owningKey in elem.signers && elem.value is Fix diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/testing/IRSTests.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt similarity index 99% rename from samples/irs-demo/src/test/kotlin/net/corda/irs/testing/IRSTests.kt rename to samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt index 9be47df1ac..78b2e7974e 100644 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/testing/IRSTests.kt +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt @@ -1,4 +1,4 @@ -package net.corda.irs.testing +package net.corda.irs.contract import net.corda.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_KEY import net.corda.core.utilities.TEST_TX_TIME -import net.corda.irs.contract.* import net.corda.testing.* import net.corda.testing.node.MockServices import org.junit.Test diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/UpdateBusinessDayFlow.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/flows/UpdateBusinessDayFlow.kt similarity index 84% rename from samples/irs-demo/src/main/kotlin/net/corda/irs/flows/UpdateBusinessDayFlow.kt rename to samples/irs-demo/src/test/kotlin/net/corda/irs/flows/UpdateBusinessDayFlow.kt index 8c0720a073..cce9a84d78 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/UpdateBusinessDayFlow.kt +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/flows/UpdateBusinessDayFlow.kt @@ -11,7 +11,6 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.unwrap import net.corda.node.utilities.TestClock -import net.corda.testing.node.MockNetworkMapCache import java.time.LocalDate /** @@ -19,8 +18,6 @@ import java.time.LocalDate */ 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 data class UpdateBusinessDayMessage(val date: LocalDate) @@ -32,7 +29,6 @@ object UpdateBusinessDayFlow { } } - @InitiatingFlow @StartableByRPC class Broadcast(val date: LocalDate, override val progressTracker: ProgressTracker) : FlowLogic() { @@ -65,12 +61,7 @@ object UpdateBusinessDayFlow { @Suspendable private fun doNextRecipient(recipient: NodeInfo) { - if (recipient.address is MockNetworkMapCache.MockAddress) { - // Ignore - } else { - send(recipient.legalIdentity, UpdateBusinessDayMessage(date)) - } + send(recipient.legalIdentity, UpdateBusinessDayMessage(date)) } } - } diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/plugin/IRSDemoPlugin.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/plugin/IRSDemoPlugin.kt new file mode 100644 index 0000000000..c7d2e50474 --- /dev/null +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/plugin/IRSDemoPlugin.kt @@ -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)) +} \ No newline at end of file diff --git a/samples/irs-demo/src/test/resources/META-INF/services/net.corda.webserver.services.WebServerPluginRegistry b/samples/irs-demo/src/test/resources/META-INF/services/net.corda.webserver.services.WebServerPluginRegistry new file mode 100644 index 0000000000..3f23b14e6a --- /dev/null +++ b/samples/irs-demo/src/test/resources/META-INF/services/net.corda.webserver.services.WebServerPluginRegistry @@ -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 \ No newline at end of file diff --git a/samples/network-visualiser/build.gradle b/samples/network-visualiser/build.gradle index 8170451d88..86337f2baa 100644 --- a/samples/network-visualiser/build.gradle +++ b/samples/network-visualiser/build.gradle @@ -7,6 +7,7 @@ apply plugin: 'us.kirchmeier.capsule' dependencies { compile project(':samples:irs-demo') + compile project(':test-utils') compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" testCompile "junit:junit:$junit_version" @@ -16,7 +17,6 @@ dependencies { compile project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts') compile project(':core') compile project(':finance') - testCompile project(':test-utils') // Javax is required for webapis compile "org.glassfish.jersey.core:jersey-server:${jersey_version}" diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/NetworkMapVisualiser.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/NetworkMapVisualiser.kt index 201a6e492d..655badd4c6 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/NetworkMapVisualiser.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/NetworkMapVisualiser.kt @@ -15,9 +15,9 @@ import net.corda.core.crypto.commonName import net.corda.core.serialization.deserialize import net.corda.core.then 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.simulation.IRSSimulation +import net.corda.netmap.simulation.Simulation import net.corda.node.services.network.NetworkMapService import net.corda.node.services.statemachine.SessionConfirm import net.corda.node.services.statemachine.SessionEnd diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/VisualiserViewModel.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/VisualiserViewModel.kt index 76e6c8888c..afcfac1f43 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/VisualiserViewModel.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/VisualiserViewModel.kt @@ -9,7 +9,7 @@ import javafx.scene.shape.Line import javafx.util.Duration import net.corda.core.crypto.commonName 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 org.bouncycastle.asn1.x500.X500Name import java.util.* @@ -50,7 +50,7 @@ class VisualiserViewModel { var bankCount: 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 displayStyle: Style = Style.MAP diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/simulation/IRSSimulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt similarity index 99% rename from samples/irs-demo/src/main/kotlin/net/corda/irs/simulation/IRSSimulation.kt rename to samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt index f6ce54c884..139bb7c81e 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/simulation/IRSSimulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt @@ -1,4 +1,4 @@ -package net.corda.irs.simulation +package net.corda.netmap.simulation import co.paralleluniverse.fibers.Suspendable import com.fasterxml.jackson.databind.ObjectMapper diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/simulation/Simulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt similarity index 99% rename from samples/irs-demo/src/main/kotlin/net/corda/irs/simulation/Simulation.kt rename to samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt index 2234a41119..c202c808c8 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/simulation/Simulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt @@ -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.ListenableFuture @@ -133,7 +133,7 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java) javaClass.classLoader.getResourceAsStream("net/corda/irs/simulation/example.rates.txt").use { database.transaction { - installCordaService(NodeInterestRates.Oracle::class.java).upload(it) + installCordaService(NodeInterestRates.Oracle::class.java).uploadFixes(it.reader().readText()) } } return this diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/testing/IRSSimulationTest.kt b/samples/network-visualiser/src/test/kotlin/net/corda/netmap/simulation/IRSSimulationTest.kt similarity index 84% rename from samples/irs-demo/src/test/kotlin/net/corda/irs/testing/IRSSimulationTest.kt rename to samples/network-visualiser/src/test/kotlin/net/corda/netmap/simulation/IRSSimulationTest.kt index bcaf971349..97777c9dbe 100644 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/testing/IRSSimulationTest.kt +++ b/samples/network-visualiser/src/test/kotlin/net/corda/netmap/simulation/IRSSimulationTest.kt @@ -1,8 +1,7 @@ -package net.corda.irs.testing +package net.corda.netmap.simulation import net.corda.core.getOrThrow import net.corda.core.utilities.LogHelper -import net.corda.irs.simulation.IRSSimulation import org.junit.Test class IRSSimulationTest { diff --git a/samples/simm-valuation-demo/build.gradle b/samples/simm-valuation-demo/build.gradle index 7a82f32234..283ecd43ba 100644 --- a/samples/simm-valuation-demo/build.gradle +++ b/samples/simm-valuation-demo/build.gradle @@ -27,8 +27,6 @@ configurations { dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" - testCompile "junit:junit:$junit_version" - testCompile "org.assertj:assertj-core:${assertj_version}" // Corda integration dependencies compile project(path: ":node:capsule", configuration: 'runtimeArtifacts') @@ -36,7 +34,6 @@ dependencies { compile project(':core') compile project(':webserver') compile project(':finance') - compile project(':test-utils') // Javax is required for webapis 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-loader:${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']) { diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/SimmFlow.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/SimmFlow.kt index 130675b4b4..9553a5347e 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/SimmFlow.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/SimmFlow.kt @@ -22,7 +22,6 @@ import net.corda.core.utilities.unwrap import net.corda.flows.AbstractStateReplacementFlow.Proposal import net.corda.flows.StateReplacementException import net.corda.flows.TwoPartyDealFlow -import net.corda.node.services.messaging.Ack import net.corda.vega.analytics.* import net.corda.vega.contracts.* import net.corda.vega.portfolio.Portfolio @@ -320,4 +319,6 @@ object SimmFlow { }) } } + + private object Ack } diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/Main.kt b/samples/simm-valuation-demo/src/test/kotlin/net/corda/vega/Main.kt similarity index 87% rename from samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/Main.kt rename to samples/simm-valuation-demo/src/test/kotlin/net/corda/vega/Main.kt index 869aecb4f9..3410f4ea48 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/Main.kt +++ b/samples/simm-valuation-demo/src/test/kotlin/net/corda/vega/Main.kt @@ -1,14 +1,14 @@ package net.corda.vega import com.google.common.util.concurrent.Futures -import net.corda.core.crypto.X509Utilities import net.corda.core.getOrThrow import net.corda.core.node.services.ServiceInfo import net.corda.core.utilities.DUMMY_BANK_A import net.corda.core.utilities.DUMMY_BANK_B 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.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 @@ -17,7 +17,7 @@ import net.corda.node.services.transactions.SimpleNotaryService */ fun main(args: Array) { driver(dsl = { - startNode(X509Utilities.getDevX509Name("Controller"), setOf(ServiceInfo(SimpleNotaryService.type))) + startNode(DUMMY_NOTARY.name, setOf(ServiceInfo(SimpleNotaryService.type))) val (nodeA, nodeB, nodeC) = Futures.allAsList( startNode(DUMMY_BANK_A.name), startNode(DUMMY_BANK_B.name), diff --git a/samples/trader-demo/build.gradle b/samples/trader-demo/build.gradle index c7213addf5..15a63e7fe9 100644 --- a/samples/trader-demo/build.gradle +++ b/samples/trader-demo/build.gradle @@ -23,15 +23,12 @@ configurations { dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" - testCompile "junit:junit:$junit_version" - testCompile "org.assertj:assertj-core:${assertj_version}" // Corda integration dependencies compile project(path: ":node:capsule", configuration: 'runtimeArtifacts') compile project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts') compile project(':core') compile project(':finance') - compile project(':test-utils') // Corda Plugins: dependent flows and services compile project(':samples:bank-of-corda-demo') @@ -45,6 +42,10 @@ dependencies { exclude group: "bouncycastle" } + testCompile project(':test-utils') + testCompile "junit:junit:$junit_version" + testCompile "org.assertj:assertj-core:${assertj_version}" + // Cordapp dependencies // Specify your cordapp's dependencies below, including dependent cordapps } diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/Main.kt b/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt similarity index 93% rename from samples/trader-demo/src/main/kotlin/net/corda/traderdemo/Main.kt rename to samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt index 5a0dc449c8..af4638e205 100644 --- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/Main.kt +++ b/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt @@ -11,6 +11,7 @@ import net.corda.node.services.transactions.SimpleNotaryService import net.corda.nodeapi.User import net.corda.testing.BOC 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) @@ -19,7 +20,7 @@ import net.corda.testing.driver.driver fun main(args: Array) { val permissions = setOf( startFlowPermission(), - startFlowPermission()) + startFlowPermission()) val demoUser = listOf(User("demo", "demo", permissions)) driver(driverDirectory = "build" / "trader-demo-nodes", isDebug = true) { val user = User("user1", "test", permissions = setOf(startFlowPermission()))