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
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 ...")
storage = initialiseStorageService(dir)
net = makeMessagingService()
smm = StateMachineManager(services, serverThread)
wallet = NodeWalletService(services)
keyManagement = E2ETestKeyManagementService()
makeInterestRateOracleService()
// 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.
@ -118,6 +118,12 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
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 {
// 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).

View File

@ -13,8 +13,10 @@ import core.crypto.DigitalSignature
import core.crypto.signWithECDSA
import core.messaging.send
import core.node.AbstractNode
import core.node.AcceptsFileUpload
import core.serialization.deserialize
import protocols.RatesFixProtocol
import java.io.InputStream
import java.math.BigDecimal
import java.security.KeyPair
import java.time.LocalDate
@ -31,13 +33,6 @@ import javax.annotation.concurrent.ThreadSafe
* for signing.
*/
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] */
fun parseOneRate(s: String): Pair<FixOf, Fix> {
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.
*/
class Service(node: AbstractNode) {
class Service(node: AbstractNode) : AcceptsFileUpload {
val ss = node.services.storageService
val oracle = Oracle(ss.myLegalIdentity, ss.myLegalIdentityKey)
val net = node.services.networkService
@ -90,6 +85,29 @@ object NodeInterestRates {
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. */
@Transient var knownFixes = INITIAL_FIXES
@Transient var knownFixes = emptyMap<FixOf, Fix>()
set(value) {
require(value.isNotEmpty())
field = value

View File

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