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.
val filterHolder = FilterHolder(DatabaseTransactionFilter(database))
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.ServiceHubInternal
import com.r3corda.node.utilities.FiberBox
import com.r3corda.node.utilities.JDBCHashSet
import com.r3corda.protocols.RatesFixProtocol.*
import com.r3corda.protocols.ServiceRequestMessage
import com.r3corda.protocols.TwoPartyDealProtocol
import java.io.InputStream
import java.math.BigDecimal
@ -116,9 +118,10 @@ object NodeInterestRates {
*/
@ThreadSafe
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())
@ -126,6 +129,8 @@ object NodeInterestRates {
set(value) {
require(value.size > 0)
mutex.write {
fixes.clear()
fixes.addAll(value.fixes)
container = value
}
}
@ -185,9 +190,9 @@ object NodeInterestRates {
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] */
class FixContainer(fixes: List<Fix>, val factory: InterpolatorFactory = CubicSplineInterpolator) {
class FixContainer(val fixes: Set<Fix>, val factory: InterpolatorFactory = CubicSplineInterpolator) {
private val container = buildContainer(fixes)
val size = fixes.size
val size: Int get() = fixes.size
operator fun get(fixOf: FixOf): Fix? {
val rates = container[fixOf.name to fixOf.forDay]
@ -195,7 +200,7 @@ object NodeInterestRates {
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>>()
for (fix in fixes) {
val fixOf = fix.of
@ -265,7 +270,8 @@ object NodeInterestRates {
map(String::trim).
// Filter out comment and empty lines.
filterNot { it.startsWith("#") || it.isBlank() }.
map { parseFix(it) }
map { parseFix(it) }.
toSet()
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.transactions.SimpleNotaryService
import com.r3corda.node.utilities.AddOrRemove
import com.r3corda.node.utilities.databaseTransaction
import com.r3corda.testing.node.*
import rx.Observable
import rx.subjects.PublishSubject
@ -147,7 +148,11 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
return object : SimulatedNode(cfg, network, networkMapAddr, advertisedServices, id, keyPair) {
override fun start(): MockNetwork.MockNode {
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
}
}

View File

@ -15,12 +15,17 @@ import com.r3corda.core.utilities.DUMMY_NOTARY
import com.r3corda.core.utilities.LogHelper
import com.r3corda.testing.node.MockNetwork
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.testing.ALICE_PUBKEY
import com.r3corda.testing.MEGA_CORP
import com.r3corda.testing.MEGA_CORP_KEY
import org.junit.Assert
import org.junit.Test
import com.r3corda.testing.node.makeTestDataSourceProperties
import org.jetbrains.exposed.sql.Database
import org.junit.*
import java.io.Closeable
import java.time.Clock
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@ -39,73 +44,107 @@ class NodeInterestRatesTest {
val DUMMY_CASH_ISSUER = Party("Cash issuer", DUMMY_CASH_ISSUER_KEY.public)
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`() {
val q = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val res = oracle.query(listOf(q), clock.instant())
assertEquals(1, res.size)
assertEquals("0.678".bd, res[0].value)
assertEquals(q, res[0].of)
databaseTransaction(database) {
val q = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val res = oracle.query(listOf(q), clock.instant())
assertEquals(1, res.size)
assertEquals("0.678".bd, res[0].value)
assertEquals(q, res[0].of)
}
}
@Test fun `query with one success and one missing`() {
val q1 = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val q2 = NodeInterestRates.parseFixOf("LIBOR 2016-03-15 1M")
val e = assertFailsWith<NodeInterestRates.UnknownFix> { oracle.query(listOf(q1, q2), clock.instant()) }
assertEquals(e.fix, q2)
databaseTransaction(database) {
val q1 = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val q2 = NodeInterestRates.parseFixOf("LIBOR 2016-03-15 1M")
val e = assertFailsWith<NodeInterestRates.UnknownFix> { oracle.query(listOf(q1, q2), clock.instant()) }
assertEquals(e.fix, q2)
}
}
@Test fun `query successfully with interpolated rate`() {
val q = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 5M")
val res = oracle.query(listOf(q), clock.instant())
assertEquals(1, res.size)
Assert.assertEquals(0.7316228, res[0].value.toDouble(), 0.0000001)
assertEquals(q, res[0].of)
databaseTransaction(database) {
val q = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 5M")
val res = oracle.query(listOf(q), clock.instant())
assertEquals(1, res.size)
Assert.assertEquals(0.7316228, res[0].value.toDouble(), 0.0000001)
assertEquals(q, res[0].of)
}
}
@Test fun `rate missing and unable to interpolate`() {
val q = NodeInterestRates.parseFixOf("EURIBOR 2016-03-15 3M")
assertFailsWith<NodeInterestRates.UnknownFix> { oracle.query(listOf(q), clock.instant()) }
databaseTransaction(database) {
val q = NodeInterestRates.parseFixOf("EURIBOR 2016-03-15 3M")
assertFailsWith<NodeInterestRates.UnknownFix> { oracle.query(listOf(q), clock.instant()) }
}
}
@Test fun `empty query`() {
assertFailsWith<IllegalArgumentException> { oracle.query(emptyList(), clock.instant()) }
databaseTransaction(database) {
assertFailsWith<IllegalArgumentException> { oracle.query(emptyList(), clock.instant()) }
}
}
@Test fun `refuse to sign with no relevant commands`() {
val tx = makeTX()
assertFailsWith<IllegalArgumentException> { oracle.sign(tx.toWireTransaction()) }
tx.addCommand(Cash.Commands.Move(), ALICE_PUBKEY)
assertFailsWith<IllegalArgumentException> { oracle.sign(tx.toWireTransaction()) }
databaseTransaction(database) {
val tx = makeTX()
assertFailsWith<IllegalArgumentException> { oracle.sign(tx.toWireTransaction()) }
tx.addCommand(Cash.Commands.Move(), ALICE_PUBKEY)
assertFailsWith<IllegalArgumentException> { oracle.sign(tx.toWireTransaction()) }
}
}
@Test fun `sign successfully`() {
val tx = makeTX()
val fix = oracle.query(listOf(NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")), clock.instant()).first()
tx.addCommand(fix, oracle.identity.owningKey)
databaseTransaction(database) {
val tx = makeTX()
val fix = oracle.query(listOf(NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")), clock.instant()).first()
tx.addCommand(fix, oracle.identity.owningKey)
// Sign successfully.
val signature = oracle.sign(tx.toWireTransaction())
tx.checkAndAddSignature(signature)
// Sign successfully.
val signature = oracle.sign(tx.toWireTransaction())
tx.checkAndAddSignature(signature)
}
}
@Test fun `do not sign with unknown fix`() {
val tx = makeTX()
val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val badFix = Fix(fixOf, "0.6789".bd)
tx.addCommand(badFix, oracle.identity.owningKey)
databaseTransaction(database) {
val tx = makeTX()
val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val badFix = Fix(fixOf, "0.6789".bd)
tx.addCommand(badFix, oracle.identity.owningKey)
val e1 = assertFailsWith<NodeInterestRates.UnknownFix> { oracle.sign(tx.toWireTransaction()) }
assertEquals(fixOf, e1.fix)
val e1 = assertFailsWith<NodeInterestRates.UnknownFix> { oracle.sign(tx.toWireTransaction()) }
assertEquals(fixOf, e1.fix)
}
}
@Test
fun network() {
val net = MockNetwork()
val (n1, n2) = net.createTwoNodes()
n2.findService<NodeInterestRates.Service>().oracle.knownFixes = TEST_DATA
databaseTransaction(n2.database) {
n2.findService<NodeInterestRates.Service>().oracle.knownFixes = TEST_DATA
}
val tx = TransactionType.General.Builder(null)
val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val protocol = RatesFixProtocol(tx, n2.info.identity, fixOf, "0.675".bd, "0.1".bd)