Fix missing snippets (#1154)

This commit is contained in:
Anthony Keenan 2018-06-29 13:08:09 +01:00 committed by Michele Sollecito
parent 532a03adf8
commit dbd07a8e6c
6 changed files with 443 additions and 83 deletions

View File

@ -67,11 +67,15 @@ Let's take an example of the interest rate swap fixings for our scheduled events
.. container:: codeset
.. literalinclude:: ../../samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/contract/IRS.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 8
.. code-block:: kotlin
override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? {
val nextFixingOf = nextFixingOf() ?: return null
// This is perhaps not how we should determine the time point in the business day, but instead expect the schedule to detail some of these aspects
val instant = suggestInterestRateAnnouncementTimeWindow(index = nextFixingOf.name, source = floatingLeg.indexSource, date = nextFixingOf.forDay).fromTime!!
return ScheduledActivity(flowLogicRefFactory.create("net.corda.irs.flows.FixingFlow\$FixingRoleDecider", thisStateRef), instant)
}
The first thing this does is establish if there are any remaining fixings. If there are none, then it returns ``null``
to indicate that there is no activity to schedule. Otherwise it calculates the ``Instant`` at which the interest rate

View File

@ -101,15 +101,15 @@ class that binds it to the network layer.
Here is an extract from the ``NodeInterestRates.Oracle`` class and supporting types:
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/finance/contracts/FinanceTypes.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
.. sourcecode:: kotlin
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/finance/contracts/FinanceTypes.kt
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
@CordaSerializable
data class FixOf(val name: String, val forDay: LocalDate, val ofTenor: Tenor)
.. sourcecode:: kotlin
/** 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
.. sourcecode:: kotlin
@ -166,11 +166,40 @@ parameter and ``CommandData`` classes.
Let's see how the ``sign`` method for ``NodeInterestRates.Oracle`` is written:
.. literalinclude:: ../../samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 8
.. 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)
}
Here we can see that there are several steps:
@ -192,20 +221,55 @@ Binding to the network
The first step is to create the oracle as a service by annotating its class with ``@CordaService``. Let's see how that's
done:
.. literalinclude:: ../../samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt
:language: kotlin
:start-after: DOCSTART 3
:end-before: DOCEND 3
:dedent: 4
.. 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()
}
The Corda node scans for any class with this annotation and initialises them. The only requirement is that the class provide
a constructor with a single parameter of type ``ServiceHub``.
.. literalinclude:: ../../samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
:dedent: 4
.. 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)
}
}
These two flows leverage the oracle to provide the querying and signing operations. They get reference to the oracle,
which will have already been initialised by the node, using ``ServiceHub.cordaService``. Both flows are annotated with
@ -219,11 +283,41 @@ We mentioned the client sub-flow briefly above. They are the mechanism that cli
use to interact with your oracle. Typically there will be one for querying and one for signing. Let's take a look at
those for ``NodeInterestRates.Oracle``.
.. literalinclude:: ../../samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 4
.. 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
}
}
}
You'll note that the ``FixSignFlow`` requires a ``FilterTransaction`` instance which includes only ``Fix`` commands.
You can find a further explanation of this in :doc:`key-concepts-oracles`. Below you will see how to build such a
@ -238,11 +332,22 @@ The oracle is invoked through sub-flows to query for values, add them to the tra
the transaction signed by the oracle. Following on from the above examples, this is all encapsulated in a sub-flow
called ``RatesFixFlow``. Here's the ``call`` method of that flow.
.. literalinclude:: ../../samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
:dedent: 4
.. 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))
}
As you can see, this:
@ -255,11 +360,31 @@ As you can see, this:
Here's an example of it in action from ``FixingFlow.Fixer``.
.. literalinclude:: ../../samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 4
.. 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)
.. note::
When overriding be careful when making the sub-class an anonymous or inner class (object declarations in Kotlin),
@ -271,24 +396,55 @@ Testing
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.
Make sure the packages you provide include your oracle service, and it automatically be installed in the test nodes.
Make sure the packages you provide include your oracle service, and it will automatically be installed in the test nodes.
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:
.. literalinclude:: ../../samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/OracleNodeTearOffTests.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 4
.. 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
}
}
}
You can then write tests on your mock network to verify the nodes interact with your Oracle correctly.
.. literalinclude:: ../../samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/OracleNodeTearOffTests.kt
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
:dedent: 4
.. 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)
}
See `here <https://github.com/corda/corda/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/OracleNodeTearOffTests.kt>`_ for more examples.

View File

@ -1,4 +1,8 @@
.. highlight:: kotlin
.. raw:: html
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/codesets.js"></script>
Using attachments
=================
@ -57,13 +61,15 @@ Attachments metadata can be used to query, in the similar manner as :doc:`api-va
* Nullability (IS_NULL, NOT_NULL)
* Collection based (IN, NOT_IN)
``And`` and ``or`` operators can be used to build queries of arbitrary complexity. Example of such query:
``And`` and ``or`` operators can be used to build queries of arbitrary complexity. For example:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt
.. container:: codeset
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt
:language: kotlin
:start-after: DOCSTART AttachmentQueryExample1
:end-before: DOCEND AttachmentQueryExample1
:dedent: 12
:dedent: 8
Protocol
--------
@ -99,20 +105,85 @@ and if so, printed out.
.. container:: codeset
.. literalinclude:: ../../samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
.. code-block:: kotlin
fun recipient(rpc: CordaRPCOps, webPort: Int) {
println("Waiting to receive transaction ...")
val stx = rpc.internalVerifiedTransactionsFeed().updates.toBlocking().first()
val wtx = stx.tx
if (wtx.attachments.isNotEmpty()) {
if (wtx.outputs.isNotEmpty()) {
val state = wtx.outputsOfType<AttachmentContract.State>().single()
require(rpc.attachmentExists(state.hash))
// Download the attachment via the Web endpoint.
val connection = URL("http://localhost:$webPort/attachments/${state.hash}").openConnection() as HttpURLConnection
try {
require(connection.responseCode == SC_OK) { "HTTP status code was ${connection.responseCode}" }
require(connection.contentType == APPLICATION_OCTET_STREAM) { "Content-Type header was ${connection.contentType}" }
require(connection.getHeaderField(CONTENT_DISPOSITION) == "attachment; filename=\"${state.hash}.zip\"") {
"Content-Disposition header was ${connection.getHeaderField(CONTENT_DISPOSITION)}"
}
// Write out the entries inside this jar.
println("Attachment JAR contains these entries:")
JarInputStream(connection.inputStream).use { it ->
while (true) {
val e = it.nextJarEntry ?: break
println("Entry> ${e.name}")
it.closeEntry()
}
}
} finally {
connection.disconnect()
}
println("File received - we're happy!\n\nFinal transaction is:\n\n${Emoji.renderIfSupported(wtx)}")
} else {
println("Error: no output state found in ${wtx.id}")
}
} else {
println("Error: no attachments found in ${wtx.id}")
}
}
The sender correspondingly builds a transaction with the attachment, then calls ``FinalityFlow`` to complete the
transaction and send it to the recipient node:
.. container:: codeset
.. literalinclude:: ../../samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
.. code-block:: kotlin
fun sender(rpc: CordaRPCOps, numOfClearBytes: Int = 1024) { // default size 1K.
val (inputStream, hash) = InputStreamAndHash.createInMemoryTestZip(numOfClearBytes, 0)
val executor = Executors.newScheduledThreadPool(2)
try {
sender(rpc, inputStream, hash, executor)
} finally {
executor.shutdown()
}
}
private fun sender(rpc: CordaRPCOps, inputStream: InputStream, hash: SecureHash.SHA256, executor: ScheduledExecutorService) {
// Get the identity key of the other side (the recipient).
val notaryFuture: CordaFuture<Party> = poll(executor, DUMMY_NOTARY_NAME.toString()) { rpc.wellKnownPartyFromX500Name(DUMMY_NOTARY_NAME) }
val otherSideFuture: CordaFuture<Party> = poll(executor, DUMMY_BANK_B_NAME.toString()) { rpc.wellKnownPartyFromX500Name(DUMMY_BANK_B_NAME) }
// Make sure we have the file in storage
if (!rpc.attachmentExists(hash)) {
inputStream.use {
val avail = inputStream.available()
val id = rpc.uploadAttachment(it)
require(hash == id) { "Id was '$id' instead of '$hash'" }
}
require(rpc.attachmentExists(hash))
}
val flowHandle = rpc.startTrackedFlow(::AttachmentDemoFlow, otherSideFuture.get(), notaryFuture.get(), hash)
flowHandle.progress.subscribe(::println)
val stx = flowHandle.returnValue.getOrThrow()
println("Sent ${stx.id}")
}
This side is a bit more complex. Firstly it looks up its counterparty by name in the network map. Then, if the node
doesn't already have the attachment in its storage, we upload it from a JAR resource and check the hash was what

View File

@ -1,4 +1,8 @@
.. highlight:: kotlin
.. raw:: html
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/codesets.js"></script>
Writing a custom notary service (experimental)
==============================================
@ -12,19 +16,77 @@ Similarly to writing an oracle service, the first step is to create a service cl
with ``@CordaService``. The Corda node scans for any class with this annotation and initialises them. The custom notary
service class should provide a constructor with two parameters of types ``AppServiceHub`` and ``PublicKey``.
.. literalinclude:: ../../samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt
:language: kotlin
:start-after: START 1
:end-before: END 1
.. container:: codeset
.. code-block:: kotlin
@CordaService
class MyCustomValidatingNotaryService(override val services: AppServiceHub, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
override val uniquenessProvider = PersistentUniquenessProvider(services.clock)
override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = MyValidatingNotaryFlow(otherPartySession, this)
override fun start() {}
override fun stop() {}
}
The next step is to write a notary service flow. You are free to copy and modify the existing built-in flows such
as ``ValidatingNotaryFlow``, ``NonValidatingNotaryFlow``, or implement your own from scratch (following the
``NotaryFlow.Service`` template). Below is an example of a custom flow for a *validating* notary service:
.. literalinclude:: ../../samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt
:language: kotlin
:start-after: START 2
:end-before: END 2
.. container:: codeset
.. code-block:: kotlin
class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidatingNotaryService) : NotaryServiceFlow(otherSide, service) {
/**
* The received transaction is checked for contract-validity, for which the caller also has to to reveal the whole
* transaction dependency chain.
*/
@Suspendable
override fun validateRequest(requestPayload: NotarisationPayload): TransactionParts {
try {
val stx = requestPayload.signedTransaction
validateRequestSignature(NotarisationRequest(stx.inputs, stx.id), requestPayload.requestSignature)
val notary = stx.notary
checkNotary(notary)
verifySignatures(stx)
resolveAndContractVerify(stx)
val timeWindow: TimeWindow? = if (stx.coreTransaction is WireTransaction) stx.tx.timeWindow else null
return TransactionParts(stx.id, stx.inputs, timeWindow, notary!!)
} catch (e: Exception) {
throw when (e) {
is TransactionVerificationException,
is SignatureException -> NotaryInternalException(NotaryError.TransactionInvalid(e))
else -> e
}
}
}
@Suspendable
private fun resolveAndContractVerify(stx: SignedTransaction) {
subFlow(ResolveTransactionsFlow(stx, otherSideSession))
stx.verify(serviceHub, false)
customVerify(stx)
}
private fun verifySignatures(stx: SignedTransaction) {
val transactionWithSignatures = stx.resolveTransactionWithSignatures(serviceHub)
checkSignatures(transactionWithSignatures)
}
private fun checkSignatures(tx: TransactionWithSignatures) {
try {
tx.verifySignaturesExcept(service.notaryIdentityKey)
} catch (e: SignatureException) {
throw NotaryInternalException(NotaryError.TransactionInvalid(e))
}
}
private fun customVerify(stx: SignedTransaction) {
// Add custom verification logic
}
}
To enable the service, add the following to the node configuration:

