First working commit

First working commit

Formatting clean up

Revert All_tests.xml

Remove extra space

Feedback from code review

First working commit

Revert All_tests.xml

Remove extra space

Feedback from code review
This commit is contained in:
rick.parker 2016-03-23 11:15:09 +00:00
parent c450c70f9a
commit 791022f130
18 changed files with 260 additions and 102 deletions

View File

@ -8,6 +8,15 @@
package contracts package contracts
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.type.SimpleType
import core.* import core.*
import core.crypto.SecureHash import core.crypto.SecureHash
import core.node.services.DummyTimestampingAuthority import core.node.services.DummyTimestampingAuthority
@ -15,6 +24,7 @@ import org.apache.commons.jexl3.JexlBuilder
import org.apache.commons.jexl3.MapContext import org.apache.commons.jexl3.MapContext
import java.math.BigDecimal import java.math.BigDecimal
import java.math.RoundingMode import java.math.RoundingMode
import java.security.PublicKey
import java.time.LocalDate import java.time.LocalDate
import java.util.* import java.util.*
@ -167,8 +177,6 @@ class InterestRateSwap() : Contract {
val hashLegalDocs: String val hashLegalDocs: String
) )
data class Expression(val expr: String)
/** /**
* The Calculation data class is "mutable" through out the life of the swap, as in, it's the only thing that contains * The Calculation data class is "mutable" through out the life of the swap, as in, it's the only thing that contains
* data that will changed from state to state (Recall that the design insists that everything is immutable, so we actually * data that will changed from state to state (Recall that the design insists that everything is immutable, so we actually
@ -177,7 +185,7 @@ class InterestRateSwap() : Contract {
data class Calculation( data class Calculation(
val expression: Expression, val expression: Expression,
val floatingLegPaymentSchedule: Map<LocalDate, FloatingRatePaymentEvent>, val floatingLegPaymentSchedule: Map<LocalDate, FloatingRatePaymentEvent>,
val fixedLegpaymentSchedule: Map<LocalDate, FixedRatePaymentEvent> val fixedLegPaymentSchedule: Map<LocalDate, FixedRatePaymentEvent>
) { ) {
/** /**
* Gets the date of the next fixing. * Gets the date of the next fixing.
@ -185,7 +193,7 @@ class InterestRateSwap() : Contract {
*/ */
fun nextFixingDate(): LocalDate? { fun nextFixingDate(): LocalDate? {
return floatingLegPaymentSchedule. return floatingLegPaymentSchedule.
filter { it.value.rate is OracleRetrievableReferenceRate }.// TODO - a better way to determine what fixings remain to be fixed filter { it.value.rate is ReferenceRate }.// TODO - a better way to determine what fixings remain to be fixed
minBy { it.value.fixingDate.toEpochDay() }?.value?.fixingDate minBy { it.value.fixingDate.toEpochDay() }?.value?.fixingDate
} }
@ -203,7 +211,7 @@ class InterestRateSwap() : Contract {
val newFloatingLPS = floatingLegPaymentSchedule + (paymentEvent.date to paymentEvent.withNewRate(newRate)) val newFloatingLPS = floatingLegPaymentSchedule + (paymentEvent.date to paymentEvent.withNewRate(newRate))
return Calculation(expression = expression, return Calculation(expression = expression,
floatingLegPaymentSchedule = newFloatingLPS, floatingLegPaymentSchedule = newFloatingLPS,
fixedLegpaymentSchedule = fixedLegpaymentSchedule) fixedLegPaymentSchedule = fixedLegPaymentSchedule)
} }
fun exportSchedule() { fun exportSchedule() {
@ -297,7 +305,7 @@ class InterestRateSwap() : Contract {
*/ */
override fun verify(tx: TransactionForVerification) { override fun verify(tx: TransactionForVerification) {
val command = tx.commands.requireSingleCommand<InterestRateSwap.Commands>() val command = tx.commands.requireSingleCommand<InterestRateSwap.Commands>()
val time = tx.getTimestampBy(DummyTimestampingAuthority.identity)?.midpoint val time = tx.commands.getTimestampByName("Mock Company 0", "Bank of Zurich")?.midpoint
if (time == null) throw IllegalArgumentException("must be timestamped") if (time == null) throw IllegalArgumentException("must be timestamped")
val irs = tx.outStates.filterIsInstance<InterestRateSwap.State>().single() val irs = tx.outStates.filterIsInstance<InterestRateSwap.State>().single()
@ -306,7 +314,7 @@ class InterestRateSwap() : Contract {
requireThat { requireThat {
"There are no in states for an agreement" by tx.inStates.isEmpty() "There are no in states for an agreement" by tx.inStates.isEmpty()
"The fixed rate is non zero" by (irs.fixedLeg.fixedRate != FixedRate(PercentageRatioUnit("0.0"))) "The fixed rate is non zero" by (irs.fixedLeg.fixedRate != FixedRate(PercentageRatioUnit("0.0")))
"There are events in the fix schedule" by (irs.calculation.fixedLegpaymentSchedule.size > 0) "There are events in the fix schedule" by (irs.calculation.fixedLegPaymentSchedule.size > 0)
"There are events in the float schedule" by (irs.calculation.floatingLegPaymentSchedule.size > 0) "There are events in the float schedule" by (irs.calculation.floatingLegPaymentSchedule.size > 0)
// "There are fixes in the schedule" by (irs.calculation.floatingLegPaymentSchedule!!.size > 0) // "There are fixes in the schedule" by (irs.calculation.floatingLegPaymentSchedule!!.size > 0)
// TODO: shortlist of other tests // TODO: shortlist of other tests
@ -348,8 +356,14 @@ class InterestRateSwap() : Contract {
val floatingLeg: FloatingLeg, val floatingLeg: FloatingLeg,
val calculation: Calculation, val calculation: Calculation,
val common: Common val common: Common
) : ContractState { ) : LinearState {
override val programRef = IRS_PROGRAM_ID override val programRef = IRS_PROGRAM_ID
override val thread = SecureHash.sha256(common.tradeID)
override val ref = common.tradeID
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean {
return (fixedLeg.fixedRatePayer.owningKey in ourKeys) || (floatingLeg.floatingRatePayer.owningKey in ourKeys)
}
/** /**
* For evaluating arbitrary java on the platform * For evaluating arbitrary java on the platform
@ -408,9 +422,6 @@ class InterestRateSwap() : Contract {
var floatingLegPaymentSchedule: MutableMap<LocalDate, FloatingRatePaymentEvent> = HashMap() var floatingLegPaymentSchedule: MutableMap<LocalDate, FloatingRatePaymentEvent> = HashMap()
periodStartDate = floatingLeg.effectiveDate periodStartDate = floatingLeg.effectiveDate
// TODO: Temporary until implemented via Rates Oracle.
val telerate = TelerateOracle("3750")
// Now create a schedule for the floating and fixes. // Now create a schedule for the floating and fixes.
for (periodEndDate in dates) { for (periodEndDate in dates) {
val paymentEvent = FloatingRatePaymentEvent( val paymentEvent = FloatingRatePaymentEvent(
@ -421,8 +432,7 @@ class InterestRateSwap() : Contract {
floatingLeg.dayCountBasisYear, floatingLeg.dayCountBasisYear,
calcFixingDate(periodStartDate, floatingLeg.fixingPeriod, floatingLeg.fixingCalendar), calcFixingDate(periodStartDate, floatingLeg.fixingPeriod, floatingLeg.fixingCalendar),
floatingLeg.notional, floatingLeg.notional,
// TODO: OracleRetrievableReferenceRate will be replaced via oracle v soon. ReferenceRate(floatingLeg.indexSource, floatingLeg.indexTenor, floatingLeg.index)
OracleRetrievableReferenceRate(telerate, floatingLeg.indexTenor, floatingLeg.index)
) )
floatingLegPaymentSchedule.put(periodEndDate, paymentEvent) floatingLegPaymentSchedule.put(periodEndDate, paymentEvent)

View File

@ -10,6 +10,6 @@ package contracts
fun InterestRateSwap.State.exportIRSToCSV() : String = fun InterestRateSwap.State.exportIRSToCSV() : String =
"Fixed Leg\n" + FixedRatePaymentEvent.CSVHeader + "\n" + "Fixed Leg\n" + FixedRatePaymentEvent.CSVHeader + "\n" +
this.calculation.fixedLegpaymentSchedule.toSortedMap().values.map{ it.asCSV() }.joinToString("\n") + "\n" + this.calculation.fixedLegPaymentSchedule.toSortedMap().values.map{ it.asCSV() }.joinToString("\n") + "\n" +
"Floating Leg\n" + FloatingRatePaymentEvent.CSVHeader + "\n" + "Floating Leg\n" + FloatingRatePaymentEvent.CSVHeader + "\n" +
this.calculation.floatingLegPaymentSchedule.toSortedMap().values.map{ it.asCSV() }.joinToString("\n") + "\n" this.calculation.floatingLegPaymentSchedule.toSortedMap().values.map{ it.asCSV() }.joinToString("\n") + "\n"

View File

@ -1,5 +1,13 @@
package contracts package contracts
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer
import com.fasterxml.jackson.databind.type.SimpleType
import core.Amount import core.Amount
import core.Tenor import core.Tenor
import java.math.BigDecimal import java.math.BigDecimal
@ -50,43 +58,7 @@ open class FloatingRate: Rate(null)
* So a reference rate is a rate that takes its value from a source at a given date * So a reference rate is a rate that takes its value from a source at a given date
* e.g. LIBOR 6M as of 17 March 2016. Hence it requires a source (name) and a value date in the getAsOf(..) method. * e.g. LIBOR 6M as of 17 March 2016. Hence it requires a source (name) and a value date in the getAsOf(..) method.
*/ */
abstract class ReferenceRate(val name: String): FloatingRate() { class ReferenceRate(val oracle: String, val tenor: Tenor, val name: String) : FloatingRate() {
abstract fun getAsOf(date: LocalDate?) : RatioUnit
}
/**
* A concrete implementation of the above for testing purposes
*/
open class TestReferenceRate(val testrate: String) : ReferenceRate(testrate) {
override fun getAsOf(date: LocalDate?) : RatioUnit {
return testrate.percent
}
}
/**
* This represents a source of data.
*/
abstract class Oracle() { abstract fun retrieve(tenor: Tenor, date: LocalDate) : RatioUnit }
class ReutersOracle() : Oracle() {
override fun retrieve(tenor: Tenor, date: LocalDate): RatioUnit {
TODO("Reuters Oracle retrieval")
}
}
class TelerateOracle(page: String) : Oracle() {
override fun retrieve(tenor: Tenor, date: LocalDate): RatioUnit {
TODO("Telerate Oracle retrieval")
}
}
/**
* A Reference rate that is retrieved via an Oracle.
*/
open class OracleRetrievableReferenceRate(val oracle: Oracle, val tenor: Tenor, referenceRate: String) : ReferenceRate(referenceRate) {
override fun getAsOf(date: LocalDate?): RatioUnit {
return oracle.retrieve(tenor,date!!)
}
override fun toString(): String = "$name - $tenor" override fun toString(): String = "$name - $tenor"
} }

View File

@ -47,4 +47,7 @@ dependencies {
// Apache JEXL: An embeddable expression evaluation library. // Apache JEXL: An embeddable expression evaluation library.
// This may be temporary until we experiment with other ways to do on-the-fly contract specialisation via an API. // This may be temporary until we experiment with other ways to do on-the-fly contract specialisation via an API.
compile "org.apache.commons:commons-jexl3:3.0" compile "org.apache.commons:commons-jexl3:3.0"
// For JSON
compile "com.fasterxml.jackson.core:jackson-databind:2.5.5"
} }

View File

@ -8,6 +8,14 @@
package core package core
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import java.math.BigDecimal import java.math.BigDecimal
import java.time.DayOfWeek import java.time.DayOfWeek
import java.time.LocalDate import java.time.LocalDate
@ -82,7 +90,25 @@ data class FixOf(val name: String, val forDay: LocalDate, val ofTenor: Tenor)
/** A [Fix] represents a named interest rate, on a given day, for a given duration. It can be embedded in a tx. */ /** A [Fix] represents a named interest rate, on a given day, for a given duration. It can be embedded in a tx. */
data class Fix(val of: FixOf, val value: BigDecimal) : CommandData data class Fix(val of: FixOf, val value: BigDecimal) : CommandData
/**
* Represents a textual expression of e.g. a formula
*
*/
@JsonDeserialize(using = ExpressionDeserializer::class)
@JsonSerialize(using = ExpressionSerializer::class)
data class Expression(val expr: String)
object ExpressionSerializer: JsonSerializer<Expression>() {
override fun serialize(expr: Expression, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(expr.expr)
}
}
object ExpressionDeserializer: JsonDeserializer<Expression>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): Expression {
return Expression(parser.text)
}
}
/** /**
* Placeholder class for the Tenor datatype - which is a standardised duration of time until maturity */ * Placeholder class for the Tenor datatype - which is a standardised duration of time until maturity */

View File

@ -4,3 +4,48 @@ LIBOR 2016-03-16 1M = 0.678
LIBOR 2016-03-16 2M = 0.655 LIBOR 2016-03-16 2M = 0.655
EURIBOR 2016-03-15 1M = 0.123 EURIBOR 2016-03-15 1M = 0.123
EURIBOR 2016-03-15 2M = 0.111 EURIBOR 2016-03-15 2M = 0.111
LIBOR 2016-03-08 3M = 0.01
LIBOR 2016-06-08 3M = 0.01
LIBOR 2016-09-08 3M = 0.01
LIBOR 2016-12-08 3M = 0.01
LIBOR 2017-03-08 3M = 0.01
LIBOR 2017-06-08 3M = 0.01
LIBOR 2017-09-07 3M = 0.01
LIBOR 2017-12-07 3M = 0.01
LIBOR 2018-03-08 3M = 0.01
LIBOR 2018-06-07 3M = 0.01
LIBOR 2018-09-06 3M = 0.01
LIBOR 2018-12-06 3M = 0.01
LIBOR 2019-03-07 3M = 0.01
LIBOR 2019-06-06 3M = 0.01
LIBOR 2019-09-06 3M = 0.01
LIBOR 2019-12-06 3M = 0.01
LIBOR 2020-03-06 3M = 0.01
LIBOR 2020-06-08 3M = 0.01
LIBOR 2020-09-08 3M = 0.01
LIBOR 2020-12-08 3M = 0.01
LIBOR 2021-03-08 3M = 0.01
LIBOR 2021-06-08 3M = 0.01
LIBOR 2021-09-08 3M = 0.01
LIBOR 2021-12-08 3M = 0.01
LIBOR 2022-03-08 3M = 0.01
LIBOR 2022-06-08 3M = 0.01
LIBOR 2022-09-08 3M = 0.01
LIBOR 2022-12-08 3M = 0.01
LIBOR 2023-03-08 3M = 0.01
LIBOR 2023-06-08 3M = 0.01
LIBOR 2023-09-07 3M = 0.01
LIBOR 2023-12-07 3M = 0.01
LIBOR 2024-03-07 3M = 0.01
LIBOR 2024-06-06 3M = 0.01
LIBOR 2024-09-06 3M = 0.01
LIBOR 2024-12-06 3M = 0.01
LIBOR 2025-03-06 3M = 0.01
LIBOR 2025-06-06 3M = 0.01
LIBOR 2025-09-08 3M = 0.01
LIBOR 2025-12-08 3M = 0.01
LIBOR 2026-03-06 3M = 0.01
LIBOR 2026-06-08 3M = 0.01
LIBOR 2026-09-08 3M = 0.01
LIBOR 2026-12-08 3M = 0.01

View File

@ -7,6 +7,7 @@ import core.crypto.SecureHash
import core.node.AbstractNode import core.node.AbstractNode
import core.protocols.ProtocolLogic import core.protocols.ProtocolLogic
import core.serialization.SerializedBytes import core.serialization.SerializedBytes
import core.utilities.ANSIProgressRenderer
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.* import java.util.*
import kotlin.reflect.KParameter import kotlin.reflect.KParameter
@ -87,6 +88,7 @@ class APIServerImpl(val node: AbstractNode): APIServer {
} }
// If we get here then we matched every parameter // If we get here then we matched every parameter
val protocol = constructor.callBy(params) as ProtocolLogic<*> val protocol = constructor.callBy(params) as ProtocolLogic<*>
ANSIProgressRenderer.progressTracker = protocol.progressTracker
val future = node.smm.add("api-call",protocol) val future = node.smm.add("api-call",protocol)
return future return future
} }

View File

@ -0,0 +1,33 @@
package api
import javax.ws.rs.container.ContainerRequestContext
import javax.ws.rs.container.ContainerResponseContext
import javax.ws.rs.container.ContainerResponseFilter
import javax.ws.rs.ext.Provider
/**
* This adds headers needed for cross site scripting on API clients
*/
@Provider
class ResponseFilter: ContainerResponseFilter {
override fun filter(requestContext: ContainerRequestContext, responseContext: ContainerResponseContext) {
val headers = responseContext.headers
/**
* TODO we need to revisit this for security reasons
*
* We don't want this scriptable from any web page anywhere, but for demo reasons
* we're making this really easy to access pending a proper security approach including
* access control and authentication at a network and software level
*
*/
headers.add("Access-Control-Allow-Origin","*")
if(requestContext.method == "OPTIONS") {
headers.add("Access-Control-Allow-Headers", "Content-Type,Accept,Origin")
headers.add("Access-Control-Allow-Methods", "POST,PUT,GET,OPTIONS")
}
}
}

View File

@ -26,6 +26,7 @@ data class LegallyIdentifiableNode(val address: SingleMessageRecipient, val iden
*/ */
interface NetworkMapService { interface NetworkMapService {
val timestampingNodes: List<LegallyIdentifiableNode> val timestampingNodes: List<LegallyIdentifiableNode>
val ratesOracleNodes: List<LegallyIdentifiableNode>
val partyNodes: List<LegallyIdentifiableNode> val partyNodes: List<LegallyIdentifiableNode>
fun nodeForPartyName(name: String): LegallyIdentifiableNode? = partyNodes.singleOrNull { it.identity.name == name } fun nodeForPartyName(name: String): LegallyIdentifiableNode? = partyNodes.singleOrNull { it.identity.name == name }
@ -37,6 +38,7 @@ class MockNetworkMapService : NetworkMapService {
data class MockAddress(val id: String): SingleMessageRecipient data class MockAddress(val id: String): SingleMessageRecipient
override val timestampingNodes = Collections.synchronizedList(ArrayList<LegallyIdentifiableNode>()) override val timestampingNodes = Collections.synchronizedList(ArrayList<LegallyIdentifiableNode>())
override val ratesOracleNodes = Collections.synchronizedList(ArrayList<LegallyIdentifiableNode>())
override val partyNodes = Collections.synchronizedList(ArrayList<LegallyIdentifiableNode>()) override val partyNodes = Collections.synchronizedList(ArrayList<LegallyIdentifiableNode>())
init { init {

View File

@ -168,10 +168,11 @@ class StateMachineManager(val serviceHub: ServiceHub, val runInThread: Executor)
fun <T> add(loggerName: String, logic: ProtocolLogic<T>): ListenableFuture<T> { fun <T> add(loggerName: String, logic: ProtocolLogic<T>): ListenableFuture<T> {
val logger = LoggerFactory.getLogger(loggerName) val logger = LoggerFactory.getLogger(loggerName)
val fiber = ProtocolStateMachine(logic) val fiber = ProtocolStateMachine(logic)
// Need to add before iterating in case of immediate completion
_stateMachines.add(logic)
iterateStateMachine(fiber, serviceHub.networkService, logger, null, null) { iterateStateMachine(fiber, serviceHub.networkService, logger, null, null) {
it.start() it.start()
} }
_stateMachines.add(logic)
totalStartedProtocols.inc() totalStartedProtocols.inc()
return fiber.resultFuture return fiber.resultFuture
} }

View File

@ -76,7 +76,8 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
CASH_PROGRAM_ID to Cash::class.java, CASH_PROGRAM_ID to Cash::class.java,
CP_PROGRAM_ID to CommercialPaper::class.java, CP_PROGRAM_ID to CommercialPaper::class.java,
CROWDFUND_PROGRAM_ID to CrowdFund::class.java, CROWDFUND_PROGRAM_ID to CrowdFund::class.java,
DUMMY_PROGRAM_ID to DummyContract::class.java DUMMY_PROGRAM_ID to DummyContract::class.java,
IRS_PROGRAM_ID to InterestRateSwap::class.java
) )
override fun <T : Contract> get(hash: SecureHash): T { override fun <T : Contract> get(hash: SecureHash): T {

View File

@ -9,6 +9,7 @@
package core.node package core.node
import api.Config import api.Config
import api.ResponseFilter
import com.codahale.metrics.JmxReporter import com.codahale.metrics.JmxReporter
import com.google.common.net.HostAndPort import com.google.common.net.HostAndPort
import core.messaging.LegallyIdentifiableNode import core.messaging.LegallyIdentifiableNode
@ -94,6 +95,7 @@ class Node(dir: Path, val p2pAddr: HostAndPort, configuration: NodeConfiguration
val resourceConfig = ResourceConfig() val resourceConfig = ResourceConfig()
// Add your API provider classes (annotated for JAX-RS) here // Add your API provider classes (annotated for JAX-RS) here
resourceConfig.register(Config(services)) resourceConfig.register(Config(services))
resourceConfig.register(ResponseFilter())
resourceConfig.register(api) resourceConfig.register(api)
// Give the app a slightly better name in JMX rather than a randomly generated one and enable JMX // Give the app a slightly better name in JMX rather than a randomly generated one and enable JMX
resourceConfig.addProperties(mapOf(ServerProperties.APPLICATION_NAME to "node.api", resourceConfig.addProperties(mapOf(ServerProperties.APPLICATION_NAME to "node.api",

View File

@ -135,9 +135,9 @@ class ArtemisMessagingService(val directory: Path, val myHostPort: HostAndPort)
session.createQueue(myHostPort.toString(), "inbound", false) session.createQueue(myHostPort.toString(), "inbound", false)
inboundConsumer = session.createConsumer("inbound").setMessageHandler { message: ClientMessage -> inboundConsumer = session.createConsumer("inbound").setMessageHandler { message: ClientMessage ->
// This code runs for every inbound message. // This code runs for every inbound message.
try {
if (!message.containsProperty(TOPIC_PROPERTY)) { if (!message.containsProperty(TOPIC_PROPERTY)) {
log.warn("Received message without a ${TOPIC_PROPERTY} property, ignoring") log.warn("Received message without a ${TOPIC_PROPERTY} property, ignoring")
// TODO: Figure out whether we always need to acknowledge messages, even when invalid.
return@setMessageHandler return@setMessageHandler
} }
val topic = message.getStringProperty(TOPIC_PROPERTY) val topic = message.getStringProperty(TOPIC_PROPERTY)
@ -171,8 +171,10 @@ class ArtemisMessagingService(val directory: Path, val myHostPort: HostAndPort)
} }
} }
} }
} finally {
message.acknowledge() message.acknowledge()
} }
}
session.start() session.start()
mutex.locked { running = true } mutex.locked { running = true }

View File

@ -0,0 +1,34 @@
package demos
import java.time.*
import javax.annotation.concurrent.ThreadSafe
/**
* A [Clock] that can have the date advanced for use in demos
*/
@ThreadSafe
class DemoClock(private var delegateClock: Clock = Clock.systemUTC()) : Clock() {
@Synchronized fun updateDate(date: LocalDate): Boolean {
val currentDate = LocalDate.now(this)
if(currentDate.isBefore(date)) {
// It's ok to increment
delegateClock = Clock.offset(delegateClock, Duration.between(currentDate.atStartOfDay(),date.atStartOfDay()))
return true
}
return false
}
@Synchronized override fun instant(): Instant {
return delegateClock.instant()
}
@Synchronized override fun withZone(zone: ZoneId): Clock {
return DemoClock(delegateClock.withZone(zone))
}
@Synchronized override fun getZone(): ZoneId {
return delegateClock.zone
}
}

View File

@ -50,10 +50,13 @@ open class RatesFixProtocol(protected val tx: TransactionBuilder,
@Suspendable @Suspendable
override fun call() { override fun call() {
progressTracker.currentStep = progressTracker.steps[0]
val fix = query() val fix = query()
progressTracker.currentStep = WORKING
checkFixIsNearExpected(fix) checkFixIsNearExpected(fix)
tx.addCommand(fix, oracle.identity.owningKey) tx.addCommand(fix, oracle.identity.owningKey)
beforeSigning(fix) beforeSigning(fix)
progressTracker.currentStep = SIGNING
tx.addSignatureUnchecked(sign()) tx.addSignatureUnchecked(sign())
} }

View File

@ -20,6 +20,7 @@ import core.node.services.TimestamperService
import core.protocols.ProtocolLogic import core.protocols.ProtocolLogic
import core.random63BitValue import core.random63BitValue
import core.serialization.SerializedBytes import core.serialization.SerializedBytes
import core.utilities.ProgressTracker
/** /**
* The TimestampingProtocol class is the client code that talks to a [NodeTimestamperService] on some remote node. It is a * The TimestampingProtocol class is the client code that talks to a [NodeTimestamperService] on some remote node. It is a
@ -31,7 +32,8 @@ import core.serialization.SerializedBytes
* a network message: use it only from spare application threads that don't have to respond to anything. * a network message: use it only from spare application threads that don't have to respond to anything.
*/ */
class TimestampingProtocol(private val node: LegallyIdentifiableNode, class TimestampingProtocol(private val node: LegallyIdentifiableNode,
private val wtxBytes: SerializedBytes<WireTransaction>) : ProtocolLogic<DigitalSignature.LegallyIdentifiable>() { private val wtxBytes: SerializedBytes<WireTransaction>,
override val progressTracker: ProgressTracker = TimestampingProtocol.tracker()) : ProtocolLogic<DigitalSignature.LegallyIdentifiable>() {
class Client(private val stateMachineManager: StateMachineManager, private val node: LegallyIdentifiableNode) : TimestamperService { class Client(private val stateMachineManager: StateMachineManager, private val node: LegallyIdentifiableNode) : TimestamperService {
override val identity: Party = node.identity override val identity: Party = node.identity
@ -41,9 +43,18 @@ class TimestampingProtocol(private val node: LegallyIdentifiableNode,
} }
} }
companion object {
object REQUESTING : ProgressTracker.Step("Requesting signature by timestamping service")
object VALIDATING : ProgressTracker.Step("Validating received signature from timestamping service")
fun tracker() = ProgressTracker(REQUESTING, VALIDATING)
}
@Suspendable @Suspendable
override fun call(): DigitalSignature.LegallyIdentifiable { override fun call(): DigitalSignature.LegallyIdentifiable {
progressTracker.currentStep = REQUESTING
val sessionID = random63BitValue() val sessionID = random63BitValue()
val replyTopic = "${NodeTimestamperService.TIMESTAMPING_PROTOCOL_TOPIC}.$sessionID" val replyTopic = "${NodeTimestamperService.TIMESTAMPING_PROTOCOL_TOPIC}.$sessionID"
val req = Request(wtxBytes, serviceHub.networkService.myAddress, replyTopic) val req = Request(wtxBytes, serviceHub.networkService.myAddress, replyTopic)
@ -52,6 +63,7 @@ class TimestampingProtocol(private val node: LegallyIdentifiableNode,
NodeTimestamperService.TIMESTAMPING_PROTOCOL_TOPIC, node.address, 0, sessionID, req) NodeTimestamperService.TIMESTAMPING_PROTOCOL_TOPIC, node.address, 0, sessionID, req)
// Check that the timestamping authority gave us back a valid signature and didn't break somehow // Check that the timestamping authority gave us back a valid signature and didn't break somehow
progressTracker.currentStep = VALIDATING
maybeSignature.validate { sig -> maybeSignature.validate { sig ->
sig.verifyWithECDSA(wtxBytes) sig.verifyWithECDSA(wtxBytes)
return sig return sig

View File

@ -74,11 +74,11 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
//expression = "( fixedLeg.notional * (fixedLeg.fixedRate)) - (floatingLeg.notional * (rateSchedule.get(context.getDate('currentDate'))))", //expression = "( fixedLeg.notional * (fixedLeg.fixedRate)) - (floatingLeg.notional * (rateSchedule.get(context.getDate('currentDate'))))",
// How it's ended up looking, which I think is now broken but it's a WIP. // How it's ended up looking, which I think is now broken but it's a WIP.
expression = InterestRateSwap.Expression("( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) -" + expression = Expression("( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) -" +
"(floatingLeg.notional.pennies * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))"), "(floatingLeg.notional.pennies * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))"),
floatingLegPaymentSchedule = HashMap(), floatingLegPaymentSchedule = HashMap(),
fixedLegpaymentSchedule = HashMap() fixedLegPaymentSchedule = HashMap()
) )
val EUR = currency("EUR") val EUR = currency("EUR")
@ -94,13 +94,13 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
valuationDate = "Every Local Business Day", valuationDate = "Every Local Business Day",
notificationTime = "2:00pm London", notificationTime = "2:00pm London",
resolutionTime = "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ", resolutionTime = "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ",
interestRate = OracleRetrievableReferenceRate(TelerateOracle("T3270"), Tenor("6M"), "EONIA"), interestRate = ReferenceRate("T3270", Tenor("6M"), "EONIA"),
addressForTransfers = "", addressForTransfers = "",
exposure = UnknownType(), exposure = UnknownType(),
localBusinessDay = BusinessCalendar.getInstance("London"), localBusinessDay = BusinessCalendar.getInstance("London"),
tradeID = "trade1", tradeID = "trade1",
hashLegalDocs = "put hash here", hashLegalDocs = "put hash here",
dailyInterestAmount = InterestRateSwap.Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360") dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360")
) )
InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common) InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common)
@ -165,11 +165,11 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
//expression = "( fixedLeg.notional * (fixedLeg.fixedRate)) - (floatingLeg.notional * (rateSchedule.get(context.getDate('currentDate'))))", //expression = "( fixedLeg.notional * (fixedLeg.fixedRate)) - (floatingLeg.notional * (rateSchedule.get(context.getDate('currentDate'))))",
// How it's ended up looking, which I think is now broken but it's a WIP. // How it's ended up looking, which I think is now broken but it's a WIP.
expression = InterestRateSwap.Expression("( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) -" + expression = Expression("( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) -" +
"(floatingLeg.notional.pennies * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))"), "(floatingLeg.notional.pennies * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))"),
floatingLegPaymentSchedule = HashMap(), floatingLegPaymentSchedule = HashMap(),
fixedLegpaymentSchedule = HashMap() fixedLegPaymentSchedule = HashMap()
) )
val EUR = currency("EUR") val EUR = currency("EUR")
@ -185,13 +185,13 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
valuationDate = "Every Local Business Day", valuationDate = "Every Local Business Day",
notificationTime = "2:00pm London", notificationTime = "2:00pm London",
resolutionTime = "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ", resolutionTime = "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ",
interestRate = OracleRetrievableReferenceRate(TelerateOracle("T3270"), Tenor("6M"), "EONIA"), interestRate = ReferenceRate("T3270", Tenor("6M"), "EONIA"),
addressForTransfers = "", addressForTransfers = "",
exposure = UnknownType(), exposure = UnknownType(),
localBusinessDay = BusinessCalendar.getInstance("London"), localBusinessDay = BusinessCalendar.getInstance("London"),
tradeID = "trade1", tradeID = "trade1",
hashLegalDocs = "put hash here", hashLegalDocs = "put hash here",
dailyInterestAmount = InterestRateSwap.Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360") dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360")
) )
return InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common) return InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common)
@ -335,12 +335,6 @@ class IRSTests {
// TODO: r1*r2 ? // TODO: r1*r2 ?
} }
@Test
fun `reference rate testing`() {
val r1 = TestReferenceRate("5")
assert(100 * r1.getAsOf(null) == 5)
}
@Test @Test
fun `expression calculation testing`() { fun `expression calculation testing`() {
val dummyIRS = singleIRS() val dummyIRS = singleIRS()
@ -366,7 +360,7 @@ class IRSTests {
for (i in stuffToPrint) { for (i in stuffToPrint) {
println(i) println(i)
var z = dummyIRS.evaluateCalculation(LocalDate.of(2016, 9, 12), InterestRateSwap.Expression(i)) var z = dummyIRS.evaluateCalculation(LocalDate.of(2016, 9, 12), Expression(i))
println(z.javaClass) println(z.javaClass)
println(z) println(z)
println("-----------") println("-----------")

View File

@ -38,6 +38,8 @@ object TestUtils {
val keypair = generateKeyPair() val keypair = generateKeyPair()
val keypair2 = generateKeyPair() val keypair2 = generateKeyPair()
val keypair3 = generateKeyPair() val keypair3 = generateKeyPair()
val keypair4 = generateKeyPair()
val keypair5 = generateKeyPair()
} }
// A dummy time at which we will be pretending test transactions are created. // A dummy time at which we will be pretending test transactions are created.
val TEST_TX_TIME = Instant.parse("2015-04-17T12:00:00.00Z") val TEST_TX_TIME = Instant.parse("2015-04-17T12:00:00.00Z")
@ -49,6 +51,14 @@ val MEGA_CORP_PUBKEY = MEGA_CORP_KEY.public
val MINI_CORP_KEY = TestUtils.keypair2 val MINI_CORP_KEY = TestUtils.keypair2
val MINI_CORP_PUBKEY = MINI_CORP_KEY.public val MINI_CORP_PUBKEY = MINI_CORP_KEY.public
// TODO remove once mock API is retired
val EXCALIBUR_BANK_KEY = TestUtils.keypair4
val EXCALIBUR_BANK_PUBKEY = EXCALIBUR_BANK_KEY.public
// TODO remove once mock API is retired
val A_N_OTHER_BANK_KEY = TestUtils.keypair5
val A_N_OTHER_BANK_PUBKEY = A_N_OTHER_BANK_KEY.public
val ORACLE_KEY = TestUtils.keypair3 val ORACLE_KEY = TestUtils.keypair3
val ORACLE_PUBKEY = ORACLE_KEY.public val ORACLE_PUBKEY = ORACLE_KEY.public
@ -64,11 +74,17 @@ val BOB = BOB_KEY.public
val MEGA_CORP = Party("MegaCorp", MEGA_CORP_PUBKEY) val MEGA_CORP = Party("MegaCorp", MEGA_CORP_PUBKEY)
val MINI_CORP = Party("MiniCorp", MINI_CORP_PUBKEY) val MINI_CORP = Party("MiniCorp", MINI_CORP_PUBKEY)
val ALL_TEST_KEYS = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, ALICE_KEY, BOB_KEY, DummyTimestampingAuthority.key) // TODO remove once mock API is retired
val EXCALIBUR_BANK = Party("Excalibur", EXCALIBUR_BANK_PUBKEY)
val A_N_OTHER_BANK = Party("ANOther",A_N_OTHER_BANK_PUBKEY)
val ALL_TEST_KEYS = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, ALICE_KEY, BOB_KEY, EXCALIBUR_BANK_KEY, A_N_OTHER_BANK_KEY, DummyTimestampingAuthority.key)
val TEST_KEYS_TO_CORP_MAP: Map<PublicKey, Party> = mapOf( val TEST_KEYS_TO_CORP_MAP: Map<PublicKey, Party> = mapOf(
MEGA_CORP_PUBKEY to MEGA_CORP, MEGA_CORP_PUBKEY to MEGA_CORP,
MINI_CORP_PUBKEY to MINI_CORP, MINI_CORP_PUBKEY to MINI_CORP,
EXCALIBUR_BANK_PUBKEY to EXCALIBUR_BANK,
A_N_OTHER_BANK_PUBKEY to A_N_OTHER_BANK,
DUMMY_TIMESTAMPER.identity.owningKey to DUMMY_TIMESTAMPER.identity DUMMY_TIMESTAMPER.identity.owningKey to DUMMY_TIMESTAMPER.identity
) )