WIP - squash me.

This commit is contained in:
Clinton Alexander 2017-06-08 18:29:26 +01:00
parent de64d13647
commit 5fdd57ec48
7 changed files with 79 additions and 40 deletions

View File

@ -23,6 +23,7 @@ import net.i2p.crypto.eddsa.EdDSAPublicKey
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import java.math.BigDecimal import java.math.BigDecimal
import java.security.PublicKey import java.security.PublicKey
import java.time.LocalDate
import java.util.* import java.util.*
/** /**
@ -76,6 +77,7 @@ object JacksonSupport {
addSerializer(SecureHash.SHA256::class.java, SecureHashSerializer) addSerializer(SecureHash.SHA256::class.java, SecureHashSerializer)
addDeserializer(SecureHash::class.java, SecureHashDeserializer()) addDeserializer(SecureHash::class.java, SecureHashDeserializer())
addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer()) addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer())
addSerializer(BusinessCalendar::class.java, CalendarSerializer)
addDeserializer(BusinessCalendar::class.java, CalendarDeserializer) addDeserializer(BusinessCalendar::class.java, CalendarDeserializer)
// For ed25519 pubkeys // For ed25519 pubkeys
@ -243,11 +245,31 @@ object JacksonSupport {
} }
} }
data class BusinessCalendarWrapper(val holidayDates: List<LocalDate>) {
fun toCalendar() = BusinessCalendar(holidayDates)
}
object CalendarSerializer : JsonSerializer<BusinessCalendar>() {
override fun serialize(obj: BusinessCalendar, generator: JsonGenerator, context: SerializerProvider) {
println(obj)
val calendarName = BusinessCalendar.calendars.find { BusinessCalendar.getInstance(it) == obj }
if(calendarName != null) {
generator.writeString(calendarName)
} else {
generator.writeObject(BusinessCalendarWrapper(obj.holidayDates))
}
}
}
object CalendarDeserializer : JsonDeserializer<BusinessCalendar>() { object CalendarDeserializer : JsonDeserializer<BusinessCalendar>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): BusinessCalendar { override fun deserialize(parser: JsonParser, context: DeserializationContext): BusinessCalendar {
return try { return try {
try {
val array = StringArrayDeserializer.instance.deserialize(parser, context) val array = StringArrayDeserializer.instance.deserialize(parser, context)
BusinessCalendar.getInstance(*array) BusinessCalendar.getInstance(*array)
} catch (e: Exception) {
parser.readValueAs(BusinessCalendarWrapper::class.java).toCalendar()
}
} catch (e: Exception) { } catch (e: Exception) {
throw JsonParseException(parser, "Invalid calendar(s) ${parser.text}: ${e.message}") throw JsonParseException(parser, "Invalid calendar(s) ${parser.text}: ${e.message}")
} }

View File

@ -633,7 +633,7 @@ fun LocalDate.isWorkingDay(accordingToCalendar: BusinessCalendar): Boolean = acc
* no staff are around to handle problems. * no staff are around to handle problems.
*/ */
@CordaSerializable @CordaSerializable
open class BusinessCalendar private constructor(val holidayDates: List<LocalDate>) { open class BusinessCalendar(val holidayDates: List<LocalDate>) {
@CordaSerializable @CordaSerializable
class UnknownCalendar(name: String) : Exception("$name not found") class UnknownCalendar(name: String) : Exception("$name not found")

View File

@ -4,7 +4,9 @@ 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.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_NOTARY import net.corda.core.utilities.DUMMY_NOTARY
@ -22,14 +24,18 @@ 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.observables.BlockingObservable import rx.observables.BlockingObservable
import java.net.URL import java.net.URL
import java.time.Duration
import java.time.LocalDate import java.time.LocalDate
import java.time.temporal.ChronoUnit
class IRSDemoTest : IntegrationTestCategory { class IRSDemoTest : IntegrationTestCategory {
val rpcUser = User("user", "password", emptySet()) val rpcUser = User("user", "password", emptySet())
val currentDate: LocalDate = LocalDate.now() val currentDate: LocalDate = LocalDate.now()
val futureDate: LocalDate = currentDate.plusMonths(6) val futureDate: LocalDate = currentDate.plusMonths(6)
val maxWaitTime: Duration = Duration.of(60, ChronoUnit.SECONDS)
@Test @Test
fun `runs IRS demo`() { fun `runs IRS demo`() {
@ -50,51 +56,50 @@ 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 mapper = net.corda.jackson.JacksonSupport.createDefaultMapper(it.first.rpc)
HttpApi.fromHostAndPort(it.second, "api/irs", mapper = mapper)
}
val nextFixingDates = getFixingDateObservable(nodeA.configuration) val nextFixingDates = getFixingDateObservable(nodeA.configuration)
val numADeals = getTradeCount(nodeAAddr) val numADeals = getTradeCount(nodeAApi)
val numBDeals = getTradeCount(nodeBAddr) val numBDeals = getTradeCount(nodeBApi)
runUploadRates(controllerAddr) runUploadRates(controllerAddr)
runTrade(nodeAAddr) runTrade(nodeAApi)
assertThat(getTradeCount(nodeAAddr)).isEqualTo(numADeals + 1) assertThat(getTradeCount(nodeAApi)).isEqualTo(numADeals + 1)
assertThat(getTradeCount(nodeBAddr)).isEqualTo(numBDeals + 1) assertThat(getTradeCount(nodeBApi)).isEqualTo(numBDeals + 1)
// Wait until the initial trade and all scheduled fixings up to the current date have finished // Wait until the initial trade and all scheduled fixings up to the current date have finished
nextFixingDates.first { it == null || it > currentDate } nextFixingDates.firstWithTimeout(maxWaitTime){ it == null || it > currentDate }
runDateChange(nodeBApi)
nextFixingDates.firstWithTimeout(maxWaitTime) { it == null || it > futureDate }
runDateChange(nodeBAddr) assertThat(getTrades(nodeAApi)[0])
nextFixingDates.first { it == null || it > futureDate }
assertThat(getTrades(nodeAAddr)[0] as InterestRateSwap.State)
} }
} }
fun getFixingDateObservable(config: FullNodeConfiguration): BlockingObservable<LocalDate?> { fun getFixingDateObservable(config: FullNodeConfiguration): Observable<LocalDate?> {
val client = CordaRPCClient(config.rpcAddress!!) val client = CordaRPCClient(config.rpcAddress!!)
val proxy = client.start("user", "password").proxy val proxy = client.start("user", "password").proxy
val vaultUpdates = proxy.vaultAndUpdates().second val vaultUpdates = proxy.vaultAndUpdates().second
val fixingDates = vaultUpdates.map { update -> return vaultUpdates.map { update ->
val irsStates = update.produced.map { it.state.data }.filterIsInstance<InterestRateSwap.State>() val irsStates = update.produced.map { it.state.data }.filterIsInstance<InterestRateSwap.State>()
irsStates.mapNotNull { it.calculation.nextFixingDate() }.max() irsStates.mapNotNull { it.calculation.nextFixingDate() }.max()
}.cache().toBlocking() }.cache()
return fixingDates
} }
private fun runDateChange(nodeAddr: HostAndPort) { private fun runDateChange(nodeApi: HttpApi) {
println("Running date change against $nodeAddr") println("Running date change against ${nodeApi.root}")
val url = URL("http://$nodeAddr/api/irs/demodate") assertThat(nodeApi.putJson("demodate", "\"$futureDate\"")).isTrue()
assertThat(putJson(url, "\"$futureDate\"")).isTrue()
} }
private fun runTrade(nodeAddr: HostAndPort) { private fun runTrade(nodeApi: HttpApi) {
println("Running trade against $nodeAddr") println("Running trade against ${nodeApi.root}")
val fileContents = loadResourceFile("net/corda/irs/simulation/example-irs-trade.json") val fileContents = loadResourceFile("net/corda/irs/simulation/example-irs-trade.json")
val tradeFile = fileContents.replace("tradeXXX", "trade1") val tradeFile = fileContents.replace("tradeXXX", "trade1")
val url = URL("http://$nodeAddr/api/irs/deals") assertThat(nodeApi.postJson("deals", tradeFile)).isTrue()
assertThat(postJson(url, tradeFile)).isTrue()
} }
private fun runUploadRates(host: HostAndPort) { private fun runUploadRates(host: HostAndPort) {
@ -108,17 +113,19 @@ class IRSDemoTest : IntegrationTestCategory {
return IOUtils.toString(Thread.currentThread().contextClassLoader.getResourceAsStream(filename), Charsets.UTF_8.name()) return IOUtils.toString(Thread.currentThread().contextClassLoader.getResourceAsStream(filename), Charsets.UTF_8.name())
} }
private fun getTradeCount(nodeAddr: HostAndPort): Int { private fun getTradeCount(nodeApi: HttpApi): Int {
println("Getting trade count from $nodeAddr") println("Getting trade count from ${nodeApi.root}")
val api = HttpApi.fromHostAndPort(nodeAddr, "api/irs") val deals = nodeApi.getJson<Array<*>>("deals")
val deals = api.getJson<Array<*>>("deals")
return deals.size return deals.size
} }
private fun getTrades(nodeAddr: HostAndPort): Array<*> { private fun getTrades(nodeApi: HttpApi): Array<InterestRateSwap.State> {
println("Getting trades from $nodeAddr") println("Getting trades from ${nodeApi.root}")
val api = HttpApi.fromHostAndPort(nodeAddr, "api/irs") val deals = nodeApi.getJson<Array<InterestRateSwap.State>>("deals")
val deals = api.getJson<Array<*>>("deals")
return deals return deals
} }
fun<T> Observable<T>.firstWithTimeout(timeout: Duration, pred: (T) -> Boolean) {
first(pred).toFuture().getOrThrow(timeout)
}
} }

