mirror of
https://github.com/corda/corda.git
synced 2025-06-17 06:38:21 +00:00
First working version with database persistence in MockNetwork/Node.
This commit is contained in:
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
Reference in New Issue
Block a user