2016-03-11 13:42:11 +01:00
|
|
|
.. highlight:: kotlin
|
|
|
|
.. raw:: html
|
|
|
|
|
|
|
|
<script type="text/javascript" src="_static/jquery.js"></script>
|
|
|
|
<script type="text/javascript" src="_static/codesets.js"></script>
|
|
|
|
|
|
|
|
Writing oracle services
|
|
|
|
=======================
|
|
|
|
|
|
|
|
This article covers *oracles*: network services that link the ledger to the outside world by providing facts that
|
|
|
|
affect the validity of transactions.
|
|
|
|
|
2016-08-24 15:38:43 +02:00
|
|
|
The current prototype includes an example oracle that provides an interest rate fixing service. It is used by the
|
|
|
|
IRS trading demo app.
|
2016-03-11 13:42:11 +01:00
|
|
|
|
2017-01-06 11:05:37 +00:00
|
|
|
Introduction to oracles
|
|
|
|
-----------------------
|
2016-03-11 13:42:11 +01:00
|
|
|
|
|
|
|
Oracles are a key concept in the block chain/decentralised ledger space. They can be essential for many kinds of
|
2017-10-01 23:33:15 +01:00
|
|
|
application, because we often wish to condition the validity of a transaction on some fact being true or false, but the ledger itself
|
2016-03-11 13:42:11 +01:00
|
|
|
has a design that is essentially functional: all transactions are *pure* and *immutable*. Phrased another way, a
|
2017-10-01 23:33:15 +01:00
|
|
|
contract cannot perform any input/output or depend on any state outside of the transaction itself. For example, there is no
|
|
|
|
way to download a web page or interact with the user from within a contract. It must be this way because everyone must
|
|
|
|
be able to independently check a transaction and arrive at an identical conclusion regarding its validity for the ledger to maintain its
|
2016-03-11 13:42:11 +01:00
|
|
|
integrity: if a transaction could evaluate to "valid" on one computer and then "invalid" a few minutes later on a
|
|
|
|
different computer, the entire shared ledger concept wouldn't work.
|
|
|
|
|
2017-10-01 23:33:15 +01:00
|
|
|
But transaction validity does often depend on data from the outside world - verifying that an
|
2016-03-11 13:42:11 +01:00
|
|
|
interest rate swap is paying out correctly may require data on interest rates, verifying that a loan has reached
|
|
|
|
maturity requires knowledge about the current time, knowing which side of a bet receives the payment may require
|
2017-10-01 23:33:15 +01:00
|
|
|
arbitrary facts about the real world (e.g. the bankruptcy or solvency of a company or country), and so on.
|
2016-03-11 13:42:11 +01:00
|
|
|
|
|
|
|
We can solve this problem by introducing services that create digitally signed data structures which assert facts.
|
|
|
|
These structures can then be used as an input to a transaction and distributed with the transaction data itself. Because
|
|
|
|
the statements are themselves immutable and signed, it is impossible for an oracle to change its mind later and
|
|
|
|
invalidate transactions that were previously found to be valid. In contrast, consider what would happen if a contract
|
|
|
|
could do an HTTP request: it's possible that an answer would change after being downloaded, resulting in loss of
|
2017-10-01 23:33:15 +01:00
|
|
|
consensus.
|
2016-03-11 13:42:11 +01:00
|
|
|
|
|
|
|
The two basic approaches
|
2017-01-06 11:05:37 +00:00
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
2016-03-11 13:42:11 +01:00
|
|
|
|
|
|
|
The architecture provides two ways of implementing oracles with different tradeoffs:
|
|
|
|
|
|
|
|
1. Using commands
|
|
|
|
2. Using attachments
|
|
|
|
|
|
|
|
When a fact is encoded in a command, it is embedded in the transaction itself. The oracle then acts as a co-signer to
|
|
|
|
the entire transaction. The oracle's signature is valid only for that transaction, and thus even if a fact (like a
|
|
|
|
stock price) does not change, every transaction that incorporates that fact must go back to the oracle for signing.
|
|
|
|
|
2016-08-24 15:38:43 +02:00
|
|
|
When a fact is encoded as an attachment, it is a separate object to the transaction and is referred to by hash.
|
2016-03-11 13:42:11 +01:00
|
|
|
Nodes download attachments from peers at the same time as they download transactions, unless of course the node has
|
|
|
|
already seen that attachment, in which case it won't fetch it again. Contracts have access to the contents of
|
2016-08-24 15:38:43 +02:00
|
|
|
attachments when they run.
|
|
|
|
|
|
|
|
.. note:: Currently attachments do not support digital signing, but this is a planned feature.
|
2016-03-11 13:42:11 +01:00
|
|
|
|
|
|
|
As you can see, both approaches share a few things: they both allow arbitrary binary data to be provided to transactions
|
|
|
|
(and thus contracts). The primary difference is whether the data is a freely reusable, standalone object or whether it's
|
|
|
|
integrated with a transaction.
|
|
|
|
|
|
|
|
Here's a quick way to decide which approach makes more sense for your data source:
|
|
|
|
|
|
|
|
* Is your data *continuously changing*, like a stock price, the current time, etc? If yes, use a command.
|
|
|
|
* Is your data *commercially valuable*, like a feed which you are not allowed to resell unless it's incorporated into
|
|
|
|
a business deal? If yes, use a command, so you can charge money for signing the same fact in each unique business
|
|
|
|
context.
|
|
|
|
* Is your data *very small*, like a single number? If yes, use a command.
|
|
|
|
* Is your data *large*, *static* and *commercially worthless*, for instance, a holiday calendar? If yes, use an
|
|
|
|
attachment.
|
|
|
|
* Is your data *intended for human consumption*, like a PDF of legal prose, or an Excel spreadsheet? If yes, use an
|
|
|
|
attachment.
|
|
|
|
|
2016-08-24 15:38:43 +02:00
|
|
|
Asserting continuously varying data
|
2017-01-06 11:05:37 +00:00
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2016-03-11 13:42:11 +01:00
|
|
|
|
2016-08-24 15:38:43 +02:00
|
|
|
Let's look at the interest rates oracle that can be found in the ``NodeInterestRates`` file. This is an example of
|
|
|
|
an oracle that uses a command because the current interest rate fix is a constantly changing fact.
|
2016-03-11 13:42:11 +01:00
|
|
|
|
2017-10-01 23:33:15 +01:00
|
|
|
The obvious way to implement such a service is this:
|
2016-03-11 13:42:11 +01:00
|
|
|
|
2016-08-24 15:38:43 +02:00
|
|
|
1. The creator of the transaction that depends on the interest rate sends it to the oracle.
|
|
|
|
2. The oracle inserts a command with the rate and signs the transaction.
|
|
|
|
3. The oracle sends it back.
|
2016-03-11 13:42:11 +01:00
|
|
|
|
2016-08-24 15:38:43 +02:00
|
|
|
But this has a problem - it would mean that the oracle has to be the first entity to sign the transaction, which might impose
|
|
|
|
ordering constraints we don't want to deal with (being able to get all parties to sign in parallel is a very nice thing).
|
|
|
|
So the way we actually implement it is like this:
|
2016-03-11 13:42:11 +01:00
|
|
|
|
2016-08-24 15:38:43 +02:00
|
|
|
1. The creator of the transaction that depends on the interest rate asks for the current rate. They can abort at this point
|
|
|
|
if they want to.
|
|
|
|
2. They insert a command with that rate and the time it was obtained into the transaction.
|
2016-11-25 13:35:05 +00:00
|
|
|
3. They then send it to the oracle for signing, along with everyone else, potentially in parallel. The oracle checks that
|
|
|
|
the command has the correct data for the asserted time, and signs if so.
|
2016-03-11 13:42:11 +01:00
|
|
|
|
|
|
|
This same technique can be adapted to other types of oracle.
|
|
|
|
|
2016-08-24 15:38:43 +02:00
|
|
|
The oracle consists of a core class that implements the query/sign operations (for easy unit testing), and then a separate
|
|
|
|
class that binds it to the network layer.
|
2016-03-11 13:42:11 +01:00
|
|
|
|
2016-11-25 13:35:05 +00:00
|
|
|
Here is an extract from the ``NodeInterestRates.Oracle`` class and supporting types:
|
2016-03-11 13:42:11 +01:00
|
|
|
|
2018-06-29 13:08:09 +01:00
|
|
|
.. sourcecode:: kotlin
|
|
|
|
|
|
|
|
@CordaSerializable
|
|
|
|
data class FixOf(val name: String, val forDay: LocalDate, val ofTenor: Tenor)
|
|
|
|
|
|
|
|
.. sourcecode:: kotlin
|
2016-03-11 13:42:11 +01:00
|
|
|
|
2018-06-29 13:08:09 +01:00
|
|
|
/** A [Fix] represents a named interest rate, on a given day, for a given duration. It can be embedded in a tx. */
|
|
|
|
data class Fix(val of: FixOf, val value: BigDecimal) : CommandData
|
2016-03-11 13:42:11 +01:00
|
|
|
|
2017-10-01 23:33:15 +01:00
|
|
|
.. sourcecode:: kotlin
|
2016-03-11 13:42:11 +01:00
|
|
|
|
|
|
|
class Oracle {
|
2017-10-01 23:33:15 +01:00
|
|
|
fun query(queries: List<FixOf>): List<Fix>
|
2016-03-11 13:42:11 +01:00
|
|
|
|
2017-10-01 23:33:15 +01:00
|
|
|
fun sign(ftx: FilteredTransaction): TransactionSignature
|
2016-03-11 13:42:11 +01:00
|
|
|
}
|
|
|
|
|
2017-10-01 23:33:15 +01:00
|
|
|
The fix contains a timestamp (the ``forDay`` field) that identifies the version of the data being requested. Since
|
|
|
|
there can be an arbitrary delay between a fix being requested via ``query`` and the signature being requested via
|
|
|
|
``sign``, this timestamp allows the Oracle to know which, potentially historical, value it is being asked to sign for. This is an
|
|
|
|
important technique for continuously varying data.
|
2016-11-25 13:35:05 +00:00
|
|
|
|
|
|
|
Hiding transaction data from the oracle
|
2017-01-06 11:05:37 +00:00
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2016-11-25 13:35:05 +00:00
|
|
|
|
|
|
|
Because the transaction is sent to the oracle for signing, ordinarily the oracle would be able to see the entire contents
|
|
|
|
of that transaction including the inputs, output contract states and all the commands, not just the one (in this case)
|
2017-10-01 23:33:15 +01:00
|
|
|
relevant command. This is an obvious privacy leak for the other participants. We currently solve this using a
|
|
|
|
``FilteredTransaction``, which implements a Merkle Tree. These reveal only the necessary parts of the transaction to the
|
|
|
|
oracle but still allow it to sign it by providing the Merkle hashes for the remaining parts. See :doc:`key-concepts-oracles`
|
|
|
|
for more details.
|
2016-03-11 13:42:11 +01:00
|
|
|
|
2016-08-24 15:38:43 +02:00
|
|
|
Pay-per-play oracles
|
2017-01-06 11:05:37 +00:00
|
|
|
~~~~~~~~~~~~~~~~~~~~
|
2016-03-11 13:42:11 +01:00
|
|
|
|
2016-08-24 15:38:43 +02:00
|
|
|
Because the signature covers the transaction, and transactions may end up being forwarded anywhere, the fact itself
|
|
|
|
is independently checkable. However, this approach can still be useful when the data itself costs money, because the act
|
|
|
|
of issuing the signature in the first place can be charged for (e.g. by requiring the submission of a fresh
|
|
|
|
``Cash.State`` that has been re-assigned to a key owned by the oracle service). Because the signature covers the
|
2017-10-01 23:33:15 +01:00
|
|
|
*transaction* and not only the *fact*, this allows for a kind of weak pseudo-DRM over data feeds. Whilst a
|
2016-08-24 15:38:43 +02:00
|
|
|
contract could in theory include a transaction parsing and signature checking library, writing a contract in this way
|
|
|
|
would be conclusive evidence of intent to disobey the rules of the service (*res ipsa loquitur*). In an environment
|
|
|
|
where parties are legally identifiable, usage of such a contract would by itself be sufficient to trigger some sort of
|
|
|
|
punishment.
|
2016-11-25 13:35:05 +00:00
|
|
|
|
|
|
|
Implementing an oracle with continuously varying data
|
2017-01-06 11:05:37 +00:00
|
|
|
-----------------------------------------------------
|
2016-11-25 13:35:05 +00:00
|
|
|
|
|
|
|
Implement the core classes
|
2017-01-06 11:05:37 +00:00
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2016-11-25 13:35:05 +00:00
|
|
|
|
|
|
|
The key is to implement your oracle in a similar way to the ``NodeInterestRates.Oracle`` outline we gave above with
|
2017-10-01 23:33:15 +01:00
|
|
|
both a ``query`` and a ``sign`` method. Typically you would want one class that encapsulates the parameters to the ``query``
|
|
|
|
method (``FixOf``, above), and a ``CommandData`` implementation (``Fix``, above) that encapsulates both an instance of
|
2016-11-25 13:35:05 +00:00
|
|
|
that parameter class and an instance of whatever the result of the ``query`` is (``BigDecimal`` above).
|
|
|
|
|
2017-10-01 23:33:15 +01:00
|
|
|
The ``NodeInterestRates.Oracle`` allows querying for multiple ``Fix`` objects but that is not necessary and is
|
|
|
|
provided for the convenience of callers who need multiple fixes and want to be able to do it all in one query request.
|
2016-11-25 13:35:05 +00:00
|
|
|
|
|
|
|
Assuming you have a data source and can query it, it should be very easy to implement your ``query`` method and the
|
|
|
|
parameter and ``CommandData`` classes.
|
|
|
|
|
|
|
|
Let's see how the ``sign`` method for ``NodeInterestRates.Oracle`` is written:
|
|
|
|
|
2018-06-29 13:08:09 +01:00
|
|
|
.. container:: codeset
|
|
|
|
|
|
|
|
.. code-block:: kotlin
|
|
|
|
|
|
|
|
fun sign(ftx: FilteredTransaction): TransactionSignature {
|
|
|
|
ftx.verify()
|
|
|
|
// Performing validation of obtained filtered components.
|
|
|
|
fun commandValidator(elem: Command<*>): Boolean {
|
|
|
|
require(services.myInfo.legalIdentities.first().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)
|
|
|
|
throw UnknownFix(fix.of)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
fun check(elem: Any): Boolean {
|
|
|
|
return when (elem) {
|
|
|
|
is Command<*> -> commandValidator(elem)
|
|
|
|
else -> throw IllegalArgumentException("Oracle received data of different type than expected.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
require(ftx.checkWithFun(::check))
|
|
|
|
ftx.checkCommandVisibility(services.myInfo.legalIdentities.first().owningKey)
|
|
|
|
// 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, services.myInfo.legalIdentities.first().owningKey)
|
|
|
|
}
|
2016-11-25 13:35:05 +00:00
|
|
|
|
|
|
|
Here we can see that there are several steps:
|
|
|
|
|
|
|
|
1. Ensure that the transaction we have been sent is indeed valid and passes verification, even though we cannot see all
|
2017-10-01 23:33:15 +01:00
|
|
|
of it
|
2016-11-25 13:35:05 +00:00
|
|
|
2. Check that we only received commands as expected, and each of those commands expects us to sign for them and is of
|
2017-10-01 23:33:15 +01:00
|
|
|
the expected type (``Fix`` here)
|
2016-11-25 13:35:05 +00:00
|
|
|
3. Iterate over each of the commands we identified in the last step and check that the data they represent matches
|
|
|
|
exactly our data source. The final step, assuming we have got this far, is to generate a signature for the
|
2017-10-01 23:33:15 +01:00
|
|
|
transaction and return it
|
2016-11-25 13:35:05 +00:00
|
|
|
|
2017-05-19 16:14:48 +01:00
|
|
|
Binding to the network
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
2016-11-25 13:35:05 +00:00
|
|
|
|
|
|
|
.. note:: Before reading any further, we advise that you understand the concept of flows and how to write them and use
|
|
|
|
them. See :doc:`flow-state-machines`. Likewise some understanding of Cordapps, plugins and services will be helpful.
|
2017-06-05 13:37:23 +01:00
|
|
|
See :doc:`running-a-node`.
|
2016-11-25 13:35:05 +00:00
|
|
|
|
2017-05-19 16:14:48 +01:00
|
|
|
The first step is to create the oracle as a service by annotating its class with ``@CordaService``. Let's see how that's
|
|
|
|
done:
|
2016-11-25 13:35:05 +00:00
|
|
|
|
2018-06-29 13:08:09 +01:00
|
|
|
.. container:: codeset
|
|
|
|
|
|
|
|
.. code-block:: kotlin
|
|
|
|
|
|
|
|
@CordaService
|
|
|
|
class Oracle(private val services: AppServiceHub) : 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.
|
|
|
|
addDefaultFixes()
|
|
|
|
}
|
2016-11-25 13:35:05 +00:00
|
|
|
|
2017-05-19 16:14:48 +01:00
|
|
|
The Corda node scans for any class with this annotation and initialises them. The only requirement is that the class provide
|
2017-09-14 09:00:02 +01:00
|
|
|
a constructor with a single parameter of type ``ServiceHub``.
|
2016-11-25 13:35:05 +00:00
|
|
|
|
2018-06-29 13:08:09 +01:00
|
|
|
.. container:: codeset
|
|
|
|
|
|
|
|
.. code-block:: kotlin
|
|
|
|
|
|
|
|
@InitiatedBy(RatesFixFlow.FixSignFlow::class)
|
|
|
|
class FixSignHandler(private val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
|
|
|
@Suspendable
|
|
|
|
override fun call() {
|
|
|
|
val request = otherPartySession.receive<RatesFixFlow.SignRequest>().unwrap { it }
|
|
|
|
val oracle = serviceHub.cordaService(Oracle::class.java)
|
|
|
|
otherPartySession.send(oracle.sign(request.ftx))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@InitiatedBy(RatesFixFlow.FixQueryFlow::class)
|
|
|
|
class FixQueryHandler(private val otherPartySession: FlowSession) : 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() {
|
|
|
|
val request = otherPartySession.receive<RatesFixFlow.QueryRequest>().unwrap { it }
|
|
|
|
progressTracker.currentStep = RECEIVED
|
|
|
|
val oracle = serviceHub.cordaService(Oracle::class.java)
|
|
|
|
val answers = oracle.query(request.queries)
|
|
|
|
progressTracker.currentStep = SENDING
|
|
|
|
otherPartySession.send(answers)
|
|
|
|
}
|
|
|
|
}
|
2016-11-25 13:35:05 +00:00
|
|
|
|
2017-05-19 16:14:48 +01:00
|
|
|
These two flows leverage the oracle to provide the querying and signing operations. They get reference to the oracle,
|
2017-10-01 23:33:15 +01:00
|
|
|
which will have already been initialised by the node, using ``ServiceHub.cordaService``. Both flows are annotated with
|
2017-05-19 16:14:48 +01:00
|
|
|
``@InitiatedBy``. This tells the node which initiating flow (which are discussed in the next section) they are meant to
|
|
|
|
be executed with.
|
2016-11-25 13:35:05 +00:00
|
|
|
|
2017-05-19 16:14:48 +01:00
|
|
|
Providing sub-flows for querying and signing
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2016-11-25 13:35:05 +00:00
|
|
|
|
|
|
|
We mentioned the client sub-flow briefly above. They are the mechanism that clients, in the form of other flows, will
|
2017-10-01 23:33:15 +01:00
|
|
|
use to interact with your oracle. Typically there will be one for querying and one for signing. Let's take a look at
|
2016-11-25 13:35:05 +00:00
|
|
|
those for ``NodeInterestRates.Oracle``.
|
|
|
|
|
2018-06-29 13:08:09 +01:00
|
|
|
.. container:: codeset
|
|
|
|
|
|
|
|
.. code-block:: kotlin
|
|
|
|
|
|
|
|
@InitiatingFlow
|
|
|
|
class FixQueryFlow(val fixOf: FixOf, val oracle: Party) : FlowLogic<Fix>() {
|
|
|
|
@Suspendable
|
|
|
|
override fun call(): Fix {
|
|
|
|
val oracleSession = initiateFlow(oracle)
|
|
|
|
// TODO: add deadline to receive
|
|
|
|
val resp = oracleSession.sendAndReceive<List<Fix>>(QueryRequest(listOf(fixOf)))
|
|
|
|
|
|
|
|
return resp.unwrap {
|
|
|
|
val fix = it.first()
|
|
|
|
// Check the returned fix is for what we asked for.
|
|
|
|
check(fix.of == fixOf)
|
|
|
|
fix
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@InitiatingFlow
|
|
|
|
class FixSignFlow(val tx: TransactionBuilder, val oracle: Party,
|
|
|
|
val partialMerkleTx: FilteredTransaction) : FlowLogic<TransactionSignature>() {
|
|
|
|
@Suspendable
|
|
|
|
override fun call(): TransactionSignature {
|
|
|
|
val oracleSession = initiateFlow(oracle)
|
|
|
|
val resp = oracleSession.sendAndReceive<TransactionSignature>(SignRequest(partialMerkleTx))
|
|
|
|
return resp.unwrap { sig ->
|
|
|
|
check(oracleSession.counterparty.owningKey.isFulfilledBy(listOf(sig.by)))
|
|
|
|
tx.toWireTransaction(serviceHub).checkSignature(sig)
|
|
|
|
sig
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-11-25 13:35:05 +00:00
|
|
|
|
2017-02-03 14:02:51 +00:00
|
|
|
You'll note that the ``FixSignFlow`` requires a ``FilterTransaction`` instance which includes only ``Fix`` commands.
|
2017-10-01 23:33:15 +01:00
|
|
|
You can find a further explanation of this in :doc:`key-concepts-oracles`. Below you will see how to build such a
|
|
|
|
transaction with hidden fields.
|
2017-02-03 14:02:51 +00:00
|
|
|
|
|
|
|
.. _filtering_ref:
|
2016-11-25 13:35:05 +00:00
|
|
|
|
|
|
|
Using an oracle
|
2017-01-06 11:05:37 +00:00
|
|
|
---------------
|
2016-11-25 13:35:05 +00:00
|
|
|
|
|
|
|
The oracle is invoked through sub-flows to query for values, add them to the transaction as commands and then get
|
2017-01-06 11:05:37 +00:00
|
|
|
the transaction signed by the oracle. Following on from the above examples, this is all encapsulated in a sub-flow
|
2016-11-25 13:35:05 +00:00
|
|
|
called ``RatesFixFlow``. Here's the ``call`` method of that flow.
|
|
|
|
|
2018-06-29 13:08:09 +01:00
|
|
|
.. container:: codeset
|
|
|
|
|
|
|
|
.. code-block:: kotlin
|
|
|
|
|
|
|
|
@Suspendable
|
|
|
|
override fun call(): TransactionSignature {
|
|
|
|
progressTracker.currentStep = progressTracker.steps[1]
|
|
|
|
val fix = subFlow(FixQueryFlow(fixOf, oracle))
|
|
|
|
progressTracker.currentStep = WORKING
|
|
|
|
checkFixIsNearExpected(fix)
|
|
|
|
tx.addCommand(fix, oracle.owningKey)
|
|
|
|
beforeSigning(fix)
|
|
|
|
progressTracker.currentStep = SIGNING
|
|
|
|
val mtx = tx.toWireTransaction(serviceHub).buildFilteredTransaction(Predicate { filtering(it) })
|
|
|
|
return subFlow(FixSignFlow(tx, oracle, mtx))
|
|
|
|
}
|
2016-11-25 13:35:05 +00:00
|
|
|
|
|
|
|
As you can see, this:
|
|
|
|
|
2017-10-01 23:33:15 +01:00
|
|
|
1. Queries the oracle for the fact using the client sub-flow for querying defined above
|
|
|
|
2. Does some quick validation
|
|
|
|
3. Adds the command to the transaction containing the fact to be signed for by the oracle
|
|
|
|
4. Calls an extension point that allows clients to generate output states based on the fact from the oracle
|
|
|
|
5. Builds filtered transaction based on filtering function extended from ``RatesFixFlow``
|
|
|
|
6. Requests the signature from the oracle using the client sub-flow for signing from above
|
2016-11-25 13:35:05 +00:00
|
|
|
|
|
|
|
Here's an example of it in action from ``FixingFlow.Fixer``.
|
|
|
|
|
2018-06-29 13:08:09 +01:00
|
|
|
.. container:: codeset
|
|
|
|
|
|
|
|
.. code-block:: kotlin
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
// We set the transaction's time-window: it may be that none of the contracts need this!
|
|
|
|
// But it can't hurt to have one.
|
|
|
|
ptx.setTimeWindow(serviceHub.clock.instant(), 30.seconds)
|
|
|
|
}
|
|
|
|
|
|
|
|
@Suspendable
|
|
|
|
override fun filtering(elem: Any): Boolean {
|
|
|
|
return when (elem) {
|
|
|
|
// Only expose Fix commands in which the oracle is on the list of requested signers
|
|
|
|
// to the oracle node, to avoid leaking privacy
|
|
|
|
is Command<*> -> handshake.payload.oracle.owningKey in elem.signers && elem.value is Fix
|
|
|
|
else -> false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
val sig = subFlow(addFixing)
|
2017-02-03 14:02:51 +00:00
|
|
|
|
|
|
|
.. note::
|
|
|
|
When overriding be careful when making the sub-class an anonymous or inner class (object declarations in Kotlin),
|
|
|
|
because that kind of classes can access variables from the enclosing scope and cause serialization problems when
|
|
|
|
checkpointed.
|
2017-06-02 15:47:20 +01:00
|
|
|
|
|
|
|
Testing
|
|
|
|
-------
|
|
|
|
|
2018-04-06 09:22:58 +01:00
|
|
|
The ``MockNetwork`` allows the creation of ``MockNode`` instances, which are simplified nodes which can be used for
|
|
|
|
testing (see :doc:`api-testing`). When creating the ``MockNetwork`` you supply a list of packages to scan for CorDapps.
|
2018-06-29 13:08:09 +01:00
|
|
|
Make sure the packages you provide include your oracle service, and it will automatically be installed in the test nodes.
|
2018-04-06 09:22:58 +01:00
|
|
|
Then you can create an oracle node on the ``MockNetwork`` and insert any initialisation logic you want to use. In this
|
|
|
|
case, our ``Oracle`` service is in the ``net.corda.irs.api`` package, so the following test setup will install
|
|
|
|
the service in each node. Then an oracle node with an oracle service which is initialised with some data is created on
|
|
|
|
the mock network:
|
|
|
|
|
2018-06-29 13:08:09 +01:00
|
|
|
.. container:: codeset
|
|
|
|
|
|
|
|
.. code-block:: kotlin
|
|
|
|
|
|
|
|
fun setUp() {
|
|
|
|
mockNet = MockNetwork(cordappPackages = listOf("net.corda.finance.contracts", "net.corda.irs"))
|
|
|
|
aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
|
|
|
oracleNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME)).apply {
|
|
|
|
transaction {
|
|
|
|
services.cordaService(NodeInterestRates.Oracle::class.java).knownFixes = TEST_DATA
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-04-06 09:22:58 +01:00
|
|
|
|
|
|
|
You can then write tests on your mock network to verify the nodes interact with your Oracle correctly.
|
|
|
|
|
2018-06-29 13:08:09 +01:00
|
|
|
.. container:: codeset
|
|
|
|
|
|
|
|
.. code-block:: kotlin
|
|
|
|
|
|
|
|
@Test
|
|
|
|
fun verify_that_the_oracle_signs_the_transaction_if_the_interest_rate_within_allowed_limit() {
|
|
|
|
// Create a partial transaction
|
|
|
|
val tx = TransactionBuilder(DUMMY_NOTARY)
|
|
|
|
.withItems(TransactionState(1000.DOLLARS.CASH issuedBy dummyCashIssuer.party ownedBy alice.party, Cash.PROGRAM_ID, DUMMY_NOTARY))
|
|
|
|
// Specify the rate we wish to get verified by the oracle
|
|
|
|
val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
|
|
|
|
|
|
|
|
// Create a new flow for the fix
|
|
|
|
val flow = FilteredRatesFlow(tx, oracle, fixOf, BigDecimal("0.675"), BigDecimal("0.1"))
|
|
|
|
// Run the mock network and wait for a result
|
|
|
|
mockNet.runNetwork()
|
|
|
|
val future = aliceNode.startFlow(flow)
|
|
|
|
mockNet.runNetwork()
|
|
|
|
future.getOrThrow()
|
|
|
|
|
|
|
|
// We should now have a valid rate on our tx from the oracle.
|
|
|
|
val fix = tx.toWireTransaction(aliceNode.services).commands.map { it }.first()
|
|
|
|
assertEquals(fixOf, (fix.value as Fix).of)
|
|
|
|
// Check that the response contains the valid rate, which is within the supplied tolerance
|
|
|
|
assertEquals(BigDecimal("0.678"), (fix.value as Fix).value)
|
|
|
|
// Check that the transaction has been signed by the oracle
|
|
|
|
assertContains(fix.signers, oracle.owningKey)
|
|
|
|
}
|
2018-04-06 09:22:58 +01:00
|
|
|
|
|
|
|
See `here <https://github.com/corda/corda/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/OracleNodeTearOffTests.kt>`_ for more examples.
|