mirror of
https://github.com/corda/corda.git
synced 2025-01-13 08:20:01 +00:00
Merge branch 'ak' into anthony-os-merge-20180406
# Conflicts: # docs/source/api-persistence.rst
This commit is contained in:
commit
2999f0eba2
@ -145,6 +145,8 @@ which is then referenced within a custom flow:
|
||||
:start-after: DOCSTART TopupIssuer
|
||||
:end-before: DOCEND TopupIssuer
|
||||
|
||||
For examples on testing ``@CordaService`` implementations, see the oracle example :doc:`here <oracles>`
|
||||
|
||||
.. _database_migration_ref:
|
||||
|
||||
Database Migration
|
||||
|
@ -59,6 +59,8 @@ object, or by using named paramters in Kotlin:
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
val network = MockNetwork(
|
||||
// A list of packages to scan. Any contracts, flows and Corda services within these
|
||||
// packages will be automatically available to any nodes within the mock network
|
||||
cordappPackages = listOf("my.cordapp.package", "my.other.cordapp.package"),
|
||||
// If true then each node will be run in its own thread. This can result in race conditions in your
|
||||
// code if not carefully written, but is more realistic and may help if you have flows in your app that
|
||||
@ -77,7 +79,10 @@ object, or by using named paramters in Kotlin:
|
||||
// notary implementations.
|
||||
servicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random())
|
||||
|
||||
val network2 = MockNetwork(listOf("my.cordapp.package", "my.other.cordapp.package"), MockNetworkParameters(
|
||||
val network2 = MockNetwork(
|
||||
// A list of packages to scan. Any contracts, flows and Corda services within these
|
||||
// packages will be automatically available to any nodes within the mock network
|
||||
listOf("my.cordapp.package", "my.other.cordapp.package"), MockNetworkParameters(
|
||||
// If true then each node will be run in its own thread. This can result in race conditions in your
|
||||
// code if not carefully written, but is more realistic and may help if you have flows in your app that
|
||||
// do long blocking operations.
|
||||
@ -98,7 +103,10 @@ object, or by using named paramters in Kotlin:
|
||||
|
||||
.. sourcecode:: java
|
||||
|
||||
MockNetwork network = MockNetwork(ImmutableList.of("my.cordapp.package", "my.other.cordapp.package"),
|
||||
MockNetwork network = MockNetwork(
|
||||
// A list of packages to scan. Any contracts, flows and Corda services within these
|
||||
// packages will be automatically available to any nodes within the mock network
|
||||
ImmutableList.of("my.cordapp.package", "my.other.cordapp.package"),
|
||||
new MockNetworkParameters()
|
||||
// If true then each node will be run in its own thread. This can result in race conditions in
|
||||
// your code if not carefully written, but is more realistic and may help if you have flows in
|
||||
@ -294,6 +302,7 @@ Further examples
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
* See the flow testing tutorial :doc:`here <flow-testing>`
|
||||
* See the oracle tutorial :doc:`here <oracles>` for information on testing ``@CordaService`` classes
|
||||
* Further examples are available in the Example CorDapp in
|
||||
`Java <https://github.com/corda/cordapp-example/blob/release-V3/java-source/src/test/java/com/example/flow/IOUFlowTests.java>`_ and
|
||||
`Kotlin <https://github.com/corda/cordapp-example/blob/release-V3/kotlin-source/src/test/kotlin/com/example/flow/IOUFlowTests.kt>`_
|
||||
|
@ -269,7 +269,26 @@ Here's an example of it in action from ``FixingFlow.Fixer``.
|
||||
Testing
|
||||
-------
|
||||
|
||||
When unit testing, we make use of the ``MockNetwork`` which allows us to create ``MockNode`` instances. A ``MockNode``
|
||||
is a simplified node suitable for tests. One feature that isn't available (and which is not suitable for unit testing
|
||||
anyway) is the node's ability to scan and automatically install oracles it finds in the CorDapp jars. Instead, when
|
||||
working with ``MockNode``, use the ``installCordaService`` method to manually install the oracle on the relevant node.
|
||||
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.
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
See `here <https://github.com/corda/corda/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/OracleNodeTearOffTests.kt>`_ for more examples.
|
||||
|
@ -79,6 +79,8 @@ object FixingFlow {
|
||||
@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
|
||||
}
|
||||
@ -91,7 +93,7 @@ object FixingFlow {
|
||||
}
|
||||
|
||||
/**
|
||||
* One side of the fixing flow for an interest rate swap, but could easily be generalised furher.
|
||||
* One side of the fixing flow for an interest rate swap, but could easily be generalised further.
|
||||
*
|
||||
* As per the [Fixer], do not infer too much from this class name in terms of business roles. This
|
||||
* is just the "side" of the flow run by the party with the floating leg as a way of deciding who
|
||||
|
@ -15,22 +15,15 @@ import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.contracts.Fix
|
||||
import net.corda.finance.contracts.FixOf
|
||||
import net.corda.finance.contracts.asset.CASH
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.irs.flows.RatesFixFlow
|
||||
import net.corda.node.internal.configureDatabase
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.internal.withoutTestSerialization
|
||||
import net.corda.testing.internal.LogHelper
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.node.*
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
@ -220,50 +213,10 @@ class NodeInterestRatesTest {
|
||||
assertFailsWith<IllegalArgumentException> { oracle.sign(ftx) } // It throws failed requirement (as it is empty there is no command to check and sign).
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `network tearoff`() = withoutTestSerialization {
|
||||
val mockNet = MockNetwork(cordappPackages = listOf("net.corda.finance.contracts", "net.corda.irs"))
|
||||
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||
val oracleNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME)).apply {
|
||||
registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java)
|
||||
registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java)
|
||||
database.transaction {
|
||||
services.cordaService(NodeInterestRates.Oracle::class.java).knownFixes = TEST_DATA
|
||||
}
|
||||
}
|
||||
val oracle = oracleNode.services.myInfo.singleIdentity()
|
||||
val tx = makePartialTX()
|
||||
val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
|
||||
val flow = FilteredRatesFlow(tx, oracle, fixOf, BigDecimal("0.675"), BigDecimal("0.1"))
|
||||
LogHelper.setLevel("rates")
|
||||
mockNet.runNetwork()
|
||||
val future = aliceNode.startFlow(flow)
|
||||
mockNet.runNetwork()
|
||||
future.getOrThrow()
|
||||
// We should now have a valid fix of our tx from the oracle.
|
||||
val fix = tx.toWireTransaction(services).commands.map { it.value as Fix }.first()
|
||||
assertEquals(fixOf, fix.of)
|
||||
assertEquals(BigDecimal("0.678"), fix.value)
|
||||
mockNet.stopNodes()
|
||||
}
|
||||
|
||||
class FilteredRatesFlow(tx: TransactionBuilder,
|
||||
oracle: Party,
|
||||
fixOf: FixOf,
|
||||
expectedRate: BigDecimal,
|
||||
rateTolerance: BigDecimal,
|
||||
progressTracker: ProgressTracker = RatesFixFlow.tracker(fixOf.name))
|
||||
: RatesFixFlow(tx, oracle, fixOf, expectedRate, rateTolerance, progressTracker) {
|
||||
override fun filtering(elem: Any): Boolean {
|
||||
return when (elem) {
|
||||
is Command<*> -> oracle.owningKey in elem.signers && elem.value is Fix
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun makePartialTX() = TransactionBuilder(DUMMY_NOTARY).withItems(
|
||||
TransactionState(1000.DOLLARS.CASH issuedBy dummyCashIssuer.party ownedBy ALICE, Cash.PROGRAM_ID, DUMMY_NOTARY))
|
||||
|
||||
private fun makeFullTx() = makePartialTX().withItems(dummyCommand())
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,155 @@
|
||||
package net.corda.irs.api
|
||||
|
||||
import com.google.common.collect.testing.Helpers
|
||||
import com.google.common.collect.testing.Helpers.assertContains
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.flows.UnexpectedFlowEndException
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.contracts.Fix
|
||||
import net.corda.finance.contracts.FixOf
|
||||
import net.corda.finance.contracts.asset.CASH
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.irs.flows.RatesFixFlow
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.internal.LogHelper
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import net.corda.testing.node.MockNodeParameters
|
||||
import net.corda.testing.node.StartedMockNode
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class OracleNodeTearOffTests {
|
||||
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
|
||||
LIBOR 2016-03-16 2Y = 0.962
|
||||
EURIBOR 2016-03-15 1M = 0.123
|
||||
EURIBOR 2016-03-15 2M = 0.111
|
||||
""".trimIndent())
|
||||
|
||||
private val dummyCashIssuer = TestIdentity(CordaX500Name("Cash issuer", "London", "GB"))
|
||||
|
||||
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
|
||||
val alice = TestIdentity(ALICE_NAME, 70)
|
||||
private lateinit var mockNet: MockNetwork
|
||||
private lateinit var aliceNode: StartedMockNode
|
||||
private lateinit var oracleNode: StartedMockNode
|
||||
private val oracle get() = oracleNode.services.myInfo.singleIdentity()
|
||||
|
||||
@Before
|
||||
// DOCSTART 1
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
// DOCEND 1
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
mockNet.stopNodes()
|
||||
}
|
||||
|
||||
// DOCSTART 2
|
||||
@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)
|
||||
}
|
||||
// DOCEND 2
|
||||
|
||||
@Test
|
||||
fun `verify that the oracle rejects the transaction if the interest rate is outside the allowed limit`() {
|
||||
val tx = makePartialTX()
|
||||
val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
|
||||
val flow = FilteredRatesFlow(tx, oracle, fixOf, BigDecimal("0.695"), BigDecimal("0.01"))
|
||||
LogHelper.setLevel("rates")
|
||||
|
||||
mockNet.runNetwork()
|
||||
val future = aliceNode.startFlow(flow)
|
||||
mockNet.runNetwork()
|
||||
assertThatThrownBy{
|
||||
future.getOrThrow()
|
||||
}.isInstanceOf(RatesFixFlow.FixOutOfRange::class.java).hasMessage("Fix out of range by 0.017")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verify that the oracle rejects the transaction if there is a privacy leak`() {
|
||||
val tx = makePartialTX()
|
||||
val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
|
||||
val flow = OverFilteredRatesFlow(tx, oracle, fixOf, BigDecimal("0.675"), BigDecimal("0.1"))
|
||||
LogHelper.setLevel("rates")
|
||||
|
||||
mockNet.runNetwork()
|
||||
val future = aliceNode.startFlow(flow)
|
||||
mockNet.runNetwork()
|
||||
//The oracle
|
||||
assertThatThrownBy{
|
||||
future.getOrThrow()
|
||||
}.isInstanceOf(UnexpectedFlowEndException::class.java)
|
||||
}
|
||||
|
||||
// Creates a version of [RatesFixFlow] that makes the command
|
||||
class FilteredRatesFlow(tx: TransactionBuilder,
|
||||
oracle: Party,
|
||||
fixOf: FixOf,
|
||||
expectedRate: BigDecimal,
|
||||
rateTolerance: BigDecimal,
|
||||
progressTracker: ProgressTracker = tracker(fixOf.name))
|
||||
: RatesFixFlow(tx, oracle, fixOf, expectedRate, rateTolerance, progressTracker) {
|
||||
override fun filtering(elem: Any): Boolean {
|
||||
return when (elem) {
|
||||
is Command<*> -> oracle.owningKey in elem.signers && elem.value is Fix
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a version of [RatesFixFlow] that makes the command
|
||||
class OverFilteredRatesFlow(tx: TransactionBuilder,
|
||||
oracle: Party,
|
||||
fixOf: FixOf,
|
||||
expectedRate: BigDecimal,
|
||||
rateTolerance: BigDecimal,
|
||||
progressTracker: ProgressTracker = tracker(fixOf.name))
|
||||
: RatesFixFlow(tx, oracle, fixOf, expectedRate, rateTolerance, progressTracker) {
|
||||
override fun filtering(elem: Any): Boolean = true
|
||||
}
|
||||
|
||||
private fun makePartialTX() = TransactionBuilder(DUMMY_NOTARY).withItems(
|
||||
TransactionState(1000.DOLLARS.CASH issuedBy dummyCashIssuer.party ownedBy alice.party, Cash.PROGRAM_ID, DUMMY_NOTARY))
|
||||
}
|
Loading…
Reference in New Issue
Block a user