View File

@ -1,5 +1,8 @@
package net.corda.irs.contract package net.corda.irs.contract
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.contracts.clauses.* import net.corda.core.contracts.clauses.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
@ -129,6 +132,7 @@ class FixedRatePaymentEvent(date: LocalDate,
* If the rate is null returns a zero payment. // TODO: Is this the desired behaviour? * If the rate is null returns a zero payment. // TODO: Is this the desired behaviour?
*/ */
@CordaSerializable @CordaSerializable
@JsonIgnoreProperties(ignoreUnknown = true)
class FloatingRatePaymentEvent(date: LocalDate, class FloatingRatePaymentEvent(date: LocalDate,
accrualStartDate: LocalDate, accrualStartDate: LocalDate,
accrualEndDate: LocalDate, accrualEndDate: LocalDate,

View File

@ -1,5 +1,6 @@
package net.corda.irs.contract package net.corda.irs.contract
import com.fasterxml.jackson.annotation.JsonIgnore
import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount
import net.corda.core.contracts.Tenor import net.corda.core.contracts.Tenor
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
@ -63,6 +64,7 @@ open class Rate(val ratioUnit: RatioUnit? = null) {
*/ */
@CordaSerializable @CordaSerializable
class FixedRate(ratioUnit: RatioUnit) : Rate(ratioUnit) { class FixedRate(ratioUnit: RatioUnit) : Rate(ratioUnit) {
@JsonIgnore
fun isPositive(): Boolean = ratioUnit!!.value > BigDecimal("0.0") fun isPositive(): Boolean = ratioUnit!!.value > BigDecimal("0.0")
override fun equals(other: Any?) = other?.javaClass == javaClass && super.equals(other) override fun equals(other: Any?) = other?.javaClass == javaClass && super.equals(other)

View File

@ -1,9 +1,10 @@
package net.corda.testing.http package net.corda.testing.http
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.common.net.HostAndPort import com.google.common.net.HostAndPort
import java.net.URL import java.net.URL
class HttpApi(val root: URL) { class HttpApi(val root: URL, val mapper: ObjectMapper = defaultMapper) {
/** /**
* Send a PUT with a payload to the path on the API specified. * Send a PUT with a payload to the path on the API specified.
* *
@ -21,12 +22,15 @@ class HttpApi(val root: URL) {
/** /**
* Send a GET request to the path on the API specified. * Send a GET request to the path on the API specified.
*/ */
inline fun <reified T : Any> getJson(path: String, params: Map<String, String> = mapOf()) = HttpUtils.getJson<T>(URL(root, path), params) inline fun <reified T : Any> getJson(path: String, params: Map<String, String> = mapOf()) = HttpUtils.getJson<T>(URL(root, path), params, mapper)
private fun toJson(any: Any) = any as? String ?: HttpUtils.defaultMapper.writeValueAsString(any) private fun toJson(any: Any) = any as? String ?: HttpUtils.defaultMapper.writeValueAsString(any)
companion object { companion object {
fun fromHostAndPort(hostAndPort: HostAndPort, base: String, protocol: String = "http"): HttpApi fun fromHostAndPort(hostAndPort: HostAndPort, base: String, protocol: String = "http", mapper: ObjectMapper = defaultMapper): HttpApi
= HttpApi(URL("$protocol://$hostAndPort/$base/")) = HttpApi(URL("$protocol://$hostAndPort/$base/"), mapper)
private val defaultMapper: ObjectMapper by lazy {
net.corda.jackson.JacksonSupport.createNonRpcMapper()
}
} }
} }

View File

@ -35,10 +35,10 @@ object HttpUtils {
return makeRequest(Request.Builder().url(url).header("Content-Type", "application/json").post(body).build()) return makeRequest(Request.Builder().url(url).header("Content-Type", "application/json").post(body).build())
} }
inline fun <reified T : Any> getJson(url: URL, params: Map<String, String> = mapOf()): T { inline fun <reified T : Any> getJson(url: URL, params: Map<String, String> = mapOf(), mapper: ObjectMapper = defaultMapper): T {
val paramString = if (params.isEmpty()) "" else "?" + params.map { "${it.key}=${it.value}" }.joinToString("&") val paramString = if (params.isEmpty()) "" else "?" + params.map { "${it.key}=${it.value}" }.joinToString("&")
val parameterisedUrl = URL(url.toExternalForm() + paramString) val parameterisedUrl = URL(url.toExternalForm() + paramString)
return defaultMapper.readValue(parameterisedUrl, T::class.java) return mapper.readValue(parameterisedUrl, T::class.java)
} }
private fun makeRequest(request: Request): Boolean { private fun makeRequest(request: Request): Boolean {