Merged in misc-simulation-improvements (pull request #53)

Misc simulation improvements
This commit is contained in:
Mike Hearn 2016-04-07 19:38:15 +02:00
commit 1da7b4bf01
23 changed files with 842 additions and 203 deletions

View File

@ -83,7 +83,7 @@ class CommercialPaper : Contract {
// Here, we match acceptable timestamp authorities by name. The list of acceptable TSAs (oracles) must be
// hard coded into the contract because otherwise we could fail to gain consensus, if nodes disagree about
// who or what is a trusted authority.
val timestamp: TimestampCommand? = tx.commands.getTimestampByName("Mock Company 0", "Bank of Zurich")
val timestamp: TimestampCommand? = tx.commands.getTimestampByName("Mock Company 0", "Bank A")
for (group in groups) {
when (command.value) {

View File

@ -52,12 +52,12 @@ abstract class PaymentEvent(date: LocalDate) : Event(date) {
* For the floating leg, the rate refers to a reference rate which is to be "fixed" at a point in the future.
*/
abstract class RatePaymentEvent(date: LocalDate,
val accrualStartDate: LocalDate,
val accrualEndDate: LocalDate,
val dayCountBasisDay: DayCountBasisDay,
val dayCountBasisYear: DayCountBasisYear,
val notional: Amount,
val rate: Rate) : PaymentEvent(date) {
val accrualStartDate: LocalDate,
val accrualEndDate: LocalDate,
val dayCountBasisDay: DayCountBasisDay,
val dayCountBasisYear: DayCountBasisYear,
val notional: Amount,
val rate: Rate) : PaymentEvent(date) {
companion object {
val CSVHeader = "AccrualStartDate,AccrualEndDate,DayCountFactor,Days,Date,Ccy,Notional,Rate,Flow"
}
@ -70,7 +70,7 @@ abstract class RatePaymentEvent(date: LocalDate,
dayCountCalculator(accrualStartDate, accrualEndDate, dayCountBasisYear, dayCountBasisDay)
val dayCountFactor: BigDecimal get() =
// TODO : Fix below (use daycount convention for division)
// TODO : Fix below (use daycount convention for division)
(BigDecimal(days).divide(BigDecimal(360.0), 8, RoundingMode.HALF_UP)).setScale(4, RoundingMode.HALF_UP)
open fun asCSV(): String = "$accrualStartDate,$accrualEndDate,$dayCountFactor,$days,$date,${notional.currency},${notional},$rate,$flow"
@ -137,12 +137,6 @@ class FloatingRatePaymentEvent(date: LocalDate,
}
/**
* Don't try and use a rate that isn't ready yet.
*/
class DataNotReadyException : Exception()
/**
* The Interest Rate Swap class. For a quick overview of what an IRS is, see here - http://www.pimco.co.uk/EN/Education/Pages/InterestRateSwapsBasics1-08.aspx (no endorsement)
* This contract has 4 significant data classes within it, the "Common", "Calculation", "FixedLeg" and "FloatingLeg"
@ -181,7 +175,7 @@ class InterestRateSwap() : Contract {
* data that will changed from state to state (Recall that the design insists that everything is immutable, so we actually
* copy / update for each transition)
*/
data class Calculation (
data class Calculation(
val expression: Expression,
val floatingLegPaymentSchedule: Map<LocalDate, FloatingRatePaymentEvent>,
val fixedLegPaymentSchedule: Map<LocalDate, FixedRatePaymentEvent>
@ -240,7 +234,7 @@ class InterestRateSwap() : Contract {
"PaymentRule=$paymentRule,PaymentDelay=$paymentDelay,PaymentCalendar=$paymentCalendar,InterestPeriodAdjustment=$interestPeriodAdjustment"
}
override fun equals(other: Any?): Boolean{
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
@ -263,7 +257,7 @@ class InterestRateSwap() : Contract {
return true
}
override fun hashCode(): Int{
override fun hashCode(): Int {
var result = notional.hashCode()
result += 31 * result + paymentFrequency.hashCode()
result += 31 * result + effectiveDate.hashCode()
@ -306,7 +300,7 @@ class InterestRateSwap() : Contract {
override fun toString(): String = "FixedLeg(Payer=$fixedRatePayer," + super.toString() + ",fixedRate=$fixedRate," +
"rollConvention=$rollConvention"
override fun equals(other: Any?): Boolean{
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
if (!super.equals(other)) return false
@ -320,7 +314,7 @@ class InterestRateSwap() : Contract {
return true
}
override fun hashCode(): Int{
override fun hashCode(): Int {
var result = super.hashCode()
result += 31 * result + fixedRatePayer.hashCode()
result += 31 * result + fixedRate.hashCode()
@ -362,7 +356,7 @@ class InterestRateSwap() : Contract {
"FixingPeriond=$fixingPeriod,ResetRule=$resetRule,FixingsPerPayment=$fixingsPerPayment,FixingCalendar=$fixingCalendar," +
"Index=$index,IndexSource=$indexSource,IndexTenor=$indexTenor"
override fun equals(other: Any?): Boolean{
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
if (!super.equals(other)) return false
@ -384,7 +378,7 @@ class InterestRateSwap() : Contract {
return true
}
override fun hashCode(): Int{
override fun hashCode(): Int {
var result = super.hashCode()
result += 31 * result + floatingRatePayer.hashCode()
result += 31 * result + rollConvention.hashCode()
@ -407,7 +401,7 @@ class InterestRateSwap() : Contract {
*/
override fun verify(tx: TransactionForVerification) {
val command = tx.commands.requireSingleCommand<InterestRateSwap.Commands>()
val time = tx.commands.getTimestampByName("Mock Company 0", "Bank A")?.midpoint
val time = tx.commands.getTimestampByName("Mock Company 0", "European Timestamping Service", "Bank A")?.midpoint
if (time == null) throw IllegalArgumentException("must be timestamped")
val irs = tx.outStates.filterIsInstance<InterestRateSwap.State>().single()
@ -473,11 +467,11 @@ class InterestRateSwap() : Contract {
override fun withPublicKey(before: Party, after: PublicKey): State {
val newParty = Party(before.name, after)
if(before == fixedLeg.fixedRatePayer) {
if (before == fixedLeg.fixedRatePayer) {
val deal = copy()
deal.fixedLeg.fixedRatePayer = newParty
return deal
} else if(before == floatingLeg.floatingRatePayer) {
} else if (before == floatingLeg.floatingRatePayer) {
val deal = copy()
deal.floatingLeg.floatingRatePayer = newParty
return deal
@ -494,7 +488,7 @@ class InterestRateSwap() : Contract {
override fun nextFixingOf(): FixOf? {
val date = calculation.nextFixingDate()
return if (date==null) null else {
return if (date == null) null else {
val fixingEvent = calculation.getFixing(date)
val oracleRate = fixingEvent.rate as ReferenceRate
FixOf(oracleRate.name, date, oracleRate.tenor)

View File

@ -18,4 +18,5 @@ import java.security.PublicKey
*/
interface IdentityService {
fun partyFromKey(key: PublicKey): Party?
fun partyFromName(name: String): Party?
}

View File

@ -42,13 +42,13 @@ import java.util.*
*/
class ProgressTracker(vararg steps: Step) {
sealed class Change {
class Position(val newStep: Step) : Change() {
class Position(val tracker: ProgressTracker, val newStep: Step) : Change() {
override fun toString() = newStep.label
}
class Rendering(val ofStep: Step) : Change() {
class Rendering(val tracker: ProgressTracker, val ofStep: Step) : Change() {
override fun toString() = ofStep.label
}
class Structural(val parent: Step) : Change() {
class Structural(val tracker: ProgressTracker, val parent: Step) : Change() {
override fun toString() = "Structural step change in child of ${parent.label}"
}
}
@ -59,13 +59,13 @@ class ProgressTracker(vararg steps: Step) {
}
/** This class makes it easier to relabel a step on the fly, to provide transient information. */
open class RelabelableStep(currentLabel: String) : Step(currentLabel) {
open inner class RelabelableStep(currentLabel: String) : Step(currentLabel) {
override val changes = BehaviorSubject.create<Change>()
var currentLabel: String = currentLabel
set(value) {
field = value
changes.onNext(ProgressTracker.Change.Rendering(this))
changes.onNext(ProgressTracker.Change.Rendering(this@ProgressTracker, this@RelabelableStep))
}
override val label: String get() = currentLabel
@ -109,7 +109,7 @@ class ProgressTracker(vararg steps: Step) {
curChangeSubscription?.unsubscribe()
stepIndex = index
_changes.onNext(Change.Position(steps[index]))
_changes.onNext(Change.Position(this, steps[index]))
curChangeSubscription = currentStep.changes.subscribe { _changes.onNext(it) }
if (currentStep == DONE) _changes.onCompleted()
@ -128,18 +128,33 @@ class ProgressTracker(vararg steps: Step) {
override fun put(key: Step, value: ProgressTracker): ProgressTracker? {
val r = super.put(key, value)
childSubscriptions[value] = value.changes.subscribe({ _changes.onNext(it) }, { _changes.onError(it) })
_changes.onNext(Change.Structural(key))
value.parent = this@ProgressTracker
_changes.onNext(Change.Structural(this@ProgressTracker, key))
return r
}
override fun remove(key: Step): ProgressTracker? {
if (containsKey(key))
childSubscriptions[this[key]]?.let { it.unsubscribe(); childSubscriptions.remove(this[key]) }
_changes.onNext(Change.Structural(key))
val tracker = this[key]
if (tracker != null) {
tracker.parent = null
childSubscriptions[tracker]?.let { it.unsubscribe(); childSubscriptions.remove(tracker) }
}
_changes.onNext(Change.Structural(this@ProgressTracker, key))
return super.remove(key)
}
}
/** The parent of this tracker: set automatically by the parent when a tracker is added as a child */
var parent: ProgressTracker? = null
/** Walks up the tree to find the top level tracker. If this is the top level tracker, returns 'this' */
val topLevelTracker: ProgressTracker
get() {
var cursor: ProgressTracker = this
while (cursor.parent != null) cursor = cursor.parent!!
return cursor
}
private val childSubscriptions = HashMap<ProgressTracker, Subscription>()
private fun _allSteps(level: Int = 0): List<Pair<Int, Step>> {

View File

@ -12,14 +12,14 @@ fi
if [[ "$mode" == "buyer" ]]; then
if [ ! -d buyer ]; then
mkdir buyer
echo "myLegalName = Bank of Zurich" >buyer/config
echo "myLegalName = Bank A" >buyer/config
fi
build/install/r3prototyping/bin/r3prototyping --dir=buyer --service-fake-trades --network-address=localhost
elif [[ "$mode" == "seller" ]]; then
if [ ! -d seller ]; then
mkdir seller
echo "myLegalName = Bank of London" >seller/config
echo "myLegalName = Bank B" >seller/config
fi
build/install/r3prototyping/bin/r3prototyping --dir=seller --fake-trade-with=localhost --network-address=localhost:31340 --timestamper-identity-file=buyer/identity-public --timestamper-address=localhost

View File

@ -1,21 +1,8 @@
package api
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers
import com.fasterxml.jackson.databind.deser.std.StringArrayDeserializer
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.module.kotlin.KotlinModule
import core.BusinessCalendar
import core.Party
import core.crypto.SecureHash
import com.fasterxml.jackson.databind.ObjectMapper
import core.node.services.ServiceHub
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalDateTime
import core.utilities.JsonSupport
import javax.ws.rs.ext.ContextResolver
import javax.ws.rs.ext.Provider
@ -25,119 +12,6 @@ import javax.ws.rs.ext.Provider
*/
@Provider
class Config(val services: ServiceHub): ContextResolver<ObjectMapper> {
val defaultObjectMapper = createDefaultMapper(services)
override fun getContext(type: java.lang.Class<*>): ObjectMapper {
return defaultObjectMapper
}
class ServiceHubObjectMapper(var serviceHub: ServiceHub): ObjectMapper() {
}
companion object {
private fun createDefaultMapper(services: ServiceHub): ObjectMapper {
val mapper = ServiceHubObjectMapper(services)
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
val timeModule = SimpleModule("java.time")
timeModule.addSerializer(LocalDate::class.java, ToStringSerializer)
timeModule.addDeserializer(LocalDate::class.java, LocalDateDeserializer)
timeModule.addKeyDeserializer(LocalDate::class.java, LocalDateKeyDeserializer)
timeModule.addSerializer(LocalDateTime::class.java, ToStringSerializer)
val cordaModule = SimpleModule("core")
cordaModule.addSerializer(Party::class.java, PartySerializer)
cordaModule.addDeserializer(Party::class.java, PartyDeserializer)
cordaModule.addSerializer(BigDecimal::class.java, ToStringSerializer)
cordaModule.addDeserializer(BigDecimal::class.java, NumberDeserializers.BigDecimalDeserializer())
cordaModule.addSerializer(SecureHash::class.java, SecureHashSerializer)
// It's slightly remarkable, but apparently Jackson works out that this is the only possibility
// for a SecureHash at the moment and tries to use SHA256 directly even though we only give it SecureHash
cordaModule.addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer())
cordaModule.addDeserializer(BusinessCalendar::class.java, CalendarDeserializer)
mapper.registerModule(timeModule)
mapper.registerModule(cordaModule)
mapper.registerModule(KotlinModule())
return mapper
}
}
object ToStringSerializer: JsonSerializer<Any>() {
override fun serialize(obj: Any, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.toString())
}
}
object LocalDateDeserializer: JsonDeserializer<LocalDate>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): LocalDate {
return try {
LocalDate.parse(parser.text)
} catch (e: Exception) {
throw JsonParseException("Invalid LocalDate ${parser.text}: ${e.message}", parser.currentLocation)
}
}
}
object LocalDateKeyDeserializer: KeyDeserializer() {
override fun deserializeKey(text: String, p1: DeserializationContext): Any? {
return LocalDate.parse(text)
}
}
object PartySerializer: JsonSerializer<Party>() {
override fun serialize(obj: Party, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.name)
}
}
object PartyDeserializer: JsonDeserializer<Party>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): Party {
if(parser.currentToken == JsonToken.FIELD_NAME) {
parser.nextToken()
}
val mapper = parser.codec as ServiceHubObjectMapper
// TODO this needs to use some industry identifier(s) not just these human readable names
val nodeForPartyName = mapper.serviceHub.networkMapCache.nodeForPartyName(parser.text) ?: throw JsonParseException("Could not find a Party with name: ${parser.text}", parser.currentLocation)
return nodeForPartyName.identity
}
}
object SecureHashSerializer: JsonSerializer<SecureHash>() {
override fun serialize(obj: SecureHash, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.toString())
}
}
/**
* Implemented as a class so that we can instantiate for T
*/
class SecureHashDeserializer<T : SecureHash>: JsonDeserializer<T>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): T {
if(parser.currentToken == JsonToken.FIELD_NAME) {
parser.nextToken()
}
return try {
return SecureHash.parse(parser.text) as T
} catch (e: Exception) {
throw JsonParseException("Invalid hash ${parser.text}: ${e.message}", parser.currentLocation)
}
}
}
object CalendarDeserializer: JsonDeserializer<BusinessCalendar>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): BusinessCalendar {
return try {
val array = StringArrayDeserializer.instance.deserialize(parser, context)
BusinessCalendar.getInstance(*array)
} catch (e: Exception) {
throw JsonParseException("Invalid calendar(s) ${parser.text}: ${e.message}", parser.currentLocation)
}
}
}
val defaultObjectMapper = JsonSupport.createDefaultMapper(services.identityService)
override fun getContext(type: java.lang.Class<*>) = defaultObjectMapper
}

View File

@ -80,6 +80,8 @@ class StateMachineManager(val serviceHub: ServiceHub, val runInThread: Executor)
// checkpointing when unit tests are run inside Gradle. The right fix is probably to stop Quasar's
// bit-too-clever-for-its-own-good ThreadLocal serialisation trick. It already wasted far more time than it can
// ever recover.
//
// TODO: Remove this now that TLS serialisation is fixed.
val checkpointing: Boolean get() = !System.err.javaClass.name.contains("LinePerThreadBufferingOutputStream")
/** Returns a list of all state machines executing the given protocol logic at the top level (subprotocols do not count) */

View File

@ -36,6 +36,7 @@ import java.nio.file.FileAlreadyExistsException
import java.nio.file.Files
import java.nio.file.Path
import java.security.KeyPair
import java.security.PublicKey
import java.time.Clock
import java.util.*
import java.util.concurrent.Executors
@ -112,9 +113,19 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
smm = StateMachineManager(services, serverThread)
wallet = NodeWalletService(services)
keyManagement = E2ETestKeyManagementService()
makeInterestRateOracleService()
makeInterestRatesOracleService()
api = APIServerImpl(this)
makeTimestampingService(timestamperAddress)
identity = makeIdentityService()
// This object doesn't need to be referenced from this class because it registers handlers on the network
// service and so that keeps it from being collected.
DataVendingService(net, storage)
return this
}
private fun makeTimestampingService(timestamperAddress: NodeInfo?) {
// Insert a network map entry for the timestamper: this is all temp scaffolding and will go away. If we are
// given the details, the timestamping node is somewhere else. Otherwise, we do our own timestamping.
val tsid = if (timestamperAddress != null) {
@ -125,30 +136,39 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
NodeInfo(net.myAddress, storage.myLegalIdentity)
}
(services.networkMapCache as MockNetworkMapCache).timestampingNodes.add(tsid)
identity = makeIdentityService()
// This object doesn't need to be referenced from this class because it registers handlers on the network
// service and so that keeps it from being collected.
DataVendingService(net, storage)
return this
}
protected fun makeInterestRateOracleService() {
// Constructing the service registers message handlers that ensure the service won't be garbage collected.
lateinit var interestRatesService: NodeInterestRates.Service
open protected fun makeInterestRatesOracleService() {
// TODO: Once the service has data, automatically register with the network map service (once built).
_servicesThatAcceptUploads += NodeInterestRates.Service(this)
interestRatesService = NodeInterestRates.Service(this)
_servicesThatAcceptUploads += interestRatesService
}
protected open fun makeIdentityService(): IdentityService {
// We don't have any identity infrastructure right now, so we just throw together the only two identities we
// know about: our own, and the identity of the remote timestamper node (if any).
val knownIdentities = if (timestamperAddress != null)
// We don't have any identity infrastructure right now, so we just throw together the only identities we
// know about: our own, the identity of the remote timestamper node (if any), plus whatever is in the
// network map.
//
// TODO: All this will be replaced soon enough.
val fixedIdentities = if (timestamperAddress != null)
listOf(storage.myLegalIdentity, timestamperAddress.identity)
else
listOf(storage.myLegalIdentity)
return FixedIdentityService(knownIdentities)
return object : IdentityService {
private val identities: List<Party> get() = fixedIdentities + services.networkMapCache.partyNodes.map { it.identity }
private val keyToParties: Map<PublicKey, Party> get() = identities.associateBy { it.owningKey }
private val nameToParties: Map<String, Party> get() = identities.associateBy { it.name }
override fun partyFromKey(key: PublicKey): Party? = keyToParties[key]
override fun partyFromName(name: String): Party? = nameToParties[name]
override fun toString(): String {
return identities.joinToString { it.name }
}
}
}
open fun stop() {

View File

@ -8,14 +8,23 @@
package core.node.services
import core.node.services.IdentityService
import core.Party
import java.security.PublicKey
import javax.annotation.concurrent.ThreadSafe
/**
* Scaffolding: a dummy identity service that just expects to have identities loaded off disk or found elsewhere.
* This class allows the provided list of identities to be mutated after construction, so it takes the list lock
* when doing lookups and recalculates the mapping each time. The ability to change the list is used by the
* MockNetwork code.
*/
class FixedIdentityService(private val identities: List<Party>) : IdentityService {
private val keyToParties: Map<PublicKey, Party> get() = identities.associateBy { it.owningKey }
@ThreadSafe
class FixedIdentityService(val identities: List<Party>) : IdentityService {
private val keyToParties: Map<PublicKey, Party>
get() = synchronized(identities) { identities.associateBy { it.owningKey } }
private val nameToParties: Map<String, Party>
get() = synchronized(identities) { identities.associateBy { it.name } }
override fun partyFromKey(key: PublicKey): Party? = keyToParties[key]
override fun partyFromName(name: String): Party? = nameToParties[name]
}

View File

@ -32,6 +32,7 @@ interface NetworkMapCache {
val timestampingNodes: List<NodeInfo>
val ratesOracleNodes: List<NodeInfo>
val partyNodes: List<NodeInfo>
val regulators: List<NodeInfo>
fun nodeForPartyName(name: String): NodeInfo? = partyNodes.singleOrNull { it.identity.name == name }
}
@ -43,6 +44,7 @@ class MockNetworkMapCache : NetworkMapCache {
override val timestampingNodes = Collections.synchronizedList(ArrayList<NodeInfo>())
override val ratesOracleNodes = Collections.synchronizedList(ArrayList<NodeInfo>())
override val partyNodes = Collections.synchronizedList(ArrayList<NodeInfo>())
override val regulators = Collections.synchronizedList(ArrayList<NodeInfo>())
init {
partyNodes.add(NodeInfo(MockAddress("bankC:8080"), Party("Bank C", DummyPublicKey("Bank C"))))

View File

@ -95,6 +95,18 @@ interface WalletService {
fun notify(tx: WireTransaction): Wallet = notifyAll(listOf(tx))
}
// TODO: Document this
@Suppress("UNCHECKED_CAST")
inline fun <reified T : LinearState> WalletService.linearHeadsOfType(): Map<SecureHash, StateAndRef<T>> {
return linearHeads.mapNotNull {
val s = it.value.state
if (s is T)
Pair(it.key, it.value as StateAndRef<T>)
else
null
}.toMap()
}
/**
* The KMS is responsible for storing and using private keys to sign things. An implementation of this may, for example,
* call out to a hardware security module that enforces various auditing and frequency-of-use requirements.

View File

@ -0,0 +1,141 @@
/*
* Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members
* pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms
* set forth therein.
*
* All other rights reserved.
*/
package core.testing
import com.fasterxml.jackson.module.kotlin.readValue
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import contracts.InterestRateSwap
import core.*
import core.crypto.SecureHash
import core.node.services.FixedIdentityService
import core.node.services.linearHeadsOfType
import core.utilities.JsonSupport
import protocols.TwoPartyDealProtocol
import java.time.LocalDate
import java.util.*
/**
* A simulation in which banks execute interest rate swaps with each other, including the fixing events.
*/
class IRSSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwork.LatencyCalculator?) : Simulation(runAsync, latencyInjector) {
val om = JsonSupport.createDefaultMapper(FixedIdentityService(network.identities))
init {
currentDay = LocalDate.of(2016, 3, 10) // Should be 12th but the actual first fixing date gets rolled backwards.
}
private val executeOnNextIteration = Collections.synchronizedList(LinkedList<() -> Unit>())
override fun start() {
startIRSDealBetween(0, 1).success {
// Next iteration is a pause.
executeOnNextIteration.add {}
executeOnNextIteration.add {
// Keep fixing until there's no more left to do.
doNextFixing(0, 1)?.addListener(object : Runnable {
override fun run() {
// Pause for an iteration.
executeOnNextIteration.add {}
executeOnNextIteration.add {
doNextFixing(0, 1)?.addListener(this, RunOnCallerThread)
}
}
}, RunOnCallerThread)
}
}
}
private fun doNextFixing(i: Int, j: Int): ListenableFuture<*>? {
println("Doing a fixing between $i and $j")
val node1: SimulatedNode = banks[i]
val node2: SimulatedNode = banks[j]
val sessionID = random63BitValue()
val swaps: Map<SecureHash, StateAndRef<InterestRateSwap.State>> = node1.services.walletService.linearHeadsOfType<InterestRateSwap.State>()
val theDealRef: StateAndRef<InterestRateSwap.State> = swaps.values.single()
// Do we have any more days left in this deal's lifetime? If not, return.
val nextFixingDate = theDealRef.state.calculation.nextFixingDate() ?: return null
extraNodeLabels[node1] = "Fixing event on $nextFixingDate"
extraNodeLabels[node2] = "Fixing event on $nextFixingDate"
// For some reason the first fix is always before the effective date.
if (nextFixingDate > currentDay)
currentDay = nextFixingDate
val sideA = TwoPartyDealProtocol.Floater(node2.net.myAddress, sessionID, timestamper.info,
theDealRef, node1.services.keyManagementService.freshKey(), sessionID)
val sideB = TwoPartyDealProtocol.Fixer(node1.net.myAddress, timestamper.info.identity,
theDealRef, sessionID)
linkConsensus(listOf(node1, node2, regulators[0]), sideB)
linkProtocolProgress(node1, sideA)
linkProtocolProgress(node2, sideB)
// We have to start the protocols in separate iterations, as adding to the SMM effectively 'iterates' that node
// in the simulation, so if we don't do this then the two sides seem to act simultaneously.
val retFuture = SettableFuture.create<Any>()
val futA = node1.smm.add("floater", sideA)
executeOnNextIteration += {
val futB = node2.smm.add("fixer", sideB)
Futures.allAsList(futA, futB).then {
retFuture.set(null)
}
}
return retFuture
}
private fun startIRSDealBetween(i: Int, j: Int): ListenableFuture<SignedTransaction> {
val node1: SimulatedNode = banks[i]
val node2: SimulatedNode = banks[j]
extraNodeLabels[node1] = "Setting up deal"
extraNodeLabels[node2] = "Setting up deal"
// We load the IRS afresh each time because the leg parts of the structure aren't data classes so they don't
// have the convenient copy() method that'd let us make small adjustments. Instead they're partly mutable.
// TODO: We should revisit this in post-Excalibur cleanup and fix, e.g. by introducing an interface.
val irs = om.readValue<InterestRateSwap.State>(javaClass.getResource("trade.json"))
irs.fixedLeg.fixedRatePayer = node1.info.identity
irs.floatingLeg.floatingRatePayer = node2.info.identity
if (irs.fixedLeg.effectiveDate < irs.floatingLeg.effectiveDate)
currentDay = irs.fixedLeg.effectiveDate
else
currentDay = irs.floatingLeg.effectiveDate
val sessionID = random63BitValue()
val instigator = TwoPartyDealProtocol.Instigator(node2.net.myAddress, timestamper.info,
irs, node1.services.keyManagementService.freshKey(), sessionID)
val acceptor = TwoPartyDealProtocol.Acceptor(node1.net.myAddress, timestamper.info.identity,
irs, sessionID)
// TODO: Eliminate the need for linkProtocolProgress
linkConsensus(listOf(node1, node2, regulators[0]), acceptor)
linkProtocolProgress(node1, instigator)
linkProtocolProgress(node2, acceptor)
val instigatorFuture: ListenableFuture<SignedTransaction> = node1.smm.add("instigator", instigator)
return Futures.transformAsync(Futures.allAsList(instigatorFuture, node2.smm.add("acceptor", acceptor))) {
instigatorFuture
}
}
override fun iterate() {
if (executeOnNextIteration.isNotEmpty())
executeOnNextIteration.removeAt(0)()
super.iterate()
}
}

View File

@ -93,6 +93,8 @@ class MockNetwork(private val threadPerNode: Boolean = false,
mockNet.identities.add(storage.myLegalIdentity)
return this
}
val place: PhysicalLocation get() = info.physicalLocation!!
}
/** Returns a started node, optionally created by the passed factory method */

View File

@ -0,0 +1,213 @@
/*
* Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members
* pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms
* set forth therein.
*
* All other rights reserved.
*/
package core.testing
import com.google.common.util.concurrent.ListenableFuture
import core.node.NodeConfiguration
import core.node.services.CityDatabase
import core.node.services.MockNetworkMapCache
import core.node.services.NodeInfo
import core.node.services.PhysicalLocation
import core.protocols.ProtocolLogic
import core.then
import core.utilities.ProgressTracker
import rx.Observable
import rx.subjects.PublishSubject
import java.nio.file.Path
import java.time.LocalDate
import java.util.*
/**
* Base class for network simulations that are based on the unit test / mock environment.
*
* Sets up some nodes that can run protocols between each other, and exposes their progress trackers. Provides banks
* in a few cities around the world.
*/
abstract class Simulation(val runAsync: Boolean,
val latencyInjector: InMemoryMessagingNetwork.LatencyCalculator?) {
init {
if (!runAsync && latencyInjector != null)
throw IllegalArgumentException("The latency injector is only useful when using manual pumping.")
}
val bankLocations = listOf("London", "Frankfurt", "Rome")
// This puts together a mock network of SimulatedNodes.
open class SimulatedNode(dir: Path, config: NodeConfiguration, mockNet: MockNetwork,
withTimestamper: NodeInfo?) : MockNetwork.MockNode(dir, config, mockNet, withTimestamper) {
override fun findMyLocation(): PhysicalLocation? = CityDatabase[configuration.nearestCity]
}
inner class BankFactory : MockNetwork.Factory {
var counter = 0
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode {
val letter = 'A' + counter
val city = bankLocations[counter++ % bankLocations.size]
val cfg = object : NodeConfiguration {
// TODO: Set this back to "Bank of $city" after video day.
override val myLegalName: String = "Bank $letter"
override val exportJMXto: String = ""
override val nearestCity: String = city
}
val node = SimulatedNode(dir, cfg, network, timestamperAddr)
// TODO: This is obviously bogus: there should be a single network map for the whole simulated network.
(node.services.networkMapCache as MockNetworkMapCache).ratesOracleNodes += ratesOracle.info
(node.services.networkMapCache as MockNetworkMapCache).regulators += regulators.map { it.info }
return node
}
fun createAll(): List<SimulatedNode> = bankLocations.map { network.createNode(timestamper.info, nodeFactory = this) as SimulatedNode }
}
val bankFactory = BankFactory()
object TimestampingNodeFactory : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode {
val cfg = object : NodeConfiguration {
override val myLegalName: String = "Timestamping Service" // A magic string recognised by the CP contract
override val exportJMXto: String = ""
override val nearestCity: String = "Zurich"
}
return SimulatedNode(dir, cfg, network, timestamperAddr)
}
}
object RatesOracleFactory : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode {
val cfg = object : NodeConfiguration {
override val myLegalName: String = "Rates Service Provider"
override val exportJMXto: String = ""
override val nearestCity: String = "Madrid"
}
val n = object : SimulatedNode(dir, cfg, network, timestamperAddr) {
override fun makeInterestRatesOracleService() {
super.makeInterestRatesOracleService()
interestRatesService.upload(javaClass.getResourceAsStream("example.rates.txt"))
(services.networkMapCache as MockNetworkMapCache).ratesOracleNodes += info
}
}
return n
}
}
object RegulatorFactory : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode {
val cfg = object : NodeConfiguration {
override val myLegalName: String = "Regulator A"
override val exportJMXto: String = ""
override val nearestCity: String = "Paris"
}
val n = object : SimulatedNode(dir, cfg, network, timestamperAddr) {
// TODO: Regulatory nodes don't actually exist properly, this is a last minute demo request.
// So we just fire a message at a node that doesn't know how to handle it, and it'll ignore it.
// But that's fine for visualisation purposes.
}
return n
}
}
val network = MockNetwork(false)
val timestamper: SimulatedNode = network.createNode(null, nodeFactory = TimestampingNodeFactory) as SimulatedNode
val ratesOracle: SimulatedNode = network.createNode(null, nodeFactory = RatesOracleFactory) as SimulatedNode
val serviceProviders: List<SimulatedNode> = listOf(timestamper, ratesOracle)
val banks: List<SimulatedNode> = bankFactory.createAll()
val regulators: List<SimulatedNode> = listOf(network.createNode(null, nodeFactory = RegulatorFactory) as SimulatedNode)
private val _allProtocolSteps = PublishSubject.create<Pair<SimulatedNode, ProgressTracker.Change>>()
private val _doneSteps = PublishSubject.create<Collection<SimulatedNode>>()
val allProtocolSteps: Observable<Pair<SimulatedNode, ProgressTracker.Change>> = _allProtocolSteps
val doneSteps: Observable<Collection<SimulatedNode>> = _doneSteps
private var pumpCursor = 0
/**
* The current simulated date. By default this never changes. If you want it to change, you should do so from
* within your overridden [iterate] call. Changes in the current day surface in the [dateChanges] observable.
*/
var currentDay: LocalDate = LocalDate.now()
protected set(value) {
field = value
_dateChanges.onNext(value)
}
private val _dateChanges = PublishSubject.create<LocalDate>()
val dateChanges: Observable<LocalDate> = _dateChanges
/**
* A place for simulations to stash human meaningful text about what the node is "thinking", which might appear
* in the UI somewhere.
*/
val extraNodeLabels = Collections.synchronizedMap(HashMap<SimulatedNode, String>())
/**
* Iterates the simulation by one step.
*
* The default implementation circles around the nodes, pumping until one of them handles a message. The next call
* will carry on from where this one stopped. In an environment where you want to take actions between anything
* interesting happening, or control the precise speed at which things operate (beyond the latency injector), this
* is a useful way to do things.
*/
open fun iterate() {
// Keep going until one of the nodes has something to do, or we have checked every node.
val endpoints = network.messagingNetwork.endpoints
var countDown = endpoints.size
while (countDown > 0) {
val handledMessage = endpoints[pumpCursor].pump(false)
if (handledMessage) break
// If this node had nothing to do, advance the cursor with wraparound and try again.
pumpCursor = (pumpCursor + 1) % endpoints.size
countDown--
}
}
protected fun linkProtocolProgress(node: SimulatedNode, protocol: ProtocolLogic<*>) {
val pt = protocol.progressTracker ?: return
pt.changes.subscribe { change: ProgressTracker.Change ->
// Runs on node thread.
_allProtocolSteps.onNext(Pair(node, change))
}
// This isn't technically a "change" but it helps with UIs to send this notification of the first step.
_allProtocolSteps.onNext(Pair(node, ProgressTracker.Change.Position(pt, pt.steps[1])))
}
protected fun linkConsensus(nodes: Collection<SimulatedNode>, protocol: ProtocolLogic<*>) {
protocol.progressTracker?.changes?.subscribe { change: ProgressTracker.Change ->
// Runs on node thread.
if (protocol.progressTracker!!.currentStep == ProgressTracker.DONE) {
_doneSteps.onNext(nodes)
}
}
}
open fun start() {}
fun stop() {
network.nodes.forEach { it.stop() }
}
/**
* Given a function that returns a future, iterates that function with arguments like (0, 1), (1, 2), (2, 3) etc
* each time the returned future completes.
*/
fun startTradingCircle(tradeBetween: (indexA: Int, indexB: Int) -> ListenableFuture<*>) {
fun next(i: Int, j: Int) {
tradeBetween(i, j).then {
val ni = (i + 1) % banks.size
val nj = (j + 1) % banks.size
next(ni, nj)
}
}
next(0, 1)
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members
* pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms
* set forth therein.
*
* All other rights reserved.
*/
package core.testing
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import contracts.CommercialPaper
import core.*
import core.node.services.NodeWalletService
import core.utilities.BriefLogFormatter
import protocols.TwoPartyTradeProtocol
import java.time.Instant
/**
* Simulates a never ending series of trades that go pair-wise through the banks (e.g. A and B trade with each other,
* then B and C trade with each other, then C and A etc).
*/
class TradeSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwork.LatencyCalculator?) : Simulation(runAsync, latencyInjector) {
override fun start() {
BriefLogFormatter.loggingOn("bank", "core.TransactionGroup", "recordingmap")
startTradingCircle { i, j -> tradeBetween(i, j) }
}
private fun tradeBetween(buyerBankIndex: Int, sellerBankIndex: Int): ListenableFuture<MutableList<SignedTransaction>> {
val buyer = banks[buyerBankIndex]
val seller = banks[sellerBankIndex]
(buyer.services.walletService as NodeWalletService).fillWithSomeTestCash(1500.DOLLARS)
val issuance = run {
val tx = CommercialPaper().generateIssue(seller.info.identity.ref(1, 2, 3), 1100.DOLLARS, Instant.now() + 10.days)
tx.setTime(Instant.now(), timestamper.info.identity, 30.seconds)
tx.signWith(timestamper.storage.myLegalIdentityKey)
tx.signWith(seller.storage.myLegalIdentityKey)
tx.toSignedTransaction(true)
}
seller.services.storageService.validatedTransactions[issuance.id] = issuance
val sessionID = random63BitValue()
val buyerProtocol = TwoPartyTradeProtocol.Buyer(seller.net.myAddress, timestamper.info.identity,
1000.DOLLARS, CommercialPaper.State::class.java, sessionID)
val sellerProtocol = TwoPartyTradeProtocol.Seller(buyer.net.myAddress, timestamper.info,
issuance.tx.outRef(0), 1000.DOLLARS, seller.storage.myLegalIdentityKey, sessionID)
linkConsensus(listOf(buyer, seller, timestamper), sellerProtocol)
linkProtocolProgress(buyer, buyerProtocol)
linkProtocolProgress(seller, sellerProtocol)
val buyerFuture = buyer.smm.add("bank.$buyerBankIndex.${TwoPartyTradeProtocol.TRADE_TOPIC}.buyer", buyerProtocol)
val sellerFuture = seller.smm.add("bank.$sellerBankIndex.${TwoPartyTradeProtocol.TRADE_TOPIC}.seller", sellerProtocol)
return Futures.successfulAsList(buyerFuture, sellerFuture)
}
}

View File

@ -0,0 +1,137 @@
/*
* Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members
* pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms
* set forth therein.
*
* All other rights reserved.
*/
package core.utilities
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers
import com.fasterxml.jackson.databind.deser.std.StringArrayDeserializer
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.module.kotlin.KotlinModule
import core.BusinessCalendar
import core.Party
import core.crypto.SecureHash
import core.node.services.IdentityService
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalDateTime
/**
* Utilities and serialisers for working with JSON representations of basic types. This adds Jackson support for
* the java.time API, some core types, and Kotlin data classes.
*/
object JsonSupport {
fun createDefaultMapper(identities: IdentityService): ObjectMapper {
val mapper = ServiceHubObjectMapper(identities)
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
val timeModule = SimpleModule("java.time")
timeModule.addSerializer(LocalDate::class.java, ToStringSerializer)
timeModule.addDeserializer(LocalDate::class.java, LocalDateDeserializer)
timeModule.addKeyDeserializer(LocalDate::class.java, LocalDateKeyDeserializer)
timeModule.addSerializer(LocalDateTime::class.java, ToStringSerializer)
val cordaModule = SimpleModule("core")
cordaModule.addSerializer(Party::class.java, PartySerializer)
cordaModule.addDeserializer(Party::class.java, PartyDeserializer)
cordaModule.addSerializer(BigDecimal::class.java, ToStringSerializer)
cordaModule.addDeserializer(BigDecimal::class.java, NumberDeserializers.BigDecimalDeserializer())
cordaModule.addSerializer(SecureHash::class.java, SecureHashSerializer)
// It's slightly remarkable, but apparently Jackson works out that this is the only possibility
// for a SecureHash at the moment and tries to use SHA256 directly even though we only give it SecureHash
cordaModule.addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer())
cordaModule.addDeserializer(BusinessCalendar::class.java, CalendarDeserializer)
mapper.registerModule(timeModule)
mapper.registerModule(cordaModule)
mapper.registerModule(KotlinModule())
return mapper
}
class ServiceHubObjectMapper(val identities: IdentityService): ObjectMapper()
object ToStringSerializer: JsonSerializer<Any>() {
override fun serialize(obj: Any, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.toString())
}
}
object LocalDateDeserializer: JsonDeserializer<LocalDate>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): LocalDate {
return try {
LocalDate.parse(parser.text)
} catch (e: Exception) {
throw JsonParseException("Invalid LocalDate ${parser.text}: ${e.message}", parser.currentLocation)
}
}
}
object LocalDateKeyDeserializer: KeyDeserializer() {
override fun deserializeKey(text: String, p1: DeserializationContext): Any? {
return LocalDate.parse(text)
}
}
object PartySerializer: JsonSerializer<Party>() {
override fun serialize(obj: Party, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.name)
}
}
object PartyDeserializer: JsonDeserializer<Party>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): Party {
if(parser.currentToken == JsonToken.FIELD_NAME) {
parser.nextToken()
}
val mapper = parser.codec as ServiceHubObjectMapper
// TODO this needs to use some industry identifier(s) not just these human readable names
return mapper.identities.partyFromName(parser.text) ?: throw JsonParseException("Could not find a Party with name: ${parser.text}", parser.currentLocation)
}
}
object SecureHashSerializer: JsonSerializer<SecureHash>() {
override fun serialize(obj: SecureHash, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.toString())
}
}
/**
* Implemented as a class so that we can instantiate for T
*/
class SecureHashDeserializer<T : SecureHash>: JsonDeserializer<T>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): T {
if(parser.currentToken == JsonToken.FIELD_NAME) {
parser.nextToken()
}
try {
@Suppress("UNCHECKED_CAST")
return SecureHash.parse(parser.text) as T
} catch (e: Exception) {
throw JsonParseException("Invalid hash ${parser.text}: ${e.message}", parser.currentLocation)
}
}
}
object CalendarDeserializer: JsonDeserializer<BusinessCalendar>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): BusinessCalendar {
return try {
val array = StringArrayDeserializer.instance.deserialize(parser, context)
BusinessCalendar.getInstance(*array)
} catch (e: Exception) {
throw JsonParseException("Invalid calendar(s) ${parser.text}: ${e.message}", parser.currentLocation)
}
}
}
}

View File

@ -73,8 +73,11 @@ object TwoPartyDealProtocol {
object TIMESTAMPING : ProgressTracker.Step("Timestamping transaction")
object SENDING_SIGS : ProgressTracker.Step("Sending transaction signatures to other party")
object RECORDING : ProgressTracker.Step("Recording completed transaction")
object COPYING_TO_REGULATOR : ProgressTracker.Step("Copying regulator")
fun tracker() = ProgressTracker(AWAITING_PROPOSAL, VERIFYING, SIGNING, TIMESTAMPING, SENDING_SIGS, RECORDING)
fun tracker() = ProgressTracker(AWAITING_PROPOSAL, VERIFYING, SIGNING, TIMESTAMPING, SENDING_SIGS, RECORDING, COPYING_TO_REGULATOR).apply {
childrenFor[TIMESTAMPING] = TimestampingProtocol.tracker()
}
}
@Suspendable
@ -149,6 +152,15 @@ object TwoPartyDealProtocol {
logger.trace { "Deal stored" }
val regulators = serviceHub.networkMapCache.regulators
if (regulators.isNotEmpty()) {
// Copy the transaction to every regulator in the network. This is obviously completely bogus, it's
// just for demo purposes.
for (regulator in regulators) {
send("regulator.all.seeing.eye", regulator.address, 0, fullySigned)
}
}
return fullySigned
}

View File

@ -0,0 +1,51 @@
# Some pretend noddy rate fixes, for the interest rate oracles.
3M USD 2016-03-16 1M = 0.678
3M USD 2016-03-16 2M = 0.655
EURIBOR 2016-03-15 1M = 0.123
EURIBOR 2016-03-15 2M = 0.111
3M USD 2016-03-08 3M = 0.0063515
3M USD 2016-06-08 3M = 0.0063520
3M USD 2016-09-08 3M = 0.0063521
3M USD 2016-12-08 3M = 0.0063515
3M USD 2017-03-08 3M = 0.0063525
3M USD 2017-06-08 3M = 0.0063530
3M USD 2017-09-07 3M = 0.0063531
3M USD 2017-12-07 3M = 0.0063532
3M USD 2018-03-08 3M = 0.0063533
3M USD 2018-06-07 3M = 0.0063534
3M USD 2018-09-06 3M = 0.0063535
3M USD 2018-12-06 3M = 0.0063536
3M USD 2019-03-07 3M = 0.0063537
3M USD 2019-06-06 3M = 0.0063538
3M USD 2019-09-06 3M = 0.0063539
3M USD 2019-12-06 3M = 0.0063540
3M USD 2020-03-06 3M = 0.0063541
3M USD 2020-06-08 3M = 0.0063542
3M USD 2020-09-08 3M = 0.0063543
3M USD 2020-12-08 3M = 0.0063544
3M USD 2021-03-08 3M = 0.0063545
3M USD 2021-06-08 3M = 0.0063546
3M USD 2021-09-08 3M = 0.0063547
3M USD 2021-12-08 3M = 0.0063548
3M USD 2022-03-08 3M = 0.0063549
3M USD 2022-06-08 3M = 0.0063550
3M USD 2022-09-08 3M = 0.0063551
3M USD 2022-12-08 3M = 0.0063553
3M USD 2023-03-08 3M = 0.0063554
3M USD 2023-06-08 3M = 0.0063555
3M USD 2023-09-07 3M = 0.0063556
3M USD 2023-12-07 3M = 0.0063557
3M USD 2024-03-07 3M = 0.0063558
3M USD 2024-06-06 3M = 0.0063559
3M USD 2024-09-06 3M = 0.0063560
3M USD 2024-12-06 3M = 0.0063561
3M USD 2025-03-06 3M = 0.0063562
3M USD 2025-06-06 3M = 0.0063563
3M USD 2025-09-08 3M = 0.0063564
3M USD 2025-12-08 3M = 0.0063565
3M USD 2026-03-06 3M = 0.0063566
3M USD 2026-06-08 3M = 0.0063567
3M USD 2026-09-08 3M = 0.0063568
3M USD 2026-12-08 3M = 0.0063569

View File

@ -0,0 +1,104 @@
{
"fixedLeg": {
"fixedRatePayer": "Bank A",
"notional": {
"pennies": 2500000000,
"currency": "USD"
},
"paymentFrequency": "SemiAnnual",
"effectiveDate": "2016-03-16",
"effectiveDateAdjustment": null,
"terminationDate": "2026-03-16",
"terminationDateAdjustment": null,
"fixedRate": {
"ratioUnit": {
"value": "0.01676"
}
},
"dayCountBasisDay": "D30",
"dayCountBasisYear": "Y360",
"rollConvention": "ModifiedFollowing",
"dayInMonth": 10,
"paymentRule": "InArrears",
"paymentDelay": 0,
"paymentCalendar": "London",
"interestPeriodAdjustment": "Adjusted"
},
"floatingLeg": {
"floatingRatePayer": "Bank B",
"notional": {
"pennies": 2500000000,
"currency": "USD"
},
"paymentFrequency": "Quarterly",
"effectiveDate": "2016-03-12",
"effectiveDateAdjustment": null,
"terminationDate": "2026-03-12",
"terminationDateAdjustment": null,
"dayCountBasisDay": "D30",
"dayCountBasisYear": "Y360",
"rollConvention": "ModifiedFollowing",
"fixingRollConvention": "ModifiedFollowing",
"dayInMonth": 10,
"resetDayInMonth": 10,
"paymentRule": "InArrears",
"paymentDelay": 0,
"paymentCalendar": [ "London" ],
"interestPeriodAdjustment": "Adjusted",
"fixingPeriod": "TWODAYS",
"resetRule": "InAdvance",
"fixingsPerPayment": "Quarterly",
"fixingCalendar": [ "NewYork" ],
"index": "3M USD",
"indexSource": "Rates Service Provider",
"indexTenor": {
"name": "3M"
}
},
"calculation": {
"expression": "( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) -(floatingLeg.notional.pennies * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))",
"floatingLegPaymentSchedule": {
},
"fixedLegPaymentSchedule": {
}
},
"common": {
"baseCurrency": "EUR",
"eligibleCurrency": "EUR",
"eligibleCreditSupport": "Cash in an Eligible Currency",
"independentAmounts": {
"pennies": 0,
"currency": "EUR"
},
"threshold": {
"pennies": 0,
"currency": "EUR"
},
"minimumTransferAmount": {
"pennies": 25000000,
"currency": "EUR"
},
"rounding": {
"pennies": 1000000,
"currency": "EUR"
},
"valuationDate": "Every Local Business Day",
"notificationTime": "2:00pm London",
"resolutionTime": "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ",
"interestRate": {
"oracle": "Rates Service Provider",
"tenor": {
"name": "6M"
},
"ratioUnit": null,
"name": "EONIA"
},
"addressForTransfers": "",
"exposure": {},
"localBusinessDay": [ "London" , "NewYork" ],
"dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360",
"tradeID": "tradeXXX",
"hashLegalDocs": "put hash here"
},
"programRef": "1E6BBA305D445341F0026E51B6C7F3ACB834AFC6C2510C0EF7BC0477235EFECF"
}

View File

@ -14,7 +14,7 @@ import core.messaging.MessagingService
import core.node.services.*
import core.serialization.SerializedBytes
import core.serialization.deserialize
import core.testutils.TEST_KEYS_TO_CORP_MAP
import core.testutils.MockIdentityService
import core.testutils.TEST_PROGRAM_MAP
import core.testutils.TEST_TX_TIME
import java.io.ByteArrayInputStream
@ -50,10 +50,6 @@ class DummyTimestamper(var clock: Clock = Clock.fixed(TEST_TX_TIME, ZoneId.syste
val DUMMY_TIMESTAMPER = DummyTimestamper()
object MockIdentityService : IdentityService {
override fun partyFromKey(key: PublicKey): Party? = TEST_KEYS_TO_CORP_MAP[key]
}
class MockKeyManagementService(vararg initialKeys: KeyPair) : KeyManagementService {
override val keys: MutableMap<PublicKey, PrivateKey>

View File

@ -48,7 +48,7 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() {
@Before
fun before() {
net = MockNetwork(false)
net.identities += TEST_KEYS_TO_CORP_MAP.values
net.identities += MockIdentityService.identities
BriefLogFormatter.loggingOn("platform.trade", "core.TransactionGroup", "recordingmap")
}

View File

@ -11,10 +11,7 @@ package core.serialization
import contracts.Cash
import core.*
import core.crypto.SecureHash
import core.testutils.DUMMY_PUBKEY_1
import core.testutils.MINI_CORP
import core.testutils.TEST_TX_TIME
import core.testutils.TestUtils
import core.testutils.*
import org.junit.Before
import org.junit.Test
import java.security.SignatureException

View File

@ -15,6 +15,7 @@ import contracts.*
import core.*
import core.crypto.*
import core.node.services.DummyTimestampingAuthority
import core.node.services.FixedIdentityService
import core.serialization.serialize
import core.visualiser.GraphVisualiser
import java.security.KeyPair
@ -66,11 +67,7 @@ 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)
val TEST_KEYS_TO_CORP_MAP: Map<PublicKey, Party> = mapOf(
MEGA_CORP_PUBKEY to MEGA_CORP,
MINI_CORP_PUBKEY to MINI_CORP,
DUMMY_TIMESTAMPER.identity.owningKey to DUMMY_TIMESTAMPER.identity
)
val MockIdentityService = FixedIdentityService(listOf(MEGA_CORP, MINI_CORP, DUMMY_TIMESTAMPER.identity))
// In a real system this would be a persistent map of hash to bytecode and we'd instantiate the object as needed inside
// a sandbox. For unit tests we just have a hard-coded list.
@ -128,7 +125,7 @@ abstract class AbstractTransactionForTest {
open fun output(label: String? = null, s: () -> ContractState) = LabeledOutput(label, s()).apply { outStates.add(this) }
protected fun commandsToAuthenticatedObjects(): List<AuthenticatedObject<CommandData>> {
return commands.map { AuthenticatedObject(it.pubkeys, it.pubkeys.mapNotNull { TEST_KEYS_TO_CORP_MAP[it] }, it.data) }
return commands.map { AuthenticatedObject(it.pubkeys, it.pubkeys.mapNotNull { MockIdentityService.partyFromKey(it) }, it.data) }
}
fun attachment(attachmentID: SecureHash) {