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:
Shams Asari 2017-09-13 12:37:31 +01:00
parent ea61e6e9d5
commit ed0aede1f1
21 changed files with 141 additions and 199 deletions

View File

@ -1,19 +1,20 @@
package net.corda.core.node.services 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 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. * 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 * Such a class needs to have a constructor with a single parameter of type [PluginServiceHub]. This construtor will be
* construtor will be invoked during node start to initialise the service. The service hub provided can be used to get * invoked during node start to initialise the service. The service hub provided can be used to get information about the
* information about the node that may be necessary for the service. Corda services are created as singletons within * node that may be necessary for the service. Corda services are created as singletons within the node and are available
* the node and are available to flows via [net.corda.core.node.ServiceHub.cordaService]. * to flows via [ServiceHub.cordaService].
* *
* The service class has to implement [net.corda.core.serialization.SerializeAsToken] to ensure correct usage within flows. * The service class has to implement [SerializeAsToken] to ensure correct usage within flows. (If possible extend
* (If possible extend [net.corda.core.serialization.SingletonSerializeAsToken] instead as it removes the boilerplate.) * [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.
*/ */
// TODO Handle the singleton serialisation of Corda services automatically, removing the need to implement SerializeAsToken // 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 // TODO Perhaps this should be an interface or abstract class due to the need for it to implement SerializeAsToken and

View File

@ -10,16 +10,11 @@ import net.corda.core.node.services.TimeWindowChecker
import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.core.node.services.TrustedAuthorityNotaryService
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.services.transactions.ValidatingNotaryService
import java.security.SignatureException import java.security.SignatureException
// START 1 // START 1
@CordaService @CordaService
class MyCustomValidatingNotaryService(override val services: PluginServiceHub) : TrustedAuthorityNotaryService() { class MyCustomValidatingNotaryService(override val services: PluginServiceHub) : TrustedAuthorityNotaryService() {
companion object {
val type = ValidatingNotaryService.type.getSubType("mycustom")
}
override val timeWindowChecker = TimeWindowChecker(services.clock) override val timeWindowChecker = TimeWindowChecker(services.clock)
override val uniquenessProvider = PersistentUniquenessProvider() override val uniquenessProvider = PersistentUniquenessProvider()

View File

@ -13,7 +13,6 @@ import net.corda.core.contracts.LinearState
import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.TokenizableAssetInfo import net.corda.core.contracts.TokenizableAssetInfo
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.services.ServiceType
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.finance.contracts.asset.CommodityContract import net.corda.finance.contracts.asset.CommodityContract
@ -416,7 +415,7 @@ interface FixableDealState : DealState {
/** /**
* What oracle service to use for the fixing * What oracle service to use for the fixing
*/ */
val oracleType: ServiceType val oracle: Party
/** /**
* Generate a fixing command for this deal and fix. * Generate a fixing command for this deal and fix.

View File

@ -31,8 +31,8 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.cert import net.corda.core.utilities.cert
import net.corda.core.utilities.debug 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.classloading.requireAnnotation
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.services.NotaryChangeHandler import net.corda.node.services.NotaryChangeHandler
import net.corda.node.services.NotifyTransactionHandler import net.corda.node.services.NotifyTransactionHandler
import net.corda.node.services.SwapIdentitiesHandler import net.corda.node.services.SwapIdentitiesHandler
@ -135,9 +135,6 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
lateinit var database: CordaPersistence lateinit var database: CordaPersistence
protected var dbCloser: (() -> Any?)? = null protected var dbCloser: (() -> Any?)? = null
var isPreviousCheckpointsPresent = false
private set
protected val _nodeReadyFuture = openFuture<Unit>() protected val _nodeReadyFuture = openFuture<Unit>()
/** Completes once the node has successfully registered with the network map service /** Completes once the node has successfully registered with the network map service
* or has loaded network map data from local database */ * 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) 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") val scanPackage = System.getProperty("net.corda.node.cordapp.scan.package")
if (scanPackage != null) { if (scanPackage != null) {
check(configuration.devMode) { "Package scanning can only occur in dev mode" } check(configuration.devMode) { "Package scanning can only occur in dev mode" }
@ -205,10 +202,6 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
makeVaultObservers() makeVaultObservers()
checkpointStorage.forEach {
isPreviousCheckpointsPresent = true
false
}
startMessagingService(rpcOps) startMessagingService(rpcOps)
installCoreFlows() installCoreFlows()
@ -233,7 +226,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
private class ServiceInstantiationException(cause: Throwable?) : Exception(cause) private class ServiceInstantiationException(cause: Throwable?) : Exception(cause)
private fun installCordaServices() { private fun installCordaServices() {
cordappLoader.cordapps.flatMap { it.filterEnabledServices(info) }.map { cordappLoader.cordapps.flatMap { it.services }.forEach {
try { try {
installCordaService(it) installCordaService(it)
} catch (e: NoSuchMethodException) { } catch (e: NoSuchMethodException) {
@ -486,8 +479,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
val address: SingleMessageRecipient = networkMapAddress ?: val address: SingleMessageRecipient = networkMapAddress ?:
network.getAddressOfParty(PartyInfo.Node(info)) as SingleMessageRecipient network.getAddressOfParty(PartyInfo.Node(info)) as SingleMessageRecipient
// Register for updates, even if we're the one running the network map. // Register for updates, even if we're the one running the network map.
return sendNetworkMapRegistration(address).flatMap { response: RegistrationResponse -> return sendNetworkMapRegistration(address).flatMap { (error) ->
check(response.error == null) { "Unable to register with the network map service: ${response.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 // 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) services.networkMapCache.addMapService(network, address, true, null)
} }

View File

@ -2,12 +2,8 @@ package net.corda.node.internal.cordapp
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.node.CordaPluginRegistry 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.schemas.MappedSchema
import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SerializeAsToken
import net.corda.core.utilities.debug
import net.corda.core.utilities.loggerFor
import java.net.URL import java.net.URL
/** /**
@ -27,32 +23,4 @@ data class Cordapp(
val services: List<Class<out SerializeAsToken>>, val services: List<Class<out SerializeAsToken>>,
val plugins: List<CordaPluginRegistry>, val plugins: List<CordaPluginRegistry>,
val customSchemas: Set<MappedSchema>, val customSchemas: Set<MappedSchema>,
val jarPath: URL) { 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
}
}
}

View File

@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCClient
import net.corda.core.contracts.UniqueIdentifier import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.identity.Party
import net.corda.core.messaging.vaultTrackBy import net.corda.core.messaging.vaultTrackBy
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.toFuture 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.loggerFor
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.finance.plugin.registerFinanceJSONMappers import net.corda.finance.plugin.registerFinanceJSONMappers
import net.corda.irs.api.NodeInterestRates
import net.corda.irs.contract.InterestRateSwap import net.corda.irs.contract.InterestRateSwap
import net.corda.irs.utilities.uploadFile import net.corda.irs.utilities.uploadFile
import net.corda.node.services.config.FullNodeConfiguration import net.corda.node.services.config.FullNodeConfiguration
@ -44,20 +44,20 @@ class IRSDemoTest : IntegrationTestCategory {
val log = loggerFor<IRSDemoTest>() val log = loggerFor<IRSDemoTest>()
} }
val rpcUser = User("user", "password", emptySet()) private val rpcUser = User("user", "password", emptySet())
val currentDate: LocalDate = LocalDate.now() private val currentDate: LocalDate = LocalDate.now()
val futureDate: LocalDate = currentDate.plusMonths(6) private val futureDate: LocalDate = currentDate.plusMonths(6)
val maxWaitTime: Duration = 60.seconds private val maxWaitTime: Duration = 60.seconds
@Test @Test
fun `runs IRS demo`() { fun `runs IRS demo`() {
driver(useTestClock = true, isDebug = true) { driver(useTestClock = true, isDebug = true) {
val controllerFuture = startNode( val (controller, nodeA, nodeB) = listOf(
startNode(
providedName = DUMMY_NOTARY.name, providedName = DUMMY_NOTARY.name,
advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type), ServiceInfo(NodeInterestRates.Oracle.type))) advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type))),
val nodeAFuture = startNode(providedName = DUMMY_BANK_A.name, rpcUsers = listOf(rpcUser)) startNode(providedName = DUMMY_BANK_A.name, rpcUsers = listOf(rpcUser)),
val nodeBFuture = startNode(providedName = DUMMY_BANK_B.name) startNode(providedName = DUMMY_BANK_B.name)).map { it.getOrThrow() }
val (controller, nodeA, nodeB) = listOf(controllerFuture, nodeAFuture, nodeBFuture).map { it.getOrThrow() }
log.info("All nodes started") log.info("All nodes started")
@ -80,7 +80,7 @@ class IRSDemoTest : IntegrationTestCategory {
val numBDeals = getTradeCount(nodeBApi) val numBDeals = getTradeCount(nodeBApi)
runUploadRates(controllerAddr) runUploadRates(controllerAddr)
runTrade(nodeAApi) runTrade(nodeAApi, controller.nodeInfo.legalIdentity)
assertThat(getTradeCount(nodeAApi)).isEqualTo(numADeals + 1) assertThat(getTradeCount(nodeAApi)).isEqualTo(numADeals + 1)
assertThat(getTradeCount(nodeBApi)).isEqualTo(numBDeals + 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 client = CordaRPCClient(config.rpcAddress!!, initialiseSerialization = false)
val proxy = client.start("user", "password").proxy val proxy = client.start("user", "password").proxy
val vaultUpdates = proxy.vaultTrackBy<InterestRateSwap.State>().updates val vaultUpdates = proxy.vaultTrackBy<InterestRateSwap.State>().updates
@ -113,10 +115,10 @@ class IRSDemoTest : IntegrationTestCategory {
assertThat(nodeApi.putJson("demodate", "\"$futureDate\"")).isTrue() 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}") log.info("Running trade against ${nodeApi.root}")
val fileContents = loadResourceFile("net/corda/irs/simulation/example-irs-trade.json") val fileContents = loadResourceFile("net/corda/irs/simulation/example-irs-trade.json")
val tradeFile = fileContents.replace("tradeXXX", "trade1") val tradeFile = fileContents.replace("tradeXXX", "trade1").replace("oracleXXX", oracle.name.toString())
assertThat(nodeApi.postJson("deals", tradeFile)).isTrue() assertThat(nodeApi.postJson("deals", tradeFile)).isTrue()
} }
@ -139,11 +141,10 @@ class IRSDemoTest : IntegrationTestCategory {
private fun getTrades(nodeApi: HttpApi): Array<InterestRateSwap.State> { private fun getTrades(nodeApi: HttpApi): Array<InterestRateSwap.State> {
log.info("Getting trades from ${nodeApi.root}") log.info("Getting trades from ${nodeApi.root}")
val deals = nodeApi.getJson<Array<InterestRateSwap.State>>("deals") return nodeApi.getJson("deals")
return 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) first(pred).toFuture().getOrThrow(timeout)
} }
@ -163,7 +164,8 @@ class IRSDemoTest : IntegrationTestCategory {
val calculation: InterestRateSwap.Calculation = mapper.readValue(node.get("calculation").toString()) val calculation: InterestRateSwap.Calculation = mapper.readValue(node.get("calculation").toString())
val common: InterestRateSwap.Common = mapper.readValue(node.get("common").toString()) val common: InterestRateSwap.Common = mapper.readValue(node.get("common").toString())
val linearId: UniqueIdentifier = mapper.readValue(node.get("linearId").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) { } catch (e: Exception) {
throw JsonParseException(parser, "Invalid interest rate swap state(s) ${parser.text}: ${e.message}") throw JsonParseException(parser, "Invalid interest rate swap state(s) ${parser.text}: ${e.message}")
} }

View File

@ -2,7 +2,8 @@ package net.corda.irs.api
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Command 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.FlowException
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatedBy 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.identity.Party
import net.corda.core.internal.ThreadBox import net.corda.core.internal.ThreadBox
import net.corda.core.node.PluginServiceHub 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.CordaService
import net.corda.core.node.services.ServiceType
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.FilteredTransaction import net.corda.core.transactions.FilteredTransaction
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
@ -27,7 +26,6 @@ import net.corda.finance.contracts.math.InterpolatorFactory
import net.corda.irs.flows.RatesFixFlow import net.corda.irs.flows.RatesFixFlow
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import java.math.BigDecimal import java.math.BigDecimal
import java.security.PublicKey
import java.time.LocalDate import java.time.LocalDate
import java.util.* import java.util.*
import javax.annotation.concurrent.ThreadSafe import javax.annotation.concurrent.ThreadSafe
@ -48,7 +46,7 @@ import kotlin.collections.set
object NodeInterestRates { object NodeInterestRates {
// DOCSTART 2 // DOCSTART 2
@InitiatedBy(RatesFixFlow.FixSignFlow::class) @InitiatedBy(RatesFixFlow.FixSignFlow::class)
class FixSignHandler(val otherParty: Party) : FlowLogic<Unit>() { class FixSignHandler(private val otherParty: Party) : FlowLogic<Unit>() {
@Suspendable @Suspendable
override fun call() { override fun call() {
val request = receive<RatesFixFlow.SignRequest>(otherParty).unwrap { it } val request = receive<RatesFixFlow.SignRequest>(otherParty).unwrap { it }
@ -58,14 +56,14 @@ object NodeInterestRates {
} }
@InitiatedBy(RatesFixFlow.FixQueryFlow::class) @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 RECEIVED : ProgressTracker.Step("Received fix request")
object SENDING : ProgressTracker.Step("Sending fix response") object SENDING : ProgressTracker.Step("Sending fix response")
override val progressTracker = ProgressTracker(RECEIVED, SENDING) override val progressTracker = ProgressTracker(RECEIVED, SENDING)
@Suspendable @Suspendable
override fun call(): Unit { override fun call() {
val request = receive<RatesFixFlow.QueryRequest>(otherParty).unwrap { it } val request = receive<RatesFixFlow.QueryRequest>(otherParty).unwrap { it }
progressTracker.currentStep = RECEIVED progressTracker.currentStep = RECEIVED
val oracle = serviceHub.cordaService(Oracle::class.java) val oracle = serviceHub.cordaService(Oracle::class.java)
@ -84,12 +82,10 @@ object NodeInterestRates {
@ThreadSafe @ThreadSafe
// DOCSTART 3 // DOCSTART 3
@CordaService @CordaService
class Oracle(val identity: Party, private val signingKey: PublicKey, val services: ServiceHub) : SingletonSerializeAsToken() { class Oracle(private val services: PluginServiceHub) : SingletonSerializeAsToken() {
constructor(services: PluginServiceHub) : this( private val mutex = ThreadBox(InnerState())
services.myInfo.serviceIdentities(type).first(),
services.myInfo.serviceIdentities(type).first().owningKey.keys.first { services.keyManagementService.keys.contains(it) }, init {
services
) {
// Set some default fixes to the Oracle, so we can smoothly run the IRS Demo without uploading fixes. // 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 // This is required to avoid a situation where the runnodes version of the demo isn't in a good state
// upon startup. // upon startup.
@ -97,19 +93,12 @@ object NodeInterestRates {
} }
// DOCEND 3 // DOCEND 3
companion object {
@JvmField
val type = ServiceType.corda.getSubType("interest_rates")
}
private class InnerState { private class InnerState {
// TODO Update this to use a database once we have an database API // TODO Update this to use a database once we have an database API
val fixes = HashSet<Fix>() val fixes = HashSet<Fix>()
var container: FixContainer = FixContainer(fixes) var container: FixContainer = FixContainer(fixes)
} }
private val mutex = ThreadBox(InnerState())
var knownFixes: FixContainer var knownFixes: FixContainer
set(value) { set(value) {
require(value.size > 0) require(value.size > 0)
@ -121,11 +110,6 @@ object NodeInterestRates {
} }
get() = mutex.locked { container } 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 @Suspendable
fun query(queries: List<FixOf>): List<Fix> { fun query(queries: List<FixOf>): List<Fix> {
require(queries.isNotEmpty()) require(queries.isNotEmpty())
@ -150,8 +134,9 @@ object NodeInterestRates {
} }
// Performing validation of obtained FilteredLeaves. // Performing validation of obtained FilteredLeaves.
fun commandValidator(elem: Command<*>): Boolean { fun commandValidator(elem: Command<*>): Boolean {
if (!(identity.owningKey in elem.signers && elem.value is Fix)) require(services.myInfo.legalIdentity.owningKey in elem.signers && elem.value is Fix) {
throw IllegalArgumentException("Oracle received unknown command (not in signers or not Fix).") "Oracle received unknown command (not in signers or not Fix)."
}
val fix = elem.value as Fix val fix = elem.value as Fix
val known = knownFixes[fix.of] val known = knownFixes[fix.of]
if (known == null || known != fix) if (known == null || known != fix)
@ -167,15 +152,14 @@ object NodeInterestRates {
} }
val leaves = ftx.filteredLeaves val leaves = ftx.filteredLeaves
if (!leaves.checkWithFun(::check)) require(leaves.checkWithFun(::check))
throw IllegalArgumentException()
// It all checks out, so we can return a signature. // 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 // 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 // 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. // an invalid transaction the signature is worthless.
return services.createSignature(ftx, signingKey) return services.createSignature(ftx, services.myInfo.legalIdentity.owningKey)
} }
// DOCEND 1 // DOCEND 1
@ -212,7 +196,7 @@ object NodeInterestRates {
private fun buildContainer(fixes: Set<Fix>): Map<Pair<String, LocalDate>, InterpolatingRateMap> { private fun buildContainer(fixes: Set<Fix>): Map<Pair<String, LocalDate>, InterpolatingRateMap> {
val tempContainer = HashMap<Pair<String, LocalDate>, HashMap<Tenor, BigDecimal>>() val tempContainer = HashMap<Pair<String, LocalDate>, HashMap<Tenor, BigDecimal>>()
for ((fixOf, value) in fixes) { 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 rates[fixOf.ofTenor] = value
} }

View File

@ -5,12 +5,10 @@ import net.corda.core.contracts.*
import net.corda.core.flows.FlowLogicRefFactory import net.corda.core.flows.FlowLogicRefFactory
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.services.ServiceType
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.finance.contracts.* import net.corda.finance.contracts.*
import net.corda.irs.api.NodeInterestRates
import net.corda.irs.flows.FixingFlow import net.corda.irs.flows.FixingFlow
import net.corda.irs.utilities.suggestInterestRateAnnouncementTimeWindow import net.corda.irs.utilities.suggestInterestRateAnnouncementTimeWindow
import org.apache.commons.jexl3.JexlBuilder import org.apache.commons.jexl3.JexlBuilder
@ -603,11 +601,9 @@ class InterestRateSwap : Contract {
val floatingLeg: FloatingLeg, val floatingLeg: FloatingLeg,
val calculation: Calculation, val calculation: Calculation,
val common: Common, val common: Common,
override val oracle: Party,
override val linearId: UniqueIdentifier = UniqueIdentifier(common.tradeID) override val linearId: UniqueIdentifier = UniqueIdentifier(common.tradeID)
) : FixableDealState, SchedulableState { ) : FixableDealState, SchedulableState {
override val oracleType: ServiceType
get() = NodeInterestRates.Oracle.type
val ref: String get() = linearId.externalId ?: "" val ref: String get() = linearId.externalId ?: ""
override val participants: List<AbstractParty> override val participants: List<AbstractParty>
@ -621,7 +617,9 @@ class InterestRateSwap : Contract {
return ScheduledActivity(flowLogicRefFactory.create(FixingFlow.FixingRoleDecider::class.java, thisStateRef), instant) 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) { override fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix) {
InterestRateSwap().generateFix(ptx, StateAndRef(TransactionState(this, IRS_PROGRAM_ID, oldState.state.notary), oldState.ref), 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. * Note: The day count, interest rate calculation etc are not finished yet, but they are demonstrable.
*/ */
fun generateAgreement(floatingLeg: FloatingLeg, fixedLeg: FixedLeg, calculation: Calculation, 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>() 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 var periodStartDate = fixedLeg.effectiveDate
// Create a schedule for the fixed payments // Create a schedule for the fixed payments
@ -717,8 +719,11 @@ class InterestRateSwap : Contract {
val newCalculation = Calculation(calculation.expression, floatingLegPaymentSchedule, fixedLegPaymentSchedule) val newCalculation = Calculation(calculation.expression, floatingLegPaymentSchedule, fixedLegPaymentSchedule)
// Put all the above into a new State object. // Put all the above into a new State object.
val state = State(fixedLeg, floatingLeg, newCalculation, common) 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))) 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 { private fun calcFixingDate(date: LocalDate, fixingPeriodOffset: Int, calendar: BusinessCalendar): LocalDate {

View File

@ -3,22 +3,16 @@ package net.corda.irs.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.utilities.toBase58String
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatedBy import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.SchedulableFlow import net.corda.core.flows.SchedulableFlow
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.services.ServiceType
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.*
import net.corda.core.utilities.seconds
import net.corda.core.utilities.trace
import net.corda.core.utilities.transient
import net.corda.finance.contracts.Fix import net.corda.finance.contracts.Fix
import net.corda.finance.contracts.FixableDealState import net.corda.finance.contracts.FixableDealState
import net.corda.finance.flows.TwoPartyDealFlow import net.corda.finance.flows.TwoPartyDealFlow
@ -65,11 +59,8 @@ object FixingFlow {
val ptx = TransactionBuilder(txState.notary) val ptx = TransactionBuilder(txState.notary)
val oracle = serviceHub.networkMapCache.getNodesWithService(handshake.payload.oracleType).first()
val oracleParty = oracle.serviceIdentities(handshake.payload.oracleType).first()
// DOCSTART 1 // 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 @Suspendable
override fun beforeSigning(fix: Fix) { override fun beforeSigning(fix: Fix) {
newDeal.generateFix(ptx, StateAndRef(txState, handshake.payload.ref), fix) newDeal.generateFix(ptx, StateAndRef(txState, handshake.payload.ref), fix)
@ -82,7 +73,7 @@ object FixingFlow {
@Suspendable @Suspendable
override fun filtering(elem: Any): Boolean { override fun filtering(elem: Any): Boolean {
return when (elem) { 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 else -> false
} }
} }
@ -104,7 +95,7 @@ object FixingFlow {
override val payload: FixingSession, override val payload: FixingSession,
override val progressTracker: ProgressTracker = TwoPartyDealFlow.Primary.tracker()) : TwoPartyDealFlow.Primary() { override val progressTracker: ProgressTracker = TwoPartyDealFlow.Primary.tracker()) : TwoPartyDealFlow.Primary() {
@Suppress("UNCHECKED_CAST") @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> val state = serviceHub.loadState(payload.ref) as TransactionState<FixableDealState>
StateAndRef(state, payload.ref) StateAndRef(state, payload.ref)
} }
@ -121,7 +112,7 @@ object FixingFlow {
/** Used to set up the session between [Floater] and [Fixer] */ /** Used to set up the session between [Floater] and [Fixer] */
@CordaSerializable @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. * 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 @Suspendable
override fun call(): Unit { override fun call() {
progressTracker.nextStep() progressTracker.nextStep()
val dealToFix = serviceHub.loadState(ref) val dealToFix = serviceHub.loadState(ref)
val fixableDeal = (dealToFix.data as FixableDealState) val fixableDeal = (dealToFix.data as FixableDealState)
val parties = fixableDeal.participants.sortedBy { it.owningKey.toBase58String() } val parties = fixableDeal.participants.sortedBy { it.owningKey.toBase58String() }
val myKey = serviceHub.myInfo.legalIdentity.owningKey val myKey = serviceHub.myInfo.legalIdentity.owningKey
if (parties[0].owningKey == myKey) { 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") val counterparty = serviceHub.identityService.partyFromAnonymous(parties[1]) ?: throw IllegalStateException("Cannot resolve floater party")
// Start the Floater which will then kick-off the Fixer // Start the Floater which will then kick-off the Fixer
subFlow(Floater(counterparty, fixing)) subFlow(Floater(counterparty, fixing))

View File

@ -69,7 +69,8 @@ define(['viewmodel/FixedRate'], (fixedRateViewModel) => {
fixedLeg: fixedLeg, fixedLeg: fixedLeg,
floatingLeg: floatingLeg, floatingLeg: floatingLeg,
calculation: calculationModel, calculation: calculationModel,
common: common common: common,
oracle: dealViewModel.oracle
}; };
return json; return json;

View File

@ -4,6 +4,7 @@ define(['viewmodel/FixedLeg', 'viewmodel/FloatingLeg', 'viewmodel/Common'], (fix
return { return {
fixedLeg: fixedLeg, fixedLeg: fixedLeg,
floatingLeg: floatingLeg, floatingLeg: floatingLeg,
common: common common: common,
oracle: "O=Notary Service,L=Zurich,C=CH"
}; };
}); });

View File

@ -81,5 +81,6 @@
"dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360", "dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360",
"tradeID": "tradeXXX", "tradeID": "tradeXXX",
"hashLegalDocs": "put hash here" "hashLegalDocs": "put hash here"
} },
"oracle": "oracleXXX"
} }

View File

@ -84,5 +84,6 @@
"dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360", "dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360",
"tradeID": "tradeXXX", "tradeID": "tradeXXX",
"hashLegalDocs": "put hash here" "hashLegalDocs": "put hash here"
} },
"oracle": "oracleXXX"
} }

View File

@ -3,6 +3,7 @@
package net.corda.irs package net.corda.irs
import joptsimple.OptionParser import joptsimple.OptionParser
import net.corda.core.identity.CordaX500Name
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import kotlin.system.exitProcess import kotlin.system.exitProcess
@ -30,7 +31,7 @@ fun main(args: Array<String>) {
val value = options.valueOf(valueArg) val value = options.valueOf(valueArg)
when (role) { when (role) {
Role.UploadRates -> IRSDemoClientApi(NetworkHostAndPort("localhost", 10004)).runUploadRates() 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) Role.Date -> IRSDemoClientApi(NetworkHostAndPort("localhost", 10010)).runDateChange(value)
} }
} }

View File

@ -1,5 +1,6 @@
package net.corda.irs package net.corda.irs
import net.corda.core.identity.CordaX500Name
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.irs.utilities.uploadFile import net.corda.irs.utilities.uploadFile
import net.corda.testing.http.HttpApi import net.corda.testing.http.HttpApi
@ -12,9 +13,9 @@ import java.net.URL
class IRSDemoClientApi(private val hostAndPort: NetworkHostAndPort) { class IRSDemoClientApi(private val hostAndPort: NetworkHostAndPort) {
private val api = HttpApi.fromHostAndPort(hostAndPort, apiRoot) 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 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) return api.postJson("deals", tradeFile)
} }

View File

@ -2,11 +2,10 @@ package net.corda.irs
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.utilities.getOrThrow 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_A
import net.corda.testing.DUMMY_BANK_B import net.corda.testing.DUMMY_BANK_B
import net.corda.testing.DUMMY_NOTARY 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 import net.corda.testing.driver.driver
/** /**
@ -15,12 +14,13 @@ import net.corda.testing.driver.driver
*/ */
fun main(args: Array<String>) { fun main(args: Array<String>) {
driver(dsl = { driver(dsl = {
val controllerFuture = startNode( val (controller, nodeA, nodeB) = listOf(
startNode(
providedName = DUMMY_NOTARY.name, providedName = DUMMY_NOTARY.name,
advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type), ServiceInfo(NodeInterestRates.Oracle.type))) advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type))),
val nodeAFuture = startNode(providedName = DUMMY_BANK_A.name) startNode(providedName = DUMMY_BANK_A.name),
val nodeBFuture = startNode(providedName = DUMMY_BANK_B.name) startNode(providedName = DUMMY_BANK_B.name))
val (controller, nodeA, nodeB) = listOf(controllerFuture, nodeAFuture, nodeBFuture).map { it.getOrThrow() } .map { it.getOrThrow() }
startWebserver(controller) startWebserver(controller)
startWebserver(nodeA) startWebserver(nodeA)

View File

@ -7,7 +7,6 @@ import net.corda.core.crypto.MerkleTreeException
import net.corda.core.crypto.generateKeyPair import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.services.ServiceInfo
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.getOrThrow 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.makeTestDatabaseProperties
import net.corda.testing.node.MockServices.Companion.makeTestIdentityService import net.corda.testing.node.MockServices.Companion.makeTestIdentityService
import org.junit.After import org.junit.After
import org.junit.Assert import org.junit.Assert.*
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import java.math.BigDecimal import java.math.BigDecimal
@ -35,7 +34,7 @@ import kotlin.test.assertFailsWith
import kotlin.test.assertFalse import kotlin.test.assertFalse
class NodeInterestRatesTest : TestDependencyInjectionBase() { 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 1M = 0.678
LIBOR 2016-03-16 2M = 0.685 LIBOR 2016-03-16 2M = 0.685
LIBOR 2016-03-16 1Y = 0.890 LIBOR 2016-03-16 1Y = 0.890
@ -44,30 +43,27 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
EURIBOR 2016-03-15 2M = 0.111 EURIBOR 2016-03-15 2M = 0.111
""".trimIndent()) """.trimIndent())
val DUMMY_CASH_ISSUER_KEY = generateKeyPair() private 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 = 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 private lateinit var oracle: NodeInterestRates.Oracle
lateinit var database: CordaPersistence private lateinit var database: CordaPersistence
fun fixCmdFilter(elem: Any): Boolean { private fun fixCmdFilter(elem: Any): Boolean {
return when (elem) { 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 else -> false
} }
} }
fun filterCmds(elem: Any): Boolean = elem is Command<*> private fun filterCmds(elem: Any): Boolean = elem is Command<*>
@Before @Before
fun setUp() { fun setUp() {
database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), createIdentityService = ::makeTestIdentityService) database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), createIdentityService = ::makeTestIdentityService)
database.transaction { database.transaction {
oracle = NodeInterestRates.Oracle( oracle = NodeInterestRates.Oracle(services).apply { knownFixes = TEST_DATA }
MEGA_CORP,
MEGA_CORP_KEY.public,
MockServices(DUMMY_CASH_ISSUER_KEY, MEGA_CORP_KEY)
).apply { knownFixes = TEST_DATA }
} }
} }
@ -103,7 +99,7 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
val q = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 5M") val q = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 5M")
val res = oracle.query(listOf(q)) val res = oracle.query(listOf(q))
assertEquals(1, res.size) 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) assertEquals(q, res[0].of)
} }
} }
@ -150,10 +146,10 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
database.transaction { database.transaction {
val tx = makePartialTX() val tx = makePartialTX()
val fix = oracle.query(listOf(NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M"))).first() 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. // Sign successfully.
val wtx = tx.toWireTransaction() val wtx = tx.toWireTransaction()
val ftx = wtx.buildFilteredTransaction(Predicate { x -> fixCmdFilter(x) }) val ftx = wtx.buildFilteredTransaction(Predicate { fixCmdFilter(it) })
val signature = oracle.sign(ftx) val signature = oracle.sign(ftx)
wtx.checkSignature(signature) wtx.checkSignature(signature)
} }
@ -165,9 +161,9 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
val tx = makePartialTX() val tx = makePartialTX()
val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M") val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val badFix = Fix(fixOf, BigDecimal("0.6789")) 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 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) } val e1 = assertFailsWith<NodeInterestRates.UnknownFix> { oracle.sign(ftx) }
assertEquals(fixOf, e1.fix) assertEquals(fixOf, e1.fix)
} }
@ -180,12 +176,12 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
val fix = oracle.query(listOf(NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M"))).first() val fix = oracle.query(listOf(NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M"))).first()
fun filtering(elem: Any): Boolean { fun filtering(elem: Any): Boolean {
return when (elem) { 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 is TransactionState<ContractState> -> true
else -> false else -> false
} }
} }
tx.addCommand(fix, oracle.identity.owningKey) tx.addCommand(fix, services.myInfo.legalIdentity.owningKey)
val wtx = tx.toWireTransaction() val wtx = tx.toWireTransaction()
val ftx = wtx.buildFilteredTransaction(Predicate(::filtering)) val ftx = wtx.buildFilteredTransaction(Predicate(::filtering))
assertFailsWith<IllegalArgumentException> { oracle.sign(ftx) } assertFailsWith<IllegalArgumentException> { oracle.sign(ftx) }
@ -204,16 +200,16 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
fun `network tearoff`() { fun `network tearoff`() {
val mockNet = MockNetwork(initialiseSerialization = false) val mockNet = MockNetwork(initialiseSerialization = false)
val n1 = mockNet.createNotaryNode() val n1 = mockNet.createNotaryNode()
val n2 = mockNet.createNode(n1.network.myAddress, advertisedServices = ServiceInfo(NodeInterestRates.Oracle.type)) val oracleNode = mockNet.createNode(n1.network.myAddress).apply {
n2.registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java) registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java)
n2.registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java) registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java)
n2.database.transaction { database.transaction {
n2.installCordaService(NodeInterestRates.Oracle::class.java).knownFixes = TEST_DATA installCordaService(NodeInterestRates.Oracle::class.java).knownFixes = TEST_DATA
}
} }
val tx = makePartialTX() val tx = makePartialTX()
val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M") val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val oracle = n2.info.serviceIdentities(NodeInterestRates.Oracle.type).first() val flow = FilteredRatesFlow(tx, oracleNode.info.legalIdentity, fixOf, BigDecimal("0.675"), BigDecimal("0.1"))
val flow = FilteredRatesFlow(tx, oracle, fixOf, BigDecimal("0.675"), BigDecimal("0.1"))
LogHelper.setLevel("rates") LogHelper.setLevel("rates")
mockNet.runNetwork() mockNet.runNetwork()
val future = n1.services.startFlow(flow).resultFuture val future = n1.services.startFlow(flow).resultFuture

View File

@ -15,7 +15,6 @@ import java.math.BigDecimal
import java.time.LocalDate import java.time.LocalDate
import java.util.* import java.util.*
import kotlin.test.assertEquals import kotlin.test.assertEquals
import net.corda.irs.contract.IRS_PROGRAM_ID
fun createDummyIRS(irsSelect: Int): InterestRateSwap.State { fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
return when (irsSelect) { 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") 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 -> { 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) // 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") 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") else -> TODO("IRS number $irsSelect not defined")
@ -199,9 +198,9 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
} }
class IRSTests : TestDependencyInjectionBase() { class IRSTests : TestDependencyInjectionBase() {
val megaCorpServices = MockServices(MEGA_CORP_KEY) private val megaCorpServices = MockServices(MEGA_CORP_KEY)
val miniCorpServices = MockServices(MINI_CORP_KEY) private val miniCorpServices = MockServices(MINI_CORP_KEY)
val notaryServices = MockServices(DUMMY_NOTARY_KEY) private val notaryServices = MockServices(DUMMY_NOTARY_KEY)
@Test @Test
fun ok() { fun ok() {
@ -216,7 +215,7 @@ class IRSTests : TestDependencyInjectionBase() {
/** /**
* Generate an IRS txn - we'll need it for a few things. * 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 dummyIRS = createDummyIRS(irsSelect)
val genTX: SignedTransaction = run { val genTX: SignedTransaction = run {
val gtx = InterestRateSwap().generateAgreement( val gtx = InterestRateSwap().generateAgreement(
@ -224,6 +223,7 @@ class IRSTests : TestDependencyInjectionBase() {
floatingLeg = dummyIRS.floatingLeg, floatingLeg = dummyIRS.floatingLeg,
calculation = dummyIRS.calculation, calculation = dummyIRS.calculation,
common = dummyIRS.common, common = dummyIRS.common,
oracle = DUMMY_PARTY,
notary = DUMMY_NOTARY).apply { notary = DUMMY_NOTARY).apply {
setTimeWindow(TEST_TX_TIME, 30.seconds) setTimeWindow(TEST_TX_TIME, 30.seconds)
} }
@ -279,7 +279,7 @@ class IRSTests : TestDependencyInjectionBase() {
newCalculation = newCalculation.applyFixing(key, FixedRate(PercentageRatioUnit(value))) 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()) println(newIRS.exportIRSToCSV())
} }

View File

@ -3,6 +3,7 @@ package net.corda.netmap.simulation
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import net.corda.client.jackson.JacksonSupport
import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateAndRef
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatedBy 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.finance.plugin.registerFinanceJSONMappers
import net.corda.irs.contract.InterestRateSwap import net.corda.irs.contract.InterestRateSwap
import net.corda.irs.flows.FixingFlow import net.corda.irs.flows.FixingFlow
import net.corda.client.jackson.JacksonSupport
import net.corda.node.services.identity.InMemoryIdentityService import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.testing.DUMMY_CA import net.corda.testing.DUMMY_CA
import net.corda.testing.node.InMemoryMessagingNetwork import net.corda.testing.node.InMemoryMessagingNetwork
import rx.Observable import rx.Observable
import java.security.PublicKey
import java.time.LocalDate import java.time.LocalDate
import java.util.* import java.util.*
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
@ -43,7 +42,7 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
private val executeOnNextIteration = Collections.synchronizedList(LinkedList<() -> Unit>()) private val executeOnNextIteration = Collections.synchronizedList(LinkedList<() -> Unit>())
override fun startMainSimulation(): CompletableFuture<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) registerFinanceJSONMappers(om)
return startIRSDealBetween(0, 1).thenCompose { 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 // 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. // 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. // 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.fixedLeg.fixedRatePayer = node1.info.legalIdentity
irs.floatingLeg.floatingRatePayer = node2.info.legalIdentity irs.floatingLeg.floatingRatePayer = node2.info.legalIdentity

View File

@ -116,7 +116,6 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?,
advertisedServices: Set<ServiceInfo>, id: Int, overrideServices: Map<ServiceInfo, KeyPair>?, advertisedServices: Set<ServiceInfo>, id: Int, overrideServices: Map<ServiceInfo, KeyPair>?,
entropyRoot: BigInteger): SimulatedNode { entropyRoot: BigInteger): SimulatedNode {
require(advertisedServices.containsType(NodeInterestRates.Oracle.type))
val cfg = testNodeConfiguration( val cfg = testNodeConfiguration(
baseDirectory = config.baseDirectory, baseDirectory = config.baseDirectory,
myLegalName = RATES_SERVICE_NAME) 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 networkMap = mockNet.createNode(nodeFactory = NetworkMapNodeFactory, advertisedServices = ServiceInfo(NetworkMapService.type))
val notary = mockNet.createNode(networkMap.network.myAddress, nodeFactory = NotaryNodeFactory, advertisedServices = ServiceInfo(SimpleNotaryService.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 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. // 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) val serviceProviders: List<SimulatedNode> = listOf(notary, ratesOracle, networkMap)

View File

@ -5,7 +5,7 @@ import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.messaging.DataFeed import net.corda.core.messaging.DataFeed
import net.corda.core.node.NodeInfo 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.node.services.*
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializeAsToken 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 * 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. * 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 { companion object {