Allow upload of interest rate fixes to the oracle over HTTP.

This commit is contained in:
Mike Hearn 2016-03-08 16:37:19 +01:00
parent 2b4a1eedc3
commit 29e58ce3db
5 changed files with 62 additions and 11 deletions

View File

@ -39,3 +39,24 @@ containers, you can also fetch a specific file within the attachment by appendin
http://localhost:31338/attachments/DECD098666B9657314870E192CED0C3519C2C9D395507A238338F8D003929DE9/path/within/zip.txt http://localhost:31338/attachments/DECD098666B9657314870E192CED0C3519C2C9D395507A238338F8D003929DE9/path/within/zip.txt
Uploading interest rate fixes
-----------------------------
If you would like to operate an interest rate fixing service (oracle), you can upload fix data by uploading data in
a simple text format to the ``/upload/interest-rates`` path on the web server.
The file looks like this::
# Some pretend noddy rate fixes, for the interest rate oracles.
LIBOR 2016-03-16 30 = 0.678
LIBOR 2016-03-16 60 = 0.655
EURIBOR 2016-03-15 30 = 0.123
EURIBOR 2016-03-15 60 = 0.111
The columns are:
* Name of the fix
* Date of the fix
* The tenor / time to maturity in days
* The interest rate itself

View File

@ -0,0 +1,6 @@
# Some pretend noddy rate fixes, for the interest rate oracles.
LIBOR 2016-03-16 30 = 0.678
LIBOR 2016-03-16 60 = 0.655
EURIBOR 2016-03-15 30 = 0.123
EURIBOR 2016-03-15 60 = 0.111

View File

@ -92,11 +92,11 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
log.info("Node starting up ...") log.info("Node starting up ...")
storage = initialiseStorageService(dir) storage = initialiseStorageService(dir)
net = makeMessagingService() net = makeMessagingService()
smm = StateMachineManager(services, serverThread) smm = StateMachineManager(services, serverThread)
wallet = NodeWalletService(services) wallet = NodeWalletService(services)
keyManagement = E2ETestKeyManagementService() keyManagement = E2ETestKeyManagementService()
makeInterestRateOracleService()
// Insert a network map entry for the timestamper: this is all temp scaffolding and will go away. If we are // Insert a network map entry for the timestamper: this is all temp scaffolding and will go away. If we are
// given the details, the timestamping node is somewhere else. Otherwise, we do our own timestamping. // given the details, the timestamping node is somewhere else. Otherwise, we do our own timestamping.
@ -118,6 +118,12 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
return this return this
} }
protected fun makeInterestRateOracleService() {
// Constructing the service registers message handlers that ensure the service won't be garbage collected.
// TODO: Once the service has data, automatically register with the network map service (once built).
_servicesThatAcceptUploads += NodeInterestRates.Service(this)
}
protected open fun makeIdentityService(): IdentityService { protected open fun makeIdentityService(): IdentityService {
// We don't have any identity infrastructure right now, so we just throw together the only two identities we // We don't have any identity infrastructure right now, so we just throw together the only two identities we
// know about: our own, and the identity of the remote timestamper node (if any). // know about: our own, and the identity of the remote timestamper node (if any).

View File

@ -13,8 +13,10 @@ import core.crypto.DigitalSignature
import core.crypto.signWithECDSA import core.crypto.signWithECDSA
import core.messaging.send import core.messaging.send
import core.node.AbstractNode import core.node.AbstractNode
import core.node.AcceptsFileUpload
import core.serialization.deserialize import core.serialization.deserialize
import protocols.RatesFixProtocol import protocols.RatesFixProtocol
import java.io.InputStream
import java.math.BigDecimal import java.math.BigDecimal
import java.security.KeyPair import java.security.KeyPair
import java.time.LocalDate import java.time.LocalDate
@ -31,13 +33,6 @@ import javax.annotation.concurrent.ThreadSafe
* for signing. * for signing.
*/ */
object NodeInterestRates { object NodeInterestRates {
val INITIAL_FIXES = parseFile("""
LIBOR 2016-03-16 30 = 0.678
LIBOR 2016-03-16 60 = 0.655
EURIBOR 2016-03-15 30 = 0.123
EURIBOR 2016-03-15 60 = 0.111
""".trimIndent())
/** Parses a string of the form "LIBOR 16-March-2016 30 = 0.678" into a [FixOf] and [Fix] */ /** Parses a string of the form "LIBOR 16-March-2016 30 = 0.678" into a [FixOf] and [Fix] */
fun parseOneRate(s: String): Pair<FixOf, Fix> { fun parseOneRate(s: String): Pair<FixOf, Fix> {
val (key, value) = s.split('=').map { it.trim() } val (key, value) = s.split('=').map { it.trim() }
@ -65,7 +60,7 @@ object NodeInterestRates {
/** /**
* The Service that wraps [Oracle] and handles messages/network interaction/request scrubbing. * The Service that wraps [Oracle] and handles messages/network interaction/request scrubbing.
*/ */
class Service(node: AbstractNode) { class Service(node: AbstractNode) : AcceptsFileUpload {
val ss = node.services.storageService val ss = node.services.storageService
val oracle = Oracle(ss.myLegalIdentity, ss.myLegalIdentityKey) val oracle = Oracle(ss.myLegalIdentity, ss.myLegalIdentityKey)
val net = node.services.networkService val net = node.services.networkService
@ -90,6 +85,29 @@ object NodeInterestRates {
net.send("${RatesFixProtocol.TOPIC}.query.${request.sessionID}", request.replyTo, answers) net.send("${RatesFixProtocol.TOPIC}.query.${request.sessionID}", request.replyTo, answers)
} }
} }
// File upload support
override val dataTypePrefix = "interest-rates"
override val acceptableFileExtensions = listOf(".rates", ".txt")
override fun upload(data: InputStream): String {
val fixes: Map<FixOf, Fix> = data.
bufferedReader().
readLines().
map { it.trim() }.
// Filter out comment and empty lines.
filterNot { it.startsWith("#") || it.isBlank() }.
map { parseOneRate(it) }.
associate { it.first to it.second }
// TODO: Save the uploaded fixes to the storage service and reload on construction.
// This assignment is thread safe because knownFixes is volatile and the oracle code always snapshots
// the pointer to the stack before working with the map.
oracle.knownFixes = fixes
return "Accepted ${fixes.size} new interest rate fixes"
}
} }
/** /**
@ -102,7 +120,7 @@ object NodeInterestRates {
} }
/** The fix data being served by this oracle. */ /** The fix data being served by this oracle. */
@Transient var knownFixes = INITIAL_FIXES @Transient var knownFixes = emptyMap<FixOf, Fix>()
set(value) { set(value) {
require(value.isNotEmpty()) require(value.isNotEmpty())
field = value field = value

View File

@ -81,7 +81,7 @@ class NodeInterestRatesTest {
fun network() { fun network() {
val net = MockNetwork() val net = MockNetwork()
val (n1, n2) = net.createTwoNodes() val (n1, n2) = net.createTwoNodes()
NodeInterestRates.Service(n2) NodeInterestRates.Service(n2).oracle.knownFixes = TEST_DATA
val tx = TransactionBuilder() val tx = TransactionBuilder()
val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 30") val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 30")