1+ package net.corda.irs.api
2+
3+ import com.google.common.collect.testing.Helpers
4+ import com.google.common.collect.testing.Helpers.assertContains
5+ import net.corda.core.contracts.Command
6+ import net.corda.core.contracts.TransactionState
7+ import net.corda.core.flows.UnexpectedFlowEndException
8+ import net.corda.core.identity.CordaX500Name
9+ import net.corda.core.identity.Party
10+ import net.corda.core.transactions.TransactionBuilder
11+ import net.corda.core.utilities.ProgressTracker
12+ import net.corda.core.utilities.getOrThrow
13+ import net.corda.finance.DOLLARS
14+ import net.corda.finance.contracts.Fix
15+ import net.corda.finance.contracts.FixOf
16+ import net.corda.finance.contracts.asset.CASH
17+ import net.corda.finance.contracts.asset.Cash
18+ import net.corda.irs.flows.RatesFixFlow
19+ import net.corda.testing.core.*
20+ import net.corda.testing.internal.LogHelper
21+ import net.corda.testing.node.MockNetwork
22+ import net.corda.testing.node.MockNodeParameters
23+ import net.corda.testing.node.StartedMockNode
24+ import org.assertj.core.api.Assertions.assertThatThrownBy
25+ import org.junit.After
26+ import org.junit.Before
27+ import org.junit.Test
28+ import java.math.BigDecimal
29+ import kotlin.test.assertEquals
30+
31+ class OracleNodeTearOffTests {
32+ private val TEST_DATA = NodeInterestRates .parseFile("""
33+ LIBOR 2016-03-16 1M = 0.678
34+ LIBOR 2016-03-16 2M = 0.685
35+ LIBOR 2016-03-16 1Y = 0.890
36+ LIBOR 2016-03-16 2Y = 0.962
37+ EURIBOR 2016-03-15 1M = 0.123
38+ EURIBOR 2016-03-15 2M = 0.111
39+ """ .trimIndent())
40+
41+ private val dummyCashIssuer = TestIdentity (CordaX500Name (" Cash issuer" , " London" , " GB" ))
42+
43+ val DUMMY_NOTARY = TestIdentity (DUMMY_NOTARY_NAME , 20 ).party
44+ val alice = TestIdentity (ALICE_NAME , 70 )
45+ private lateinit var mockNet: MockNetwork
46+ private lateinit var aliceNode: StartedMockNode
47+ private lateinit var oracleNode: StartedMockNode
48+ private val oracle get() = oracleNode.services.myInfo.singleIdentity()
49+
50+ @Before
51+ // DOCSTART 1
52+ fun setUp () {
53+ mockNet = MockNetwork (cordappPackages = listOf (" net.corda.finance.contracts" , " net.corda.irs" ))
54+ aliceNode = mockNet.createPartyNode(ALICE_NAME )
55+ oracleNode = mockNet.createNode(MockNodeParameters (legalName = BOB_NAME )).apply {
56+ transaction {
57+ services.cordaService(NodeInterestRates .Oracle ::class .java).knownFixes = TEST_DATA
58+ }
59+ }
60+ }
61+ // DOCEND 1
62+
63+ @After
64+ fun tearDown () {
65+ mockNet.stopNodes()
66+ }
67+
68+ // DOCSTART 2
69+ @Test
70+ fun `verify that the oracle signs the transaction if the interest rate within allowed limit` () {
71+ // Create a partial transaction
72+ val tx = TransactionBuilder (DUMMY_NOTARY )
73+ .withItems(TransactionState (1000 .DOLLARS .CASH issuedBy dummyCashIssuer.party ownedBy alice.party, Cash .PROGRAM_ID , DUMMY_NOTARY ))
74+ // Specify the rate we wish to get verified by the oracle
75+ val fixOf = NodeInterestRates .parseFixOf(" LIBOR 2016-03-16 1M" )
76+
77+ // Create a new flow for the fix
78+ val flow = FilteredRatesFlow (tx, oracle, fixOf, BigDecimal (" 0.675" ), BigDecimal (" 0.1" ))
79+ // Run the mock network and wait for a result
80+ mockNet.runNetwork()
81+ val future = aliceNode.startFlow(flow)
82+ mockNet.runNetwork()
83+ future.getOrThrow()
84+
85+ // We should now have a valid rate on our tx from the oracle.
86+ val fix = tx.toWireTransaction(aliceNode.services).commands.map { it }.first()
87+ assertEquals(fixOf, (fix.value as Fix ).of)
88+ // Check that the response contains the valid rate, which is within the supplied tolerance
89+ assertEquals(BigDecimal (" 0.678" ), (fix.value as Fix ).value)
90+ // Check that the transaction has been signed by the oracle
91+ assertContains(fix.signers, oracle.owningKey)
92+ }
93+ // DOCEND 2
94+
95+ @Test
96+ fun `verify that the oracle rejects the transaction if the interest rate is outside the allowed limit` () {
97+ val tx = makePartialTX()
98+ val fixOf = NodeInterestRates .parseFixOf(" LIBOR 2016-03-16 1M" )
99+ val flow = FilteredRatesFlow (tx, oracle, fixOf, BigDecimal (" 0.695" ), BigDecimal (" 0.01" ))
100+ LogHelper .setLevel(" rates" )
101+
102+ mockNet.runNetwork()
103+ val future = aliceNode.startFlow(flow)
104+ mockNet.runNetwork()
105+ assertThatThrownBy{
106+ future.getOrThrow()
107+ }.isInstanceOf(RatesFixFlow .FixOutOfRange ::class .java).hasMessage(" Fix out of range by 0.017" )
108+ }
109+
110+ @Test
111+ fun `verify that the oracle rejects the transaction if there is a privacy leak` () {
112+ val tx = makePartialTX()
113+ val fixOf = NodeInterestRates .parseFixOf(" LIBOR 2016-03-16 1M" )
114+ val flow = OverFilteredRatesFlow (tx, oracle, fixOf, BigDecimal (" 0.675" ), BigDecimal (" 0.1" ))
115+ LogHelper .setLevel(" rates" )
116+
117+ mockNet.runNetwork()
118+ val future = aliceNode.startFlow(flow)
119+ mockNet.runNetwork()
120+ // The oracle
121+ assertThatThrownBy{
122+ future.getOrThrow()
123+ }.isInstanceOf(UnexpectedFlowEndException ::class .java)
124+ }
125+
126+ // Creates a version of [RatesFixFlow] that makes the command
127+ class FilteredRatesFlow (tx : TransactionBuilder ,
128+ oracle : Party ,
129+ fixOf : FixOf ,
130+ expectedRate : BigDecimal ,
131+ rateTolerance : BigDecimal ,
132+ progressTracker : ProgressTracker = tracker(fixOf.name))
133+ : RatesFixFlow (tx, oracle, fixOf, expectedRate, rateTolerance, progressTracker) {
134+ override fun filtering (elem : Any ): Boolean {
135+ return when (elem) {
136+ is Command <* > -> oracle.owningKey in elem.signers && elem.value is Fix
137+ else -> false
138+ }
139+ }
140+ }
141+
142+ // Creates a version of [RatesFixFlow] that makes the command
143+ class OverFilteredRatesFlow (tx : TransactionBuilder ,
144+ oracle : Party ,
145+ fixOf : FixOf ,
146+ expectedRate : BigDecimal ,
147+ rateTolerance : BigDecimal ,
148+ progressTracker : ProgressTracker = tracker(fixOf.name))
149+ : RatesFixFlow (tx, oracle, fixOf, expectedRate, rateTolerance, progressTracker) {
150+ override fun filtering (elem : Any ): Boolean = true
151+ }
152+
153+ private fun makePartialTX () = TransactionBuilder (DUMMY_NOTARY ).withItems(
154+ TransactionState (1000 .DOLLARS .CASH issuedBy dummyCashIssuer.party ownedBy alice.party, Cash .PROGRAM_ID , DUMMY_NOTARY ))
155+ }
0 commit comments