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
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

View File

@ -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()

View File

@ -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.

View File

@ -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)
}

View File

@ -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)

View File

@ -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}")
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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))

View File

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

View File

@ -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"
};
});

View File

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

View File

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

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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

View File

@ -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())
}

View File

@ -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

View File

@ -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)

View File

@ -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 {