First working version with database persistence in MockNetwork/Node.

This commit is contained in:
rick.parker
2016-09-20 13:35:28 +01:00
parent a15a2b1d9f
commit cd301c727e
4 changed files with 96 additions and 45 deletions

View File

@ -236,6 +236,7 @@ class Node(val p2pAddr: HostAndPort, val webServerAddr: HostAndPort,
// Wrap all API calls in a database transaction. // Wrap all API calls in a database transaction.
val filterHolder = FilterHolder(DatabaseTransactionFilter(database)) val filterHolder = FilterHolder(DatabaseTransactionFilter(database))
addFilter(filterHolder, "/api/*", EnumSet.of(DispatcherType.REQUEST)) addFilter(filterHolder, "/api/*", EnumSet.of(DispatcherType.REQUEST))
addFilter(filterHolder, "/upload/*", EnumSet.of(DispatcherType.REQUEST))
} }
} }

View File

@ -18,7 +18,9 @@ import com.r3corda.core.utilities.ProgressTracker
import com.r3corda.node.services.api.AcceptsFileUpload import com.r3corda.node.services.api.AcceptsFileUpload
import com.r3corda.node.services.api.ServiceHubInternal import com.r3corda.node.services.api.ServiceHubInternal
import com.r3corda.node.utilities.FiberBox import com.r3corda.node.utilities.FiberBox
import com.r3corda.node.utilities.JDBCHashSet
import com.r3corda.protocols.RatesFixProtocol.* import com.r3corda.protocols.RatesFixProtocol.*
import com.r3corda.protocols.ServiceRequestMessage
import com.r3corda.protocols.TwoPartyDealProtocol import com.r3corda.protocols.TwoPartyDealProtocol
import java.io.InputStream import java.io.InputStream
import java.math.BigDecimal import java.math.BigDecimal
@ -116,9 +118,10 @@ object NodeInterestRates {
*/ */
@ThreadSafe @ThreadSafe
class Oracle(val identity: Party, private val signingKey: KeyPair, val clock: Clock) { class Oracle(val identity: Party, private val signingKey: KeyPair, val clock: Clock) {
private class InnerState {
var container: FixContainer = FixContainer(emptyList<Fix>())
private class InnerState {
val fixes = JDBCHashSet<Fix>("interest_rate_fixes")
var container: FixContainer = FixContainer(fixes)
} }
private val mutex = FiberBox(InnerState()) private val mutex = FiberBox(InnerState())
@ -126,6 +129,8 @@ object NodeInterestRates {
set(value) { set(value) {
require(value.size > 0) require(value.size > 0)
mutex.write { mutex.write {
fixes.clear()
fixes.addAll(value.fixes)
container = value container = value
} }
} }
@ -185,9 +190,9 @@ object NodeInterestRates {
class UnknownFix(val fix: FixOf) : RetryableException("Unknown fix: $fix") class UnknownFix(val fix: FixOf) : RetryableException("Unknown fix: $fix")
/** Fix container, for every fix name & date pair stores a tenor to interest rate map - [InterpolatingRateMap] */ /** Fix container, for every fix name & date pair stores a tenor to interest rate map - [InterpolatingRateMap] */
class FixContainer(fixes: List<Fix>, val factory: InterpolatorFactory = CubicSplineInterpolator) { class FixContainer(val fixes: Set<Fix>, val factory: InterpolatorFactory = CubicSplineInterpolator) {
private val container = buildContainer(fixes) private val container = buildContainer(fixes)
val size = fixes.size val size: Int get() = fixes.size
operator fun get(fixOf: FixOf): Fix? { operator fun get(fixOf: FixOf): Fix? {
val rates = container[fixOf.name to fixOf.forDay] val rates = container[fixOf.name to fixOf.forDay]
@ -195,7 +200,7 @@ object NodeInterestRates {
return Fix(fixOf, fixValue) return Fix(fixOf, fixValue)
} }
private fun buildContainer(fixes: List<Fix>): Map<Pair<String, LocalDate>, InterpolatingRateMap> { private fun buildContainer(fixes: Set<Fix>): Map<Pair<String, LocalDate>, InterpolatingRateMap> {
val tempContainer = HashMap<Pair<String, LocalDate>, HashMap<Tenor, BigDecimal>>() val tempContainer = HashMap<Pair<String, LocalDate>, HashMap<Tenor, BigDecimal>>()
for (fix in fixes) { for (fix in fixes) {
val fixOf = fix.of val fixOf = fix.of
@ -265,7 +270,8 @@ object NodeInterestRates {
map(String::trim). map(String::trim).
// Filter out comment and empty lines. // Filter out comment and empty lines.
filterNot { it.startsWith("#") || it.isBlank() }. filterNot { it.startsWith("#") || it.isBlank() }.
map { parseFix(it) } map { parseFix(it) }.
toSet()
return FixContainer(fixes) return FixContainer(fixes)
} }

View File

@ -17,6 +17,7 @@ import com.r3corda.node.services.config.NodeConfiguration
import com.r3corda.node.services.network.NetworkMapService import com.r3corda.node.services.network.NetworkMapService
import com.r3corda.node.services.transactions.SimpleNotaryService import com.r3corda.node.services.transactions.SimpleNotaryService
import com.r3corda.node.utilities.AddOrRemove import com.r3corda.node.utilities.AddOrRemove
import com.r3corda.node.utilities.databaseTransaction
import com.r3corda.testing.node.* import com.r3corda.testing.node.*
import rx.Observable import rx.Observable
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
@ -147,7 +148,11 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
return object : SimulatedNode(cfg, network, networkMapAddr, advertisedServices, id, keyPair) { return object : SimulatedNode(cfg, network, networkMapAddr, advertisedServices, id, keyPair) {
override fun start(): MockNetwork.MockNode { override fun start(): MockNetwork.MockNode {
super.start() super.start()
findService<NodeInterestRates.Service>().upload(javaClass.getResourceAsStream("example.rates.txt")) javaClass.getResourceAsStream("example.rates.txt").use {
databaseTransaction(database) {
findService<NodeInterestRates.Service>().upload(it)
}
}
return this return this
} }
} }

View File

@ -15,12 +15,17 @@ import com.r3corda.core.utilities.DUMMY_NOTARY
import com.r3corda.core.utilities.LogHelper import com.r3corda.core.utilities.LogHelper
import com.r3corda.testing.node.MockNetwork import com.r3corda.testing.node.MockNetwork
import com.r3corda.demos.api.NodeInterestRates import com.r3corda.demos.api.NodeInterestRates
import com.r3corda.node.services.transactions.PersistentUniquenessProvider
import com.r3corda.node.utilities.configureDatabase
import com.r3corda.node.utilities.databaseTransaction
import com.r3corda.protocols.RatesFixProtocol import com.r3corda.protocols.RatesFixProtocol
import com.r3corda.testing.ALICE_PUBKEY import com.r3corda.testing.ALICE_PUBKEY
import com.r3corda.testing.MEGA_CORP import com.r3corda.testing.MEGA_CORP
import com.r3corda.testing.MEGA_CORP_KEY import com.r3corda.testing.MEGA_CORP_KEY
import org.junit.Assert import com.r3corda.testing.node.makeTestDataSourceProperties
import org.junit.Test import org.jetbrains.exposed.sql.Database
import org.junit.*
import java.io.Closeable
import java.time.Clock import java.time.Clock
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
@ -39,48 +44,78 @@ class NodeInterestRatesTest {
val DUMMY_CASH_ISSUER = Party("Cash issuer", DUMMY_CASH_ISSUER_KEY.public) val DUMMY_CASH_ISSUER = Party("Cash issuer", DUMMY_CASH_ISSUER_KEY.public)
val clock = Clock.systemUTC() val clock = Clock.systemUTC()
val oracle = NodeInterestRates.Oracle(MEGA_CORP, MEGA_CORP_KEY, clock).apply { knownFixes = TEST_DATA } lateinit var oracle: NodeInterestRates.Oracle
lateinit var dataSource: Closeable
lateinit var database: Database
@Before
fun setUp() {
val dataSourceAndDatabase = configureDatabase(makeTestDataSourceProperties())
dataSource=dataSourceAndDatabase.first
database=dataSourceAndDatabase.second
databaseTransaction(database) {
oracle = NodeInterestRates.Oracle(MEGA_CORP, MEGA_CORP_KEY, clock).apply { knownFixes = TEST_DATA }
}
}
@After
fun tearDown() {
dataSource.close()
}
@Test fun `query successfully`() { @Test fun `query successfully`() {
databaseTransaction(database) {
val q = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M") val q = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val res = oracle.query(listOf(q), clock.instant()) val res = oracle.query(listOf(q), clock.instant())
assertEquals(1, res.size) assertEquals(1, res.size)
assertEquals("0.678".bd, res[0].value) assertEquals("0.678".bd, res[0].value)
assertEquals(q, res[0].of) assertEquals(q, res[0].of)
} }
}
@Test fun `query with one success and one missing`() { @Test fun `query with one success and one missing`() {
databaseTransaction(database) {
val q1 = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M") val q1 = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val q2 = NodeInterestRates.parseFixOf("LIBOR 2016-03-15 1M") val q2 = NodeInterestRates.parseFixOf("LIBOR 2016-03-15 1M")
val e = assertFailsWith<NodeInterestRates.UnknownFix> { oracle.query(listOf(q1, q2), clock.instant()) } val e = assertFailsWith<NodeInterestRates.UnknownFix> { oracle.query(listOf(q1, q2), clock.instant()) }
assertEquals(e.fix, q2) assertEquals(e.fix, q2)
} }
}
@Test fun `query successfully with interpolated rate`() { @Test fun `query successfully with interpolated rate`() {
databaseTransaction(database) {
val q = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 5M") val q = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 5M")
val res = oracle.query(listOf(q), clock.instant()) val res = oracle.query(listOf(q), clock.instant())
assertEquals(1, res.size) assertEquals(1, res.size)
Assert.assertEquals(0.7316228, res[0].value.toDouble(), 0.0000001) Assert.assertEquals(0.7316228, res[0].value.toDouble(), 0.0000001)
assertEquals(q, res[0].of) assertEquals(q, res[0].of)
} }
}
@Test fun `rate missing and unable to interpolate`() { @Test fun `rate missing and unable to interpolate`() {
databaseTransaction(database) {
val q = NodeInterestRates.parseFixOf("EURIBOR 2016-03-15 3M") val q = NodeInterestRates.parseFixOf("EURIBOR 2016-03-15 3M")
assertFailsWith<NodeInterestRates.UnknownFix> { oracle.query(listOf(q), clock.instant()) } assertFailsWith<NodeInterestRates.UnknownFix> { oracle.query(listOf(q), clock.instant()) }
} }
}
@Test fun `empty query`() { @Test fun `empty query`() {
databaseTransaction(database) {
assertFailsWith<IllegalArgumentException> { oracle.query(emptyList(), clock.instant()) } assertFailsWith<IllegalArgumentException> { oracle.query(emptyList(), clock.instant()) }
} }
}
@Test fun `refuse to sign with no relevant commands`() { @Test fun `refuse to sign with no relevant commands`() {
databaseTransaction(database) {
val tx = makeTX() val tx = makeTX()
assertFailsWith<IllegalArgumentException> { oracle.sign(tx.toWireTransaction()) } assertFailsWith<IllegalArgumentException> { oracle.sign(tx.toWireTransaction()) }
tx.addCommand(Cash.Commands.Move(), ALICE_PUBKEY) tx.addCommand(Cash.Commands.Move(), ALICE_PUBKEY)
assertFailsWith<IllegalArgumentException> { oracle.sign(tx.toWireTransaction()) } assertFailsWith<IllegalArgumentException> { oracle.sign(tx.toWireTransaction()) }
} }
}
@Test fun `sign successfully`() { @Test fun `sign successfully`() {
databaseTransaction(database) {
val tx = makeTX() val tx = makeTX()
val fix = oracle.query(listOf(NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")), clock.instant()).first() val fix = oracle.query(listOf(NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")), clock.instant()).first()
tx.addCommand(fix, oracle.identity.owningKey) tx.addCommand(fix, oracle.identity.owningKey)
@ -89,8 +124,10 @@ class NodeInterestRatesTest {
val signature = oracle.sign(tx.toWireTransaction()) val signature = oracle.sign(tx.toWireTransaction())
tx.checkAndAddSignature(signature) tx.checkAndAddSignature(signature)
} }
}
@Test fun `do not sign with unknown fix`() { @Test fun `do not sign with unknown fix`() {
databaseTransaction(database) {
val tx = makeTX() val tx = makeTX()
val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M") val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val badFix = Fix(fixOf, "0.6789".bd) val badFix = Fix(fixOf, "0.6789".bd)
@ -99,13 +136,15 @@ class NodeInterestRatesTest {
val e1 = assertFailsWith<NodeInterestRates.UnknownFix> { oracle.sign(tx.toWireTransaction()) } val e1 = assertFailsWith<NodeInterestRates.UnknownFix> { oracle.sign(tx.toWireTransaction()) }
assertEquals(fixOf, e1.fix) assertEquals(fixOf, e1.fix)
} }
}
@Test @Test
fun network() { fun network() {
val net = MockNetwork() val net = MockNetwork()
val (n1, n2) = net.createTwoNodes() val (n1, n2) = net.createTwoNodes()
databaseTransaction(n2.database) {
n2.findService<NodeInterestRates.Service>().oracle.knownFixes = TEST_DATA n2.findService<NodeInterestRates.Service>().oracle.knownFixes = TEST_DATA
}
val tx = TransactionType.General.Builder(null) val tx = TransactionType.General.Builder(null)
val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M") val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val protocol = RatesFixProtocol(tx, n2.info.identity, fixOf, "0.675".bd, "0.1".bd) val protocol = RatesFixProtocol(tx, n2.info.identity, fixOf, "0.675".bd, "0.1".bd)