mirror of
https://github.com/corda/corda.git
synced 2024-12-30 01:39:04 +00:00
Add simulation classes, which build on top of the MockNetwork infrastructure but set up a group of nodes and performs scenarios between them. Currently there's a base class and two subclasses, one that does the trader demo scenario and one that does an IRS with fixings scenario.
This commit is contained in:
parent
0ad6a30fee
commit
1633ff1ef7
@ -115,7 +115,17 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
|
||||
keyManagement = E2ETestKeyManagementService()
|
||||
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) {
|
||||
@ -126,14 +136,6 @@ 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
|
||||
}
|
||||
|
||||
lateinit var interestRatesService: NodeInterestRates.Service
|
||||
|
@ -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"))))
|
||||
|
141
src/main/kotlin/core/testing/IRSSimulation.kt
Normal file
141
src/main/kotlin/core/testing/IRSSimulation.kt
Normal 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()
|
||||
}
|
||||
}
|
@ -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 */
|
||||
|
213
src/main/kotlin/core/testing/Simulation.kt
Normal file
213
src/main/kotlin/core/testing/Simulation.kt
Normal 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)
|
||||
}
|
||||
}
|
60
src/main/kotlin/core/testing/TradeSimulation.kt
Normal file
60
src/main/kotlin/core/testing/TradeSimulation.kt
Normal 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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
51
src/main/resources/core/testing/example.rates.txt
Normal file
51
src/main/resources/core/testing/example.rates.txt
Normal 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
|
104
src/main/resources/core/testing/trade.json
Normal file
104
src/main/resources/core/testing/trade.json
Normal 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"
|
||||
}
|
Loading…
Reference in New Issue
Block a user