mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
In preparation for the removal of advertised services, @CordaService no longer expects a static "type" field for the ServiceType.
Instead @CordaServices will use the main identity of the node.
This commit is contained in:
parent
ea61e6e9d5
commit
ed0aede1f1
@ -1,19 +1,20 @@
|
||||
package net.corda.core.node.services
|
||||
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import kotlin.annotation.AnnotationTarget.CLASS
|
||||
|
||||
/**
|
||||
* Annotate any class that needs to be a long-lived service within the node, such as an oracle, with this annotation.
|
||||
* Such a class needs to have a constructor with a single parameter of type [net.corda.core.node.PluginServiceHub]. This
|
||||
* construtor will be invoked during node start to initialise the service. The service hub provided can be used to get
|
||||
* information about the node that may be necessary for the service. Corda services are created as singletons within
|
||||
* the node and are available to flows via [net.corda.core.node.ServiceHub.cordaService].
|
||||
* Such a class needs to have a constructor with a single parameter of type [PluginServiceHub]. This construtor will be
|
||||
* invoked during node start to initialise the service. The service hub provided can be used to get information about the
|
||||
* node that may be necessary for the service. Corda services are created as singletons within the node and are available
|
||||
* to flows via [ServiceHub.cordaService].
|
||||
*
|
||||
* The service class has to implement [net.corda.core.serialization.SerializeAsToken] to ensure correct usage within flows.
|
||||
* (If possible extend [net.corda.core.serialization.SingletonSerializeAsToken] instead as it removes the boilerplate.)
|
||||
*
|
||||
* The annotated class should expose its [ServiceType] via a public static field named `type`, so that the service is
|
||||
* only loaded in nodes that declare the type in their advertisedServices.
|
||||
* The service class has to implement [SerializeAsToken] to ensure correct usage within flows. (If possible extend
|
||||
* [SingletonSerializeAsToken] instead as it removes the boilerplate.)
|
||||
*/
|
||||
// TODO Handle the singleton serialisation of Corda services automatically, removing the need to implement SerializeAsToken
|
||||
// TODO Perhaps this should be an interface or abstract class due to the need for it to implement SerializeAsToken and
|
||||
|
@ -10,16 +10,11 @@ import net.corda.core.node.services.TimeWindowChecker
|
||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.node.services.transactions.PersistentUniquenessProvider
|
||||
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||
import java.security.SignatureException
|
||||
|
||||
// START 1
|
||||
@CordaService
|
||||
class MyCustomValidatingNotaryService(override val services: PluginServiceHub) : TrustedAuthorityNotaryService() {
|
||||
companion object {
|
||||
val type = ValidatingNotaryService.type.getSubType("mycustom")
|
||||
}
|
||||
|
||||
override val timeWindowChecker = TimeWindowChecker(services.clock)
|
||||
override val uniquenessProvider = PersistentUniquenessProvider()
|
||||
|
||||
|
@ -13,7 +13,6 @@ import net.corda.core.contracts.LinearState
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.TokenizableAssetInfo
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.finance.contracts.asset.CommodityContract
|
||||
@ -416,7 +415,7 @@ interface FixableDealState : DealState {
|
||||
/**
|
||||
* What oracle service to use for the fixing
|
||||
*/
|
||||
val oracleType: ServiceType
|
||||
val oracle: Party
|
||||
|
||||
/**
|
||||
* Generate a fixing command for this deal and fix.
|
||||
|
@ -31,8 +31,8 @@ import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.cert
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.node.internal.cordapp.CordappLoader
|
||||
import net.corda.node.internal.classloading.requireAnnotation
|
||||
import net.corda.node.internal.cordapp.CordappLoader
|
||||
import net.corda.node.services.NotaryChangeHandler
|
||||
import net.corda.node.services.NotifyTransactionHandler
|
||||
import net.corda.node.services.SwapIdentitiesHandler
|
||||
@ -135,9 +135,6 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
lateinit var database: CordaPersistence
|
||||
protected var dbCloser: (() -> Any?)? = null
|
||||
|
||||
var isPreviousCheckpointsPresent = false
|
||||
private set
|
||||
|
||||
protected val _nodeReadyFuture = openFuture<Unit>()
|
||||
/** Completes once the node has successfully registered with the network map service
|
||||
* or has loaded network map data from local database */
|
||||
@ -149,7 +146,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
CordaX500Name.build(cert.subject).copy(commonName = null)
|
||||
}
|
||||
|
||||
val cordappLoader: CordappLoader by lazy {
|
||||
private val cordappLoader: CordappLoader by lazy {
|
||||
val scanPackage = System.getProperty("net.corda.node.cordapp.scan.package")
|
||||
if (scanPackage != null) {
|
||||
check(configuration.devMode) { "Package scanning can only occur in dev mode" }
|
||||
@ -205,10 +202,6 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
|
||||
makeVaultObservers()
|
||||
|
||||
checkpointStorage.forEach {
|
||||
isPreviousCheckpointsPresent = true
|
||||
false
|
||||
}
|
||||
startMessagingService(rpcOps)
|
||||
installCoreFlows()
|
||||
|
||||
@ -233,7 +226,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
private class ServiceInstantiationException(cause: Throwable?) : Exception(cause)
|
||||
|
||||
private fun installCordaServices() {
|
||||
cordappLoader.cordapps.flatMap { it.filterEnabledServices(info) }.map {
|
||||
cordappLoader.cordapps.flatMap { it.services }.forEach {
|
||||
try {
|
||||
installCordaService(it)
|
||||
} catch (e: NoSuchMethodException) {
|
||||
@ -486,8 +479,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
val address: SingleMessageRecipient = networkMapAddress ?:
|
||||
network.getAddressOfParty(PartyInfo.Node(info)) as SingleMessageRecipient
|
||||
// Register for updates, even if we're the one running the network map.
|
||||
return sendNetworkMapRegistration(address).flatMap { response: RegistrationResponse ->
|
||||
check(response.error == null) { "Unable to register with the network map service: ${response.error}" }
|
||||
return sendNetworkMapRegistration(address).flatMap { (error) ->
|
||||
check(error == null) { "Unable to register with the network map service: $error" }
|
||||
// The future returned addMapService will complete on the same executor as sendNetworkMapRegistration, namely the one used by net
|
||||
services.networkMapCache.addMapService(network, address, true, null)
|
||||
}
|
||||
|
@ -2,12 +2,8 @@ package net.corda.node.internal.cordapp
|
||||
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.node.CordaPluginRegistry
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import java.net.URL
|
||||
|
||||
/**
|
||||
@ -27,32 +23,4 @@ data class Cordapp(
|
||||
val services: List<Class<out SerializeAsToken>>,
|
||||
val plugins: List<CordaPluginRegistry>,
|
||||
val customSchemas: Set<MappedSchema>,
|
||||
val jarPath: URL) {
|
||||
companion object {
|
||||
private val logger = loggerFor<Cordapp>()
|
||||
}
|
||||
|
||||
fun filterEnabledServices(info: NodeInfo): List<Class<out SerializeAsToken>> {
|
||||
return services.filter {
|
||||
val serviceType = getServiceType(it)
|
||||
if (serviceType != null && info.serviceIdentities(serviceType).isEmpty()) {
|
||||
logger.debug {
|
||||
"Ignoring ${it.name} as a Corda service since $serviceType is not one of our " +
|
||||
"advertised services"
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getServiceType(clazz: Class<*>): ServiceType? {
|
||||
return try {
|
||||
clazz.getField("type").get(null) as ServiceType
|
||||
} catch (e: NoSuchFieldException) {
|
||||
logger.warn("${clazz.name} does not have a type field, optimistically proceeding with install.")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
val jarPath: URL)
|
@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.core.contracts.UniqueIdentifier
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.messaging.vaultTrackBy
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.toFuture
|
||||
@ -18,7 +19,6 @@ import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.finance.plugin.registerFinanceJSONMappers
|
||||
import net.corda.irs.api.NodeInterestRates
|
||||
import net.corda.irs.contract.InterestRateSwap
|
||||
import net.corda.irs.utilities.uploadFile
|
||||
import net.corda.node.services.config.FullNodeConfiguration
|
||||
@ -44,20 +44,20 @@ class IRSDemoTest : IntegrationTestCategory {
|
||||
val log = loggerFor<IRSDemoTest>()
|
||||
}
|
||||
|
||||
val rpcUser = User("user", "password", emptySet())
|
||||
val currentDate: LocalDate = LocalDate.now()
|
||||
val futureDate: LocalDate = currentDate.plusMonths(6)
|
||||
val maxWaitTime: Duration = 60.seconds
|
||||
private val rpcUser = User("user", "password", emptySet())
|
||||
private val currentDate: LocalDate = LocalDate.now()
|
||||
private val futureDate: LocalDate = currentDate.plusMonths(6)
|
||||
private val maxWaitTime: Duration = 60.seconds
|
||||
|
||||
@Test
|
||||
fun `runs IRS demo`() {
|
||||
driver(useTestClock = true, isDebug = true) {
|
||||
val controllerFuture = startNode(
|
||||
providedName = DUMMY_NOTARY.name,
|
||||
advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type), ServiceInfo(NodeInterestRates.Oracle.type)))
|
||||
val nodeAFuture = startNode(providedName = DUMMY_BANK_A.name, rpcUsers = listOf(rpcUser))
|
||||
val nodeBFuture = startNode(providedName = DUMMY_BANK_B.name)
|
||||
val (controller, nodeA, nodeB) = listOf(controllerFuture, nodeAFuture, nodeBFuture).map { it.getOrThrow() }
|
||||
val (controller, nodeA, nodeB) = listOf(
|
||||
startNode(
|
||||
providedName = DUMMY_NOTARY.name,
|
||||
advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type))),
|
||||
startNode(providedName = DUMMY_BANK_A.name, rpcUsers = listOf(rpcUser)),
|
||||
startNode(providedName = DUMMY_BANK_B.name)).map { it.getOrThrow() }
|
||||
|
||||
log.info("All nodes started")
|
||||
|
||||
@ -80,7 +80,7 @@ class IRSDemoTest : IntegrationTestCategory {
|
||||
val numBDeals = getTradeCount(nodeBApi)
|
||||
|
||||
runUploadRates(controllerAddr)
|
||||
runTrade(nodeAApi)
|
||||
runTrade(nodeAApi, controller.nodeInfo.legalIdentity)
|
||||
|
||||
assertThat(getTradeCount(nodeAApi)).isEqualTo(numADeals + 1)
|
||||
assertThat(getTradeCount(nodeBApi)).isEqualTo(numBDeals + 1)
|
||||
@ -95,9 +95,11 @@ class IRSDemoTest : IntegrationTestCategory {
|
||||
}
|
||||
}
|
||||
|
||||
fun getFloatingLegFixCount(nodeApi: HttpApi) = getTrades(nodeApi)[0].calculation.floatingLegPaymentSchedule.count { it.value.rate.ratioUnit != null }
|
||||
private fun getFloatingLegFixCount(nodeApi: HttpApi): Int {
|
||||
return getTrades(nodeApi)[0].calculation.floatingLegPaymentSchedule.count { it.value.rate.ratioUnit != null }
|
||||
}
|
||||
|
||||
fun getFixingDateObservable(config: FullNodeConfiguration): Observable<LocalDate?> {
|
||||
private fun getFixingDateObservable(config: FullNodeConfiguration): Observable<LocalDate?> {
|
||||
val client = CordaRPCClient(config.rpcAddress!!, initialiseSerialization = false)
|
||||
val proxy = client.start("user", "password").proxy
|
||||
val vaultUpdates = proxy.vaultTrackBy<InterestRateSwap.State>().updates
|
||||
@ -113,10 +115,10 @@ class IRSDemoTest : IntegrationTestCategory {
|
||||
assertThat(nodeApi.putJson("demodate", "\"$futureDate\"")).isTrue()
|
||||
}
|
||||
|
||||
private fun runTrade(nodeApi: HttpApi) {
|
||||
private fun runTrade(nodeApi: HttpApi, oracle: Party) {
|
||||
log.info("Running trade against ${nodeApi.root}")
|
||||
val fileContents = loadResourceFile("net/corda/irs/simulation/example-irs-trade.json")
|
||||
val tradeFile = fileContents.replace("tradeXXX", "trade1")
|
||||
val tradeFile = fileContents.replace("tradeXXX", "trade1").replace("oracleXXX", oracle.name.toString())
|
||||
assertThat(nodeApi.postJson("deals", tradeFile)).isTrue()
|
||||
}
|
||||
|
||||
@ -139,11 +141,10 @@ class IRSDemoTest : IntegrationTestCategory {
|
||||
|
||||
private fun getTrades(nodeApi: HttpApi): Array<InterestRateSwap.State> {
|
||||
log.info("Getting trades from ${nodeApi.root}")
|
||||
val deals = nodeApi.getJson<Array<InterestRateSwap.State>>("deals")
|
||||
return deals
|
||||
return nodeApi.getJson("deals")
|
||||
}
|
||||
|
||||
fun <T> Observable<T>.firstWithTimeout(timeout: Duration, pred: (T) -> Boolean) {
|
||||
private fun <T> Observable<T>.firstWithTimeout(timeout: Duration, pred: (T) -> Boolean) {
|
||||
first(pred).toFuture().getOrThrow(timeout)
|
||||
}
|
||||
|
||||
@ -163,7 +164,8 @@ class IRSDemoTest : IntegrationTestCategory {
|
||||
val calculation: InterestRateSwap.Calculation = mapper.readValue(node.get("calculation").toString())
|
||||
val common: InterestRateSwap.Common = mapper.readValue(node.get("common").toString())
|
||||
val linearId: UniqueIdentifier = mapper.readValue(node.get("linearId").toString())
|
||||
InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common, linearId = linearId)
|
||||
val oracle: Party = mapper.readValue(node.get("oracle").toString())
|
||||
InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common, linearId = linearId, oracle = oracle)
|
||||
} catch (e: Exception) {
|
||||
throw JsonParseException(parser, "Invalid interest rate swap state(s) ${parser.text}: ${e.message}")
|
||||
}
|
||||
|
@ -2,7 +2,8 @@ package net.corda.irs.api
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.MerkleTreeException
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.flows.FlowException
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatedBy
|
||||
@ -10,9 +11,7 @@ import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.ThreadBox
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.services.CordaService
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.transactions.FilteredTransaction
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
@ -27,7 +26,6 @@ import net.corda.finance.contracts.math.InterpolatorFactory
|
||||
import net.corda.irs.flows.RatesFixFlow
|
||||
import org.apache.commons.io.IOUtils
|
||||
import java.math.BigDecimal
|
||||
import java.security.PublicKey
|
||||
import java.time.LocalDate
|
||||
import java.util.*
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
@ -48,7 +46,7 @@ import kotlin.collections.set
|
||||
object NodeInterestRates {
|
||||
// DOCSTART 2
|
||||
@InitiatedBy(RatesFixFlow.FixSignFlow::class)
|
||||
class FixSignHandler(val otherParty: Party) : FlowLogic<Unit>() {
|
||||
class FixSignHandler(private val otherParty: Party) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
val request = receive<RatesFixFlow.SignRequest>(otherParty).unwrap { it }
|
||||
@ -58,14 +56,14 @@ object NodeInterestRates {
|
||||
}
|
||||
|
||||
@InitiatedBy(RatesFixFlow.FixQueryFlow::class)
|
||||
class FixQueryHandler(val otherParty: Party) : FlowLogic<Unit>() {
|
||||
class FixQueryHandler(private val otherParty: Party) : FlowLogic<Unit>() {
|
||||
object RECEIVED : ProgressTracker.Step("Received fix request")
|
||||
object SENDING : ProgressTracker.Step("Sending fix response")
|
||||
|
||||
override val progressTracker = ProgressTracker(RECEIVED, SENDING)
|
||||
|
||||
@Suspendable
|
||||
override fun call(): Unit {
|
||||
override fun call() {
|
||||
val request = receive<RatesFixFlow.QueryRequest>(otherParty).unwrap { it }
|
||||
progressTracker.currentStep = RECEIVED
|
||||
val oracle = serviceHub.cordaService(Oracle::class.java)
|
||||
@ -84,12 +82,10 @@ object NodeInterestRates {
|
||||
@ThreadSafe
|
||||
// DOCSTART 3
|
||||
@CordaService
|
||||
class Oracle(val identity: Party, private val signingKey: PublicKey, val services: ServiceHub) : SingletonSerializeAsToken() {
|
||||
constructor(services: PluginServiceHub) : this(
|
||||
services.myInfo.serviceIdentities(type).first(),
|
||||
services.myInfo.serviceIdentities(type).first().owningKey.keys.first { services.keyManagementService.keys.contains(it) },
|
||||
services
|
||||
) {
|
||||
class Oracle(private val services: PluginServiceHub) : SingletonSerializeAsToken() {
|
||||
private val mutex = ThreadBox(InnerState())
|
||||
|
||||
init {
|
||||
// Set some default fixes to the Oracle, so we can smoothly run the IRS Demo without uploading fixes.
|
||||
// This is required to avoid a situation where the runnodes version of the demo isn't in a good state
|
||||
// upon startup.
|
||||
@ -97,19 +93,12 @@ object NodeInterestRates {
|
||||
}
|
||||
// DOCEND 3
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val type = ServiceType.corda.getSubType("interest_rates")
|
||||
}
|
||||
|
||||
private class InnerState {
|
||||
// TODO Update this to use a database once we have an database API
|
||||
val fixes = HashSet<Fix>()
|
||||
var container: FixContainer = FixContainer(fixes)
|
||||
}
|
||||
|
||||
private val mutex = ThreadBox(InnerState())
|
||||
|
||||
var knownFixes: FixContainer
|
||||
set(value) {
|
||||
require(value.size > 0)
|
||||
@ -121,11 +110,6 @@ object NodeInterestRates {
|
||||
}
|
||||
get() = mutex.locked { container }
|
||||
|
||||
// Make this the last bit of initialisation logic so fully constructed when entered into instances map
|
||||
init {
|
||||
require(signingKey in identity.owningKey.keys)
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
fun query(queries: List<FixOf>): List<Fix> {
|
||||
require(queries.isNotEmpty())
|
||||
@ -150,8 +134,9 @@ object NodeInterestRates {
|
||||
}
|
||||
// Performing validation of obtained FilteredLeaves.
|
||||
fun commandValidator(elem: Command<*>): Boolean {
|
||||
if (!(identity.owningKey in elem.signers && elem.value is Fix))
|
||||
throw IllegalArgumentException("Oracle received unknown command (not in signers or not Fix).")
|
||||
require(services.myInfo.legalIdentity.owningKey in elem.signers && elem.value is Fix) {
|
||||
"Oracle received unknown command (not in signers or not Fix)."
|
||||
}
|
||||
val fix = elem.value as Fix
|
||||
val known = knownFixes[fix.of]
|
||||
if (known == null || known != fix)
|
||||
@ -167,15 +152,14 @@ object NodeInterestRates {
|
||||
}
|
||||
|
||||
val leaves = ftx.filteredLeaves
|
||||
if (!leaves.checkWithFun(::check))
|
||||
throw IllegalArgumentException()
|
||||
require(leaves.checkWithFun(::check))
|
||||
|
||||
// It all checks out, so we can return a signature.
|
||||
//
|
||||
// Note that we will happily sign an invalid transaction, as we are only being presented with a filtered
|
||||
// version so we can't resolve or check it ourselves. However, that doesn't matter much, as if we sign
|
||||
// an invalid transaction the signature is worthless.
|
||||
return services.createSignature(ftx, signingKey)
|
||||
return services.createSignature(ftx, services.myInfo.legalIdentity.owningKey)
|
||||
}
|
||||
// DOCEND 1
|
||||
|
||||
@ -212,7 +196,7 @@ object NodeInterestRates {
|
||||
private fun buildContainer(fixes: Set<Fix>): Map<Pair<String, LocalDate>, InterpolatingRateMap> {
|
||||
val tempContainer = HashMap<Pair<String, LocalDate>, HashMap<Tenor, BigDecimal>>()
|
||||
for ((fixOf, value) in fixes) {
|
||||
val rates = tempContainer.getOrPut(fixOf.name to fixOf.forDay) { HashMap<Tenor, BigDecimal>() }
|
||||
val rates = tempContainer.getOrPut(fixOf.name to fixOf.forDay) { HashMap() }
|
||||
rates[fixOf.ofTenor] = value
|
||||
}
|
||||
|
||||
|
@ -5,12 +5,10 @@ import net.corda.core.contracts.*
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.finance.contracts.*
|
||||
import net.corda.irs.api.NodeInterestRates
|
||||
import net.corda.irs.flows.FixingFlow
|
||||
import net.corda.irs.utilities.suggestInterestRateAnnouncementTimeWindow
|
||||
import org.apache.commons.jexl3.JexlBuilder
|
||||
@ -603,11 +601,9 @@ class InterestRateSwap : Contract {
|
||||
val floatingLeg: FloatingLeg,
|
||||
val calculation: Calculation,
|
||||
val common: Common,
|
||||
override val oracle: Party,
|
||||
override val linearId: UniqueIdentifier = UniqueIdentifier(common.tradeID)
|
||||
) : FixableDealState, SchedulableState {
|
||||
override val oracleType: ServiceType
|
||||
get() = NodeInterestRates.Oracle.type
|
||||
|
||||
val ref: String get() = linearId.externalId ?: ""
|
||||
|
||||
override val participants: List<AbstractParty>
|
||||
@ -621,7 +617,9 @@ class InterestRateSwap : Contract {
|
||||
return ScheduledActivity(flowLogicRefFactory.create(FixingFlow.FixingRoleDecider::class.java, thisStateRef), instant)
|
||||
}
|
||||
|
||||
override fun generateAgreement(notary: Party): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common, notary)
|
||||
override fun generateAgreement(notary: Party): TransactionBuilder {
|
||||
return InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common, oracle, notary)
|
||||
}
|
||||
|
||||
override fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix) {
|
||||
InterestRateSwap().generateFix(ptx, StateAndRef(TransactionState(this, IRS_PROGRAM_ID, oldState.state.notary), oldState.ref), fix)
|
||||
@ -665,10 +663,14 @@ class InterestRateSwap : Contract {
|
||||
* Note: The day count, interest rate calculation etc are not finished yet, but they are demonstrable.
|
||||
*/
|
||||
fun generateAgreement(floatingLeg: FloatingLeg, fixedLeg: FixedLeg, calculation: Calculation,
|
||||
common: Common, notary: Party): TransactionBuilder {
|
||||
|
||||
common: Common, oracle: Party, notary: Party): TransactionBuilder {
|
||||
val fixedLegPaymentSchedule = LinkedHashMap<LocalDate, FixedRatePaymentEvent>()
|
||||
var dates = BusinessCalendar.createGenericSchedule(fixedLeg.effectiveDate, fixedLeg.paymentFrequency, fixedLeg.paymentCalendar, fixedLeg.rollConvention, endDate = fixedLeg.terminationDate)
|
||||
var dates = BusinessCalendar.createGenericSchedule(
|
||||
fixedLeg.effectiveDate,
|
||||
fixedLeg.paymentFrequency,
|
||||
fixedLeg.paymentCalendar,
|
||||
fixedLeg.rollConvention,
|
||||
endDate = fixedLeg.terminationDate)
|
||||
var periodStartDate = fixedLeg.effectiveDate
|
||||
|
||||
// Create a schedule for the fixed payments
|
||||
@ -717,8 +719,11 @@ class InterestRateSwap : Contract {
|
||||
val newCalculation = Calculation(calculation.expression, floatingLegPaymentSchedule, fixedLegPaymentSchedule)
|
||||
|
||||
// Put all the above into a new State object.
|
||||
val state = State(fixedLeg, floatingLeg, newCalculation, common)
|
||||
return TransactionBuilder(notary).withItems(StateAndContract(state, IRS_PROGRAM_ID), Command(Commands.Agree(), listOf(state.floatingLeg.floatingRatePayer.owningKey, state.fixedLeg.fixedRatePayer.owningKey)))
|
||||
val state = State(fixedLeg, floatingLeg, newCalculation, common, oracle)
|
||||
return TransactionBuilder(notary).withItems(
|
||||
StateAndContract(state, IRS_PROGRAM_ID),
|
||||
Command(Commands.Agree(), listOf(state.floatingLeg.floatingRatePayer.owningKey, state.fixedLeg.fixedRatePayer.owningKey))
|
||||
)
|
||||
}
|
||||
|
||||
private fun calcFixingDate(date: LocalDate, fixingPeriodOffset: Int, calendar: BusinessCalendar): LocalDate {
|
||||
|
@ -3,22 +3,16 @@ package net.corda.irs.flows
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatedBy
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.SchedulableFlow
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.core.utilities.transient
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.finance.contracts.Fix
|
||||
import net.corda.finance.contracts.FixableDealState
|
||||
import net.corda.finance.flows.TwoPartyDealFlow
|
||||
@ -65,11 +59,8 @@ object FixingFlow {
|
||||
|
||||
val ptx = TransactionBuilder(txState.notary)
|
||||
|
||||
val oracle = serviceHub.networkMapCache.getNodesWithService(handshake.payload.oracleType).first()
|
||||
val oracleParty = oracle.serviceIdentities(handshake.payload.oracleType).first()
|
||||
|
||||
// DOCSTART 1
|
||||
val addFixing = object : RatesFixFlow(ptx, oracleParty, fixOf, BigDecimal.ZERO, BigDecimal.ONE) {
|
||||
val addFixing = object : RatesFixFlow(ptx, handshake.payload.oracle, fixOf, BigDecimal.ZERO, BigDecimal.ONE) {
|
||||
@Suspendable
|
||||
override fun beforeSigning(fix: Fix) {
|
||||
newDeal.generateFix(ptx, StateAndRef(txState, handshake.payload.ref), fix)
|
||||
@ -82,7 +73,7 @@ object FixingFlow {
|
||||
@Suspendable
|
||||
override fun filtering(elem: Any): Boolean {
|
||||
return when (elem) {
|
||||
is Command<*> -> oracleParty.owningKey in elem.signers && elem.value is Fix
|
||||
is Command<*> -> handshake.payload.oracle.owningKey in elem.signers && elem.value is Fix
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@ -104,7 +95,7 @@ object FixingFlow {
|
||||
override val payload: FixingSession,
|
||||
override val progressTracker: ProgressTracker = TwoPartyDealFlow.Primary.tracker()) : TwoPartyDealFlow.Primary() {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal val dealToFix: StateAndRef<FixableDealState> by transient {
|
||||
private val dealToFix: StateAndRef<FixableDealState> by transient {
|
||||
val state = serviceHub.loadState(payload.ref) as TransactionState<FixableDealState>
|
||||
StateAndRef(state, payload.ref)
|
||||
}
|
||||
@ -121,7 +112,7 @@ object FixingFlow {
|
||||
|
||||
/** Used to set up the session between [Floater] and [Fixer] */
|
||||
@CordaSerializable
|
||||
data class FixingSession(val ref: StateRef, val oracleType: ServiceType)
|
||||
data class FixingSession(val ref: StateRef, val oracle: Party)
|
||||
|
||||
/**
|
||||
* This flow looks at the deal and decides whether to be the Fixer or Floater role in agreeing a fixing.
|
||||
@ -142,14 +133,14 @@ object FixingFlow {
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
override fun call(): Unit {
|
||||
override fun call() {
|
||||
progressTracker.nextStep()
|
||||
val dealToFix = serviceHub.loadState(ref)
|
||||
val fixableDeal = (dealToFix.data as FixableDealState)
|
||||
val parties = fixableDeal.participants.sortedBy { it.owningKey.toBase58String() }
|
||||
val myKey = serviceHub.myInfo.legalIdentity.owningKey
|
||||
if (parties[0].owningKey == myKey) {
|
||||
val fixing = FixingSession(ref, fixableDeal.oracleType)
|
||||
val fixing = FixingSession(ref, fixableDeal.oracle)
|
||||
val counterparty = serviceHub.identityService.partyFromAnonymous(parties[1]) ?: throw IllegalStateException("Cannot resolve floater party")
|
||||
// Start the Floater which will then kick-off the Fixer
|
||||
subFlow(Floater(counterparty, fixing))
|
||||
|
@ -69,7 +69,8 @@ define(['viewmodel/FixedRate'], (fixedRateViewModel) => {
|
||||
fixedLeg: fixedLeg,
|
||||
floatingLeg: floatingLeg,
|
||||
calculation: calculationModel,
|
||||
common: common
|
||||
common: common,
|
||||
oracle: dealViewModel.oracle
|
||||
};
|
||||
|
||||
return json;
|
||||
|
@ -4,6 +4,7 @@ define(['viewmodel/FixedLeg', 'viewmodel/FloatingLeg', 'viewmodel/Common'], (fix
|
||||
return {
|
||||
fixedLeg: fixedLeg,
|
||||
floatingLeg: floatingLeg,
|
||||
common: common
|
||||
common: common,
|
||||
oracle: "O=Notary Service,L=Zurich,C=CH"
|
||||
};
|
||||
});
|
@ -81,5 +81,6 @@
|
||||
"dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360",
|
||||
"tradeID": "tradeXXX",
|
||||
"hashLegalDocs": "put hash here"
|
||||
}
|
||||
},
|
||||
"oracle": "oracleXXX"
|
||||
}
|
||||
|
@ -84,5 +84,6 @@
|
||||
"dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360",
|
||||
"tradeID": "tradeXXX",
|
||||
"hashLegalDocs": "put hash here"
|
||||
}
|
||||
},
|
||||
"oracle": "oracleXXX"
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
package net.corda.irs
|
||||
|
||||
import joptsimple.OptionParser
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@ -30,7 +31,7 @@ fun main(args: Array<String>) {
|
||||
val value = options.valueOf(valueArg)
|
||||
when (role) {
|
||||
Role.UploadRates -> IRSDemoClientApi(NetworkHostAndPort("localhost", 10004)).runUploadRates()
|
||||
Role.Trade -> IRSDemoClientApi(NetworkHostAndPort("localhost", 10007)).runTrade(value)
|
||||
Role.Trade -> IRSDemoClientApi(NetworkHostAndPort("localhost", 10007)).runTrade(value, CordaX500Name.parse("O=Notary Service,L=Zurich,C=CH"))
|
||||
Role.Date -> IRSDemoClientApi(NetworkHostAndPort("localhost", 10010)).runDateChange(value)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.irs
|
||||
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.irs.utilities.uploadFile
|
||||
import net.corda.testing.http.HttpApi
|
||||
@ -12,9 +13,9 @@ import java.net.URL
|
||||
class IRSDemoClientApi(private val hostAndPort: NetworkHostAndPort) {
|
||||
private val api = HttpApi.fromHostAndPort(hostAndPort, apiRoot)
|
||||
|
||||
fun runTrade(tradeId: String): Boolean {
|
||||
fun runTrade(tradeId: String, oracleName: CordaX500Name): Boolean {
|
||||
val fileContents = IOUtils.toString(javaClass.classLoader.getResourceAsStream("net/corda/irs/simulation/example-irs-trade.json"), Charsets.UTF_8.name())
|
||||
val tradeFile = fileContents.replace("tradeXXX", tradeId)
|
||||
val tradeFile = fileContents.replace("tradeXXX", tradeId).replace("oracleXXX", oracleName.toString())
|
||||
return api.postJson("deals", tradeFile)
|
||||
}
|
||||
|
||||
|
@ -2,11 +2,10 @@ package net.corda.irs
|
||||
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.services.transactions.SimpleNotaryService
|
||||
import net.corda.testing.DUMMY_BANK_A
|
||||
import net.corda.testing.DUMMY_BANK_B
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
import net.corda.irs.api.NodeInterestRates
|
||||
import net.corda.node.services.transactions.SimpleNotaryService
|
||||
import net.corda.testing.driver.driver
|
||||
|
||||
/**
|
||||
@ -15,12 +14,13 @@ import net.corda.testing.driver.driver
|
||||
*/
|
||||
fun main(args: Array<String>) {
|
||||
driver(dsl = {
|
||||
val controllerFuture = startNode(
|
||||
providedName = DUMMY_NOTARY.name,
|
||||
advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type), ServiceInfo(NodeInterestRates.Oracle.type)))
|
||||
val nodeAFuture = startNode(providedName = DUMMY_BANK_A.name)
|
||||
val nodeBFuture = startNode(providedName = DUMMY_BANK_B.name)
|
||||
val (controller, nodeA, nodeB) = listOf(controllerFuture, nodeAFuture, nodeBFuture).map { it.getOrThrow() }
|
||||
val (controller, nodeA, nodeB) = listOf(
|
||||
startNode(
|
||||
providedName = DUMMY_NOTARY.name,
|
||||
advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type))),
|
||||
startNode(providedName = DUMMY_BANK_A.name),
|
||||
startNode(providedName = DUMMY_BANK_B.name))
|
||||
.map { it.getOrThrow() }
|
||||
|
||||
startWebserver(controller)
|
||||
startWebserver(nodeA)
|
||||
|
@ -7,7 +7,6 @@ import net.corda.core.crypto.MerkleTreeException
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
@ -25,7 +24,7 @@ import net.corda.testing.node.MockServices.Companion.makeTestDataSourcePropertie
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestIdentityService
|
||||
import org.junit.After
|
||||
import org.junit.Assert
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
@ -35,7 +34,7 @@ import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
|
||||
class NodeInterestRatesTest : TestDependencyInjectionBase() {
|
||||
val TEST_DATA = NodeInterestRates.parseFile("""
|
||||
private val TEST_DATA = NodeInterestRates.parseFile("""
|
||||
LIBOR 2016-03-16 1M = 0.678
|
||||
LIBOR 2016-03-16 2M = 0.685
|
||||
LIBOR 2016-03-16 1Y = 0.890
|
||||
@ -44,30 +43,27 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
|
||||
EURIBOR 2016-03-15 2M = 0.111
|
||||
""".trimIndent())
|
||||
|
||||
val DUMMY_CASH_ISSUER_KEY = generateKeyPair()
|
||||
val DUMMY_CASH_ISSUER = Party(CordaX500Name(organisation = "Cash issuer", locality = "London", country = "GB"), DUMMY_CASH_ISSUER_KEY.public)
|
||||
private val DUMMY_CASH_ISSUER_KEY = generateKeyPair()
|
||||
private val DUMMY_CASH_ISSUER = Party(CordaX500Name(organisation = "Cash issuer", locality = "London", country = "GB"), DUMMY_CASH_ISSUER_KEY.public)
|
||||
private val services = MockServices(DUMMY_CASH_ISSUER_KEY, MEGA_CORP_KEY)
|
||||
|
||||
lateinit var oracle: NodeInterestRates.Oracle
|
||||
lateinit var database: CordaPersistence
|
||||
private lateinit var oracle: NodeInterestRates.Oracle
|
||||
private lateinit var database: CordaPersistence
|
||||
|
||||
fun fixCmdFilter(elem: Any): Boolean {
|
||||
private fun fixCmdFilter(elem: Any): Boolean {
|
||||
return when (elem) {
|
||||
is Command<*> -> oracle.identity.owningKey in elem.signers && elem.value is Fix
|
||||
is Command<*> -> services.myInfo.legalIdentity.owningKey in elem.signers && elem.value is Fix
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun filterCmds(elem: Any): Boolean = elem is Command<*>
|
||||
private fun filterCmds(elem: Any): Boolean = elem is Command<*>
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), createIdentityService = ::makeTestIdentityService)
|
||||
database.transaction {
|
||||
oracle = NodeInterestRates.Oracle(
|
||||
MEGA_CORP,
|
||||
MEGA_CORP_KEY.public,
|
||||
MockServices(DUMMY_CASH_ISSUER_KEY, MEGA_CORP_KEY)
|
||||
).apply { knownFixes = TEST_DATA }
|
||||
oracle = NodeInterestRates.Oracle(services).apply { knownFixes = TEST_DATA }
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,7 +99,7 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
|
||||
val q = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 5M")
|
||||
val res = oracle.query(listOf(q))
|
||||
assertEquals(1, res.size)
|
||||
Assert.assertEquals(0.7316228, res[0].value.toDouble(), 0.0000001)
|
||||
assertEquals(0.7316228, res[0].value.toDouble(), 0.0000001)
|
||||
assertEquals(q, res[0].of)
|
||||
}
|
||||
}
|
||||
@ -150,10 +146,10 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
|
||||
database.transaction {
|
||||
val tx = makePartialTX()
|
||||
val fix = oracle.query(listOf(NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M"))).first()
|
||||
tx.addCommand(fix, oracle.identity.owningKey)
|
||||
tx.addCommand(fix, services.myInfo.legalIdentity.owningKey)
|
||||
// Sign successfully.
|
||||
val wtx = tx.toWireTransaction()
|
||||
val ftx = wtx.buildFilteredTransaction(Predicate { x -> fixCmdFilter(x) })
|
||||
val ftx = wtx.buildFilteredTransaction(Predicate { fixCmdFilter(it) })
|
||||
val signature = oracle.sign(ftx)
|
||||
wtx.checkSignature(signature)
|
||||
}
|
||||
@ -165,9 +161,9 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
|
||||
val tx = makePartialTX()
|
||||
val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
|
||||
val badFix = Fix(fixOf, BigDecimal("0.6789"))
|
||||
tx.addCommand(badFix, oracle.identity.owningKey)
|
||||
tx.addCommand(badFix, services.myInfo.legalIdentity.owningKey)
|
||||
val wtx = tx.toWireTransaction()
|
||||
val ftx = wtx.buildFilteredTransaction(Predicate { x -> fixCmdFilter(x) })
|
||||
val ftx = wtx.buildFilteredTransaction(Predicate { fixCmdFilter(it) })
|
||||
val e1 = assertFailsWith<NodeInterestRates.UnknownFix> { oracle.sign(ftx) }
|
||||
assertEquals(fixOf, e1.fix)
|
||||
}
|
||||
@ -180,12 +176,12 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
|
||||
val fix = oracle.query(listOf(NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M"))).first()
|
||||
fun filtering(elem: Any): Boolean {
|
||||
return when (elem) {
|
||||
is Command<*> -> oracle.identity.owningKey in elem.signers && elem.value is Fix
|
||||
is Command<*> -> services.myInfo.legalIdentity.owningKey in elem.signers && elem.value is Fix
|
||||
is TransactionState<ContractState> -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
tx.addCommand(fix, oracle.identity.owningKey)
|
||||
tx.addCommand(fix, services.myInfo.legalIdentity.owningKey)
|
||||
val wtx = tx.toWireTransaction()
|
||||
val ftx = wtx.buildFilteredTransaction(Predicate(::filtering))
|
||||
assertFailsWith<IllegalArgumentException> { oracle.sign(ftx) }
|
||||
@ -204,16 +200,16 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
|
||||
fun `network tearoff`() {
|
||||
val mockNet = MockNetwork(initialiseSerialization = false)
|
||||
val n1 = mockNet.createNotaryNode()
|
||||
val n2 = mockNet.createNode(n1.network.myAddress, advertisedServices = ServiceInfo(NodeInterestRates.Oracle.type))
|
||||
n2.registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java)
|
||||
n2.registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java)
|
||||
n2.database.transaction {
|
||||
n2.installCordaService(NodeInterestRates.Oracle::class.java).knownFixes = TEST_DATA
|
||||
val oracleNode = mockNet.createNode(n1.network.myAddress).apply {
|
||||
registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java)
|
||||
registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java)
|
||||
database.transaction {
|
||||
installCordaService(NodeInterestRates.Oracle::class.java).knownFixes = TEST_DATA
|
||||
}
|
||||
}
|
||||
val tx = makePartialTX()
|
||||
val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
|
||||
val oracle = n2.info.serviceIdentities(NodeInterestRates.Oracle.type).first()
|
||||
val flow = FilteredRatesFlow(tx, oracle, fixOf, BigDecimal("0.675"), BigDecimal("0.1"))
|
||||
val flow = FilteredRatesFlow(tx, oracleNode.info.legalIdentity, fixOf, BigDecimal("0.675"), BigDecimal("0.1"))
|
||||
LogHelper.setLevel("rates")
|
||||
mockNet.runNetwork()
|
||||
val future = n1.services.startFlow(flow).resultFuture
|
||||
|
@ -15,7 +15,6 @@ import java.math.BigDecimal
|
||||
import java.time.LocalDate
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
import net.corda.irs.contract.IRS_PROGRAM_ID
|
||||
|
||||
fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
|
||||
return when (irsSelect) {
|
||||
@ -103,7 +102,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
|
||||
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, oracle = DUMMY_PARTY)
|
||||
}
|
||||
2 -> {
|
||||
// 10y swap, we pay 1.3% fixed 30/360 semi, rec 3m usd libor act/360 Q on 25m notional (mod foll/adj on both sides)
|
||||
@ -191,7 +190,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
|
||||
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, oracle = DUMMY_PARTY)
|
||||
|
||||
}
|
||||
else -> TODO("IRS number $irsSelect not defined")
|
||||
@ -199,9 +198,9 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
|
||||
}
|
||||
|
||||
class IRSTests : TestDependencyInjectionBase() {
|
||||
val megaCorpServices = MockServices(MEGA_CORP_KEY)
|
||||
val miniCorpServices = MockServices(MINI_CORP_KEY)
|
||||
val notaryServices = MockServices(DUMMY_NOTARY_KEY)
|
||||
private val megaCorpServices = MockServices(MEGA_CORP_KEY)
|
||||
private val miniCorpServices = MockServices(MINI_CORP_KEY)
|
||||
private val notaryServices = MockServices(DUMMY_NOTARY_KEY)
|
||||
|
||||
@Test
|
||||
fun ok() {
|
||||
@ -216,7 +215,7 @@ class IRSTests : TestDependencyInjectionBase() {
|
||||
/**
|
||||
* Generate an IRS txn - we'll need it for a few things.
|
||||
*/
|
||||
fun generateIRSTxn(irsSelect: Int): SignedTransaction {
|
||||
private fun generateIRSTxn(irsSelect: Int): SignedTransaction {
|
||||
val dummyIRS = createDummyIRS(irsSelect)
|
||||
val genTX: SignedTransaction = run {
|
||||
val gtx = InterestRateSwap().generateAgreement(
|
||||
@ -224,6 +223,7 @@ class IRSTests : TestDependencyInjectionBase() {
|
||||
floatingLeg = dummyIRS.floatingLeg,
|
||||
calculation = dummyIRS.calculation,
|
||||
common = dummyIRS.common,
|
||||
oracle = DUMMY_PARTY,
|
||||
notary = DUMMY_NOTARY).apply {
|
||||
setTimeWindow(TEST_TX_TIME, 30.seconds)
|
||||
}
|
||||
@ -279,7 +279,7 @@ class IRSTests : TestDependencyInjectionBase() {
|
||||
newCalculation = newCalculation.applyFixing(key, FixedRate(PercentageRatioUnit(value)))
|
||||
}
|
||||
|
||||
val newIRS = InterestRateSwap.State(irs.fixedLeg, irs.floatingLeg, newCalculation, irs.common)
|
||||
val newIRS = InterestRateSwap.State(irs.fixedLeg, irs.floatingLeg, newCalculation, irs.common, DUMMY_PARTY)
|
||||
println(newIRS.exportIRSToCSV())
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package net.corda.netmap.simulation
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import net.corda.client.jackson.JacksonSupport
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatedBy
|
||||
@ -18,12 +19,10 @@ import net.corda.finance.flows.TwoPartyDealFlow.Instigator
|
||||
import net.corda.finance.plugin.registerFinanceJSONMappers
|
||||
import net.corda.irs.contract.InterestRateSwap
|
||||
import net.corda.irs.flows.FixingFlow
|
||||
import net.corda.client.jackson.JacksonSupport
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.testing.DUMMY_CA
|
||||
import net.corda.testing.node.InMemoryMessagingNetwork
|
||||
import rx.Observable
|
||||
import java.security.PublicKey
|
||||
import java.time.LocalDate
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
@ -43,7 +42,7 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
|
||||
private val executeOnNextIteration = Collections.synchronizedList(LinkedList<() -> Unit>())
|
||||
|
||||
override fun startMainSimulation(): CompletableFuture<Unit> {
|
||||
om = JacksonSupport.createInMemoryMapper(InMemoryIdentityService((banks + regulators + networkMap).map { it.info.legalIdentityAndCert }, trustRoot = DUMMY_CA.certificate))
|
||||
om = JacksonSupport.createInMemoryMapper(InMemoryIdentityService((banks + regulators + networkMap + ratesOracle).map { it.info.legalIdentityAndCert }, trustRoot = DUMMY_CA.certificate))
|
||||
registerFinanceJSONMappers(om)
|
||||
|
||||
return startIRSDealBetween(0, 1).thenCompose {
|
||||
@ -127,7 +126,11 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
|
||||
// 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.classLoader.getResource("net/corda/irs/simulation/trade.json"))
|
||||
|
||||
val irs = om.readValue<InterestRateSwap.State>(javaClass.classLoader.getResourceAsStream("net/corda/irs/simulation/trade.json")
|
||||
.reader()
|
||||
.readText()
|
||||
.replace("oracleXXX", RatesOracleFactory.RATES_SERVICE_NAME.toString()))
|
||||
irs.fixedLeg.fixedRatePayer = node1.info.legalIdentity
|
||||
irs.floatingLeg.floatingRatePayer = node2.info.legalIdentity
|
||||
|
||||
|
@ -116,7 +116,6 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
|
||||
override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?,
|
||||
advertisedServices: Set<ServiceInfo>, id: Int, overrideServices: Map<ServiceInfo, KeyPair>?,
|
||||
entropyRoot: BigInteger): SimulatedNode {
|
||||
require(advertisedServices.containsType(NodeInterestRates.Oracle.type))
|
||||
val cfg = testNodeConfiguration(
|
||||
baseDirectory = config.baseDirectory,
|
||||
myLegalName = RATES_SERVICE_NAME)
|
||||
@ -155,7 +154,7 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
|
||||
val networkMap = mockNet.createNode(nodeFactory = NetworkMapNodeFactory, advertisedServices = ServiceInfo(NetworkMapService.type))
|
||||
val notary = mockNet.createNode(networkMap.network.myAddress, nodeFactory = NotaryNodeFactory, advertisedServices = ServiceInfo(SimpleNotaryService.type))
|
||||
val regulators = listOf(mockNet.createNode(networkMap.network.myAddress, start = false, nodeFactory = RegulatorFactory))
|
||||
val ratesOracle = mockNet.createNode(networkMap.network.myAddress, start = false, nodeFactory = RatesOracleFactory, advertisedServices = ServiceInfo(NodeInterestRates.Oracle.type))
|
||||
val ratesOracle = mockNet.createNode(networkMap.network.myAddress, start = false, nodeFactory = RatesOracleFactory)
|
||||
|
||||
// All nodes must be in one of these two lists for the purposes of the visualiser tool.
|
||||
val serviceProviders: List<SimulatedNode> = listOf(notary, ratesOracle, networkMap)
|
||||
|
@ -5,7 +5,7 @@ import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.messaging.DataFeed
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
@ -45,7 +45,7 @@ import java.util.*
|
||||
* A singleton utility that only provides a mock identity, key and storage service. However, this is sufficient for
|
||||
* building chains of transactions and verifying them. It isn't sufficient for testing flows however.
|
||||
*/
|
||||
open class MockServices(vararg val keys: KeyPair) : ServiceHub {
|
||||
open class MockServices(vararg val keys: KeyPair) : PluginServiceHub {
|
||||
|
||||
companion object {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user