View File

@ -17,10 +17,42 @@ Just define a new flow that wraps the SendTransactionFlow/ReceiveTransactionFlow
.. container:: codeset
.. literalinclude:: ../../samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
.. code-block:: kotlin
@InitiatedBy(Requester::class)
class AutoOfferAcceptor(otherSideSession: FlowSession) : Acceptor(otherSideSession) {
@Suspendable
override fun call(): SignedTransaction {
val finalTx = super.call()
// Our transaction is now committed to the ledger, so report it to our regulator. We use a custom flow
// that wraps SendTransactionFlow to allow the receiver to customise how ReceiveTransactionFlow is run,
// and because in a real life app you'd probably have more complex logic here e.g. describing why the report
// was filed, checking that the reportee is a regulated entity and not some random node from the wrong
// country and so on.
val regulator = serviceHub.identityService.partiesFromName("Regulator", true).single()
subFlow(ReportToRegulatorFlow(regulator, finalTx))
return finalTx
}
}
@InitiatingFlow
class ReportToRegulatorFlow(private val regulator: Party, private val finalTx: SignedTransaction) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val session = initiateFlow(regulator)
subFlow(SendTransactionFlow(session, finalTx))
}
}
@InitiatedBy(ReportToRegulatorFlow::class)
class ReceiveRegulatoryReportFlow(private val otherSideSession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
// Start the matching side of SendTransactionFlow above, but tell it to record all visible states even
// though they (as far as the node can tell) are nothing to do with us.
subFlow(ReceiveTransactionFlow(otherSideSession, true, StatesToRecord.ALL_VISIBLE))
}
}
In this example, the ``AutoOfferFlow`` is the business logic, and we define two very short and simple flows to send
the transaction to the regulator. There are two important aspects to note here:

View File

@ -1,3 +1,9 @@
.. highlight:: kotlin
.. raw:: html
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/codesets.js"></script>
Transaction tear-offs
=====================
@ -41,13 +47,42 @@ transaction components is exactly the same. Note that unlike ``WireTransaction``
:end-before: DOCEND 3
:dedent: 4
The following code snippet is taken from ``NodeInterestRates.kt`` and implements a signing part of an Oracle.
The following code snippet is taken from the IRS Demo and implements a signing part of an Oracle.
.. literalinclude:: ../../samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 8
.. 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)
}
.. note:: The way the ``FilteredTransaction`` is constructed ensures that after signing of the root hash it's impossible to add or remove
components (leaves). However, it can happen that having transaction with multiple commands one party reveals only subset of them to the Oracle.