From dd6208ef2503b1b2f027812cbe5fd9f8bed9442c Mon Sep 17 00:00:00 2001
From: Mike Hearn
Date: Fri, 11 Mar 2016 13:41:42 +0100
Subject: [PATCH 01/19] Docs: fix font size issue that's visible now I got
myself a bigger monitor and changed the default font size in my browser
---
docs/source/_static/css/custom.css | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/source/_static/css/custom.css b/docs/source/_static/css/custom.css
index d4c211d508..2fd187f700 100644
--- a/docs/source/_static/css/custom.css
+++ b/docs/source/_static/css/custom.css
@@ -28,4 +28,8 @@
.wy-nav-content {
max-width: 1000px;
+}
+
+p {
+ font-size: 100%; /* Get rid of RTD rule that assumes nobody changes their browser font size */
}
\ No newline at end of file
From 07f925a9f76d7adf2ddb1d731b6ce79f7a3c271e Mon Sep 17 00:00:00 2001
From: Mike Hearn
Date: Fri, 11 Mar 2016 13:42:11 +0100
Subject: [PATCH 02/19] Docs: remove roadmap, add a new doc page on oracles.
---
docs/source/index.rst | 2 +-
docs/source/inthebox.rst | 14 +--
docs/source/oracles.rst | 186 +++++++++++++++++++++++++++++++++++++++
docs/source/roadmap.rst | 37 --------
4 files changed, 195 insertions(+), 44 deletions(-)
create mode 100644 docs/source/oracles.rst
delete mode 100644 docs/source/roadmap.rst
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 473fa96175..e5a07d6b9b 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -38,12 +38,12 @@ Read on to learn:
tutorial
protocol-state-machines
+ oracles
.. toctree::
:maxdepth: 2
:caption: Appendix
visualiser
- roadmap
codestyle
diff --git a/docs/source/inthebox.rst b/docs/source/inthebox.rst
index 30be0a9db8..df7b4ad8c5 100644
--- a/docs/source/inthebox.rst
+++ b/docs/source/inthebox.rst
@@ -3,15 +3,16 @@ What's included?
The current prototype consists of a small amount of code that defines:
-* Key data structures
+* Key data structures.
* Algorithms that work with them, such as serialising, hashing, signing, and verification of the signatures.
-* Three smart contracts that implement a notion of a cash claim, a basic commercial paper and a crowdfunding contract.
- These are simplified versions of the real things.
+* Two smart contracts that implement a notion of a cash claim and basic commercial paper (implemented twice, in two
+ different programming languages). These are simplified versions of the real things.
* Unit tests that check the algorithms do what is expected, and which verify the behaviour of the smart contracts.
* API documentation and tutorials (what you're reading)
-* A simple standalone node that uses an embedded message queue broker as its P2P messaging layer
+* A simple standalone node that uses an embedded message queue broker as its P2P messaging layer.
* A trading demo that runs the node in either a listening/buying mode, or a connecting/selling mode, and swaps some
- fake commercial paper assets for some self-issued IOU cash.
+ fake commercial paper assets for some self-issued IOU cash, using a generic *protocol framework*.
+* It also includes two oracles: one for precise timestamping and another for interest rate swaps.
Some things it does not currently include but should gain later are:
@@ -27,8 +28,9 @@ You can browse `the JIRA bug tracker `_.
The prototype's goal is rapid exploration of ideas. Therefore in places it takes shortcuts that a production system
would not in order to boost productivity:
-* It uses a serialization framework instead of a well specified, vendor neutral protocol.
+* It uses an object graph serialization framework instead of a well specified, vendor neutral protocol.
* It uses secp256r1, an obsolete elliptic curve.
+* It uses the default, out of the box Apache Artemis MQ protocol instead of AMQP/1.0 (although switching should be easy)
Contracts
---------
diff --git a/docs/source/oracles.rst b/docs/source/oracles.rst
new file mode 100644
index 0000000000..5457b49fc6
--- /dev/null
+++ b/docs/source/oracles.rst
@@ -0,0 +1,186 @@
+.. highlight:: kotlin
+.. raw:: html
+
+
+
+
+Writing oracle services
+=======================
+
+This article covers *oracles*: network services that link the ledger to the outside world by providing facts that
+affect the validity of transactions.
+
+The current prototype includes two oracles:
+
+1. A timestamping service
+2. An interest rate fixing service
+
+We will examine the similarities and differences in their design, whilst covering how the oracle concept works.
+
+Introduction
+------------
+
+Oracles are a key concept in the block chain/decentralised ledger space. They can be essential for many kinds of
+application, because we often wish to condition a transaction on some fact being true or false, but the ledger itself
+has a design that is essentially functional: all transactions are *pure* and *immutable*. Phrased another way, a
+smart contract cannot perform any input/output or depend on any state outside of the transaction itself. There is no
+way to download a web page or interact with the user, in a smart contract. It must be this way because everyone must
+be able to independently check a transaction and arrive at an identical conclusion for the ledger to maintan its
+integrity: if a transaction could evaluate to "valid" on one computer and then "invalid" a few minutes later on a
+different computer, the entire shared ledger concept wouldn't work.
+
+But it is often essential that transactions do depend on data from the outside world, for example, verifying that an
+interest rate swap is paying out correctly may require data on interest rates, verifying that a loan has reached
+maturity requires knowledge about the current time, knowing which side of a bet receives the payment may require
+arbitrary facts about the real world (e.g. the bankruptcy or solvency of a company or country) ... and so on.
+
+We can solve this problem by introducing services that create digitally signed data structures which assert facts.
+These structures can then be used as an input to a transaction and distributed with the transaction data itself. Because
+the statements are themselves immutable and signed, it is impossible for an oracle to change its mind later and
+invalidate transactions that were previously found to be valid. In contrast, consider what would happen if a contract
+could do an HTTP request: it's possible that an answer would change after being downloaded, resulting in loss of
+consensus (breaks).
+
+The two basic approaches
+------------------------
+
+The architecture provides two ways of implementing oracles with different tradeoffs:
+
+1. Using commands
+2. Using attachments
+
+When a fact is encoded in a command, it is embedded in the transaction itself. The oracle then acts as a co-signer to
+the entire transaction. The oracle's signature is valid only for that transaction, and thus even if a fact (like a
+stock price) does not change, every transaction that incorporates that fact must go back to the oracle for signing.
+
+When a fact is encoded as an attachment, it is a separate object to the transaction which is referred to by hash.
+Nodes download attachments from peers at the same time as they download transactions, unless of course the node has
+already seen that attachment, in which case it won't fetch it again. Contracts have access to the contents of
+attachments and attachments can be digitally signed (in future).
+
+As you can see, both approaches share a few things: they both allow arbitrary binary data to be provided to transactions
+(and thus contracts). The primary difference is whether the data is a freely reusable, standalone object or whether it's
+integrated with a transaction.
+
+Here's a quick way to decide which approach makes more sense for your data source:
+
+* Is your data *continuously changing*, like a stock price, the current time, etc? If yes, use a command.
+* Is your data *commercially valuable*, like a feed which you are not allowed to resell unless it's incorporated into
+ a business deal? If yes, use a command, so you can charge money for signing the same fact in each unique business
+ context.
+* Is your data *very small*, like a single number? If yes, use a command.
+* Is your data *large*, *static* and *commercially worthless*, for instance, a holiday calendar? If yes, use an
+ attachment.
+* Is your data *intended for human consumption*, like a PDF of legal prose, or an Excel spreadsheet? If yes, use an
+ attachment.
+
+Asserting continuously varying data that is publicly known
+----------------------------------------------------------
+
+Let's look at the timestamping oracle that can be found in the ``TimestamperService`` class. This is an example of
+an oracle that uses a command because the current time is a constantly changing fact that everybody knows.
+
+The most obvious way to implement such a service would be:
+
+1. The creator of the transaction that depends on the time reads their local clock
+2. They insert a command with that time into the transaction
+3. They then send it to the oracle for signing.
+
+But this approach has a problem. There will never be exact clock synchronisation between the party creating the
+transaction and the oracle. This is not only due to physics, network latencies etc but because between inserting the
+command and getting the oracle to sign there may be many other steps, like sending the transaction to other parties
+involved in the trade as well, or even requesting human signoff. Thus the time observed by the oracle may be quite
+different to the time observed in step 1. This problem can occur any time an oracle attests to a constantly changing
+value.
+
+.. note:: It is assumed that "true time" for a timestamping oracle means GPS/NaviStar time as defined by the atomic
+ clocks at the US Naval Observatory. This time feed is extremely accurate and available globally for free.
+
+We fix it by including explicit tolerances in the command, which is defined like this:
+
+.. sourcecode:: kotlin
+
+ data class TimestampCommand(val after: Instant?, val before: Instant?) : CommandData
+ init {
+ if (after == null && before == null)
+ throw IllegalArgumentException("At least one of before/after must be specified")
+ if (after != null && before != null)
+ check(after <= before)
+ }
+ }
+
+This defines a class that has two optional fields: before and after, along with a constructor that imposes a couple
+more constraints that cannot be expressed in the type system, namely, that "after" actually is temporally after
+"before", and that at least one bound must be present. A timestamp command that doesn't contain anything is illegal.
+
+Thus we express that the *true value* of the fact "the current time" is actually unknowable. Even when both before and
+after times are included, the transaction could have occurred at any point between those two timestamps. In this case
+"occurrence" could mean the execution date, the value date, the trade date etc ... the oracle doesn't care what precise
+meaning the timestamp has to the contract.
+
+By creating a range that can be either closed or open at one end, we allow all of the following facts to be modelled:
+
+* This transaction occurred at some point after the given time (e.g. after a maturity event)
+* This transaction occurred at any time before the given time (e.g. before a bankruptcy event)
+* This transaction occurred at some point roughly around the given time (e.g. on a specific day)
+
+This same technique can be adapted to other types of oracle.
+
+Asserting occasionally varying data that is not publicly known
+--------------------------------------------------------------
+
+Sometimes you may want a fact that changes, but is not entirely continuous. Additionally the exact value may not be
+public, or may only be semi-public (e.g. easily available to some entities on the network but not all). An example of
+this would be a LIBOR interest rate fix.
+
+In this case, the following design can be used. The oracle service provides a query API which returns the current value,
+and a signing service that signs a transaction if the data in the command matches the answer being returned by the
+query API. Probably the query response contains some sort of timestamp as well, so the service can recognise values
+that were true in the past but no longer are (this is arguably a part of the fact being asserted).
+
+Because the signature covers the transaction, and transactions may end up being forwarded anywhere, the fact itself
+is independently checkable. However, this approach can be useful when the data itself costs money, because the act
+of issuing the signature in the first place can be charged for (e.g. by requiring the submission of a fresh
+``Cash.State`` that has been re-assigned to a key owned by the oracle service). Because the signature covers the
+*transaction* and not only the *fact*, this allows for a kind of weak pseudo-DRM over data feeds. Whilst a smart
+contract could in theory include a transaction parsing and signature checking library, writing a contract in this way
+would be conclusive evidence of intent to disobey the rules of the service (*res ipsa loquitur*). In an environment
+where parties are legally identifiable, usage of such a contract would by itself be sufficient to trigger some sort of
+punishment.
+
+Here is an extract from the ``NodeService.Oracle`` class and supporting types:
+
+.. sourcecode:: kotlin
+
+ /** A [FixOf] identifies the question side of a fix: what day, tenor and type of fix ("LIBOR", "EURIBOR" etc) */
+ data class FixOf(val name: String, val forDay: LocalDate, val ofTenor: Duration)
+
+ /** A [Fix] represents a named interest rate, on a given day, for a given duration. It can be embedded in a tx. */
+ data class Fix(val of: FixOf, val value: BigDecimal) : CommandData
+
+ class Oracle {
+ fun query(queries: List): List
+
+ fun sign(wtx: WireTransaction): DigitalSignature.LegallyIdentifiable
+ }
+
+Because the fix contains a timestamp (the ``forDay`` field), there can be an arbitrary delay between a fix being
+requested via ``query`` and the signature being requested via ``sign``.
+
+Implementing oracles in the framework
+-------------------------------------
+
+Implementation involves the following steps:
+
+1. Defining a high level oracle class, that exposes the basic API operations.
+2. Defining a lower level service class, that binds network messages to the API.
+3. Defining a protocol using the :doc:`protocol-state-machines` framework to make it easy for a client to interact
+ with the oracle.
+
+An example of how to do this can be found in the ``NodeInterestRates.Oracle``, ``NodeInterestRates.Service`` and
+``RateFixProtocol`` classes. The exact details of how this code works will change in future, so for now consulting
+the protocols tutorial and the code for the server-side oracles implementation will have to suffice. There will be more
+detail added once the platform APIs have settled down.
+
+Currently, there's no network map service, so the location and identity keys of an oracle must be distributed out of
+band.
\ No newline at end of file
diff --git a/docs/source/roadmap.rst b/docs/source/roadmap.rst
deleted file mode 100644
index 9a26720dcb..0000000000
--- a/docs/source/roadmap.rst
+++ /dev/null
@@ -1,37 +0,0 @@
-Roadmap
-=======
-
-The canonical place to learn about pending tasks is the `R3 JIRA `_ site. This
-page gives some examples of tasks that we wish to explore in future milestones as part of proving (or disproving)
-our core thesis
-
-Data distribution and management:
-
-* Introduce a pluggable network messaging backend with a mock implementation for testing, and an Apache Kafka based
- implementation for bringing up first networking capability. Using Kafka as a message routing/storage layer is not
- necessarily the final approach or suitable for P2P WAN messaging, but it should be a good next step for prototyping
- and may even be a useful for internal deployments.
-* Flesh out the core code enough to have a server that downloads and verifies transactions as they are uploaded to the
- cluster. At this stage all transactions are assumed to be public to the network (this will change later). Some basic
- logging/JMX/monitoring dashboard should be present to see what the node is doing.
-* Experimentation with block-free conflict/double spend resolution using a voting pool of *observers* with lazy consensus.
- Logic for rolling back losing transaction subgraphs when a conflict is resolved, reporting these events to observer
- APIs and so on.
-* Support a pluggable storage layer for recording seen transactions and their validity states.
-
-Contracts API:
-
-* Upgrades to the composability of contracts: demonstrate how states can require the presence of other states as a way
- to mix in things like multi-signature requirements.
-* Demonstrate how states can be separated into two parts, the minimum necessary for conflict resolution (e.g. owner keys)
- and a separated part that contains data useful for auditing and building confidence in the validity of a transaction
- (e.g. amounts).
-* Explorations of improved time handling, and how best to express temporal logic in the contract API/DSL.
-
-JVM adaptations:
-
-* Implement a sandbox and packaging system for contract logic. Contracts should be distributable through the network
- layer.
-* Experiment with modifications to HotSpot to allow for safely killing threads (i.e. fixing the issues that make
- Thread.stop() unsafe to use), and to measure and enforce runtime limits to handle runaway code.
-
From 139bf1e45062000abe5c36f12cc3b6b124400842 Mon Sep 17 00:00:00 2001
From: Mike Hearn
Date: Fri, 11 Mar 2016 17:02:35 +0100
Subject: [PATCH 03/19] Minor: fix trader demo
---
.../{core/node => demos}/bank-of-london-cp.jar | Bin
1 file changed, 0 insertions(+), 0 deletions(-)
rename src/main/resources/{core/node => demos}/bank-of-london-cp.jar (100%)
diff --git a/src/main/resources/core/node/bank-of-london-cp.jar b/src/main/resources/demos/bank-of-london-cp.jar
similarity index 100%
rename from src/main/resources/core/node/bank-of-london-cp.jar
rename to src/main/resources/demos/bank-of-london-cp.jar
From 730b7949ea01dcd4d7e51ddc19c9b1846ba565bc Mon Sep 17 00:00:00 2001
From: Mike Hearn
Date: Thu, 10 Mar 2016 18:14:41 +0100
Subject: [PATCH 04/19] Export stats to JMX from the state machine manager.
---
.../core/messaging/StateMachineManager.kt | 12 ++++++++++++
src/main/kotlin/core/node/AbstractNode.kt | 2 ++
src/main/kotlin/core/node/Node.kt | 17 +++++++++++++++++
src/main/kotlin/core/node/services/Services.kt | 8 ++++++++
src/test/kotlin/core/MockServices.kt | 3 +++
5 files changed, 42 insertions(+)
diff --git a/src/main/kotlin/core/messaging/StateMachineManager.kt b/src/main/kotlin/core/messaging/StateMachineManager.kt
index 369c76c7a5..beb667bb87 100644
--- a/src/main/kotlin/core/messaging/StateMachineManager.kt
+++ b/src/main/kotlin/core/messaging/StateMachineManager.kt
@@ -11,6 +11,7 @@ package core.messaging
import co.paralleluniverse.fibers.Fiber
import co.paralleluniverse.fibers.FiberExecutorScheduler
import co.paralleluniverse.io.serialization.kryo.KryoSerializer
+import com.codahale.metrics.Gauge
import com.esotericsoftware.kryo.io.Input
import com.google.common.base.Throwables
import com.google.common.util.concurrent.ListenableFuture
@@ -65,6 +66,13 @@ class StateMachineManager(val serviceHub: ServiceHub, val runInThread: Executor)
// property.
private val _stateMachines = Collections.synchronizedList(ArrayList>())
+ // Monitoring support.
+ private val metrics = serviceHub.monitoringService.metrics
+ init { metrics.register("Protocols.InFlight", Gauge { _stateMachines.size }) }
+ private val checkpointingMeter = metrics.meter("Protocols.Checkpointing Rate")
+ private val totalStartedProtocols = metrics.counter("Protocols.Started")
+ private val totalFinishedProtocols = metrics.counter("Protocols.Finished")
+
// This is a workaround for something Gradle does to us during unit tests. It replaces stderr with its own
// class that inserts itself into a ThreadLocal. That then gets caught in fiber serialisation, which we don't
// want because it can't get recreated properly. It turns out there's no good workaround for this! All the obvious
@@ -163,6 +171,8 @@ class StateMachineManager(val serviceHub: ServiceHub, val runInThread: Executor)
iterateStateMachine(fiber, serviceHub.networkService, logger, null, null) {
it.start()
}
+ _stateMachines.add(logic)
+ totalStartedProtocols.inc()
return fiber.resultFuture
}
@@ -173,6 +183,7 @@ class StateMachineManager(val serviceHub: ServiceHub, val runInThread: Executor)
checkpointsMap.remove(prevCheckpointKey)
val key = SecureHash.sha256(new)
checkpointsMap[key] = new
+ checkpointingMeter.mark()
return key
}
@@ -212,6 +223,7 @@ class StateMachineManager(val serviceHub: ServiceHub, val runInThread: Executor)
psm.logic.progressTracker?.currentStep = ProgressTracker.DONE
_stateMachines.remove(psm.logic)
checkpointsMap.remove(prevCheckpointKey)
+ totalFinishedProtocols.inc()
}
}
diff --git a/src/main/kotlin/core/node/AbstractNode.kt b/src/main/kotlin/core/node/AbstractNode.kt
index 50a6865641..75e959dcab 100644
--- a/src/main/kotlin/core/node/AbstractNode.kt
+++ b/src/main/kotlin/core/node/AbstractNode.kt
@@ -16,6 +16,7 @@
package core.node
+import com.codahale.metrics.MetricRegistry
import contracts.*
import core.*
import core.crypto.SecureHash
@@ -60,6 +61,7 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
override val walletService: WalletService get() = wallet
override val keyManagementService: KeyManagementService get() = keyManagement
override val identityService: IdentityService get() = identity
+ override val monitoringService: MonitoringService = MonitoringService(MetricRegistry())
}
val legallyIdentifableAddress: LegallyIdentifiableNode get() = LegallyIdentifiableNode(net.myAddress, storage.myLegalIdentity)
diff --git a/src/main/kotlin/core/node/Node.kt b/src/main/kotlin/core/node/Node.kt
index 0d5bb786ce..9f8fcb78c5 100644
--- a/src/main/kotlin/core/node/Node.kt
+++ b/src/main/kotlin/core/node/Node.kt
@@ -9,6 +9,7 @@
package core.node
import api.Config
+import com.codahale.metrics.JmxReporter
import com.google.common.net.HostAndPort
import core.messaging.LegallyIdentifiableNode
import core.messaging.MessagingService
@@ -29,6 +30,7 @@ import java.nio.channels.FileLock
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardOpenOption
+import javax.management.ObjectName
import kotlin.reflect.KClass
class ConfigurationException(message: String) : Exception(message)
@@ -112,6 +114,21 @@ class Node(dir: Path, val p2pAddr: HostAndPort, configuration: NodeConfiguration
webServer = initWebServer()
// Start up the MQ service.
(net as ArtemisMessagingService).start()
+ // Begin exporting our own metrics via JMX.
+ JmxReporter.
+ forRegistry(services.monitoringService.metrics).
+ inDomain("com.r3cev.corda").
+ createsObjectNamesWith { type, domain, name ->
+ // Make the JMX hierarchy a bit better organised.
+ val category = name.substringBefore('.')
+ val subName = name.substringAfter('.', "")
+ if (subName == "")
+ ObjectName("$domain:name=$category")
+ else
+ ObjectName("$domain:type=$category,name=$subName")
+ }.
+ build().
+ start()
return this
}
diff --git a/src/main/kotlin/core/node/services/Services.kt b/src/main/kotlin/core/node/services/Services.kt
index 02103ff015..e63e41102c 100644
--- a/src/main/kotlin/core/node/services/Services.kt
+++ b/src/main/kotlin/core/node/services/Services.kt
@@ -8,6 +8,7 @@
package core.node.services
+import com.codahale.metrics.MetricRegistry
import core.*
import core.crypto.SecureHash
import core.messaging.MessagingService
@@ -143,6 +144,12 @@ interface AttachmentStorage {
fun importAttachment(jar: InputStream): SecureHash
}
+/**
+ * Provides access to various metrics and ways to notify monitoring services of things, for sysadmin purposes.
+ * This is not an interface because it is too lightweight to bother mocking out.
+ */
+class MonitoringService(val metrics: MetricRegistry)
+
/**
* A service hub simply vends references to the other services a node has. Some of those services may be missing or
* mocked out. This class is useful to pass to chunks of pluggable code that might have need of many different kinds of
@@ -155,6 +162,7 @@ interface ServiceHub {
val storageService: StorageService
val networkService: MessagingService
val networkMapService: NetworkMapService
+ val monitoringService: MonitoringService
/**
* Given a [LedgerTransaction], looks up all its dependencies in the local database, uses the identity service to map
diff --git a/src/test/kotlin/core/MockServices.kt b/src/test/kotlin/core/MockServices.kt
index 0bbca83cea..be482753e4 100644
--- a/src/test/kotlin/core/MockServices.kt
+++ b/src/test/kotlin/core/MockServices.kt
@@ -8,6 +8,7 @@
package core
+import com.codahale.metrics.MetricRegistry
import core.crypto.*
import core.messaging.MessagingService
import core.messaging.MockNetworkMapService
@@ -169,6 +170,8 @@ class MockServices(
override val storageService: StorageService
get() = storage ?: throw UnsupportedOperationException()
+ override val monitoringService: MonitoringService = MonitoringService(MetricRegistry())
+
init {
if (net != null && storage != null) {
// Creating this class is sufficient, we don't have to store it anywhere, because it registers a listener
From 28869ad85de97c9adedfdaeb679bad58e97c80b7 Mon Sep 17 00:00:00 2001
From: Mike Hearn
Date: Fri, 11 Mar 2016 16:59:50 +0100
Subject: [PATCH 05/19] Export attachment count via JMX
---
src/main/kotlin/core/node/AbstractNode.kt | 2 +-
.../node/services/NodeAttachmentService.kt | 47 ++++++++++++-------
.../core/node/NodeAttachmentStorageTest.kt | 7 +--
3 files changed, 36 insertions(+), 20 deletions(-)
diff --git a/src/main/kotlin/core/node/AbstractNode.kt b/src/main/kotlin/core/node/AbstractNode.kt
index 75e959dcab..bfe3b61c32 100644
--- a/src/main/kotlin/core/node/AbstractNode.kt
+++ b/src/main/kotlin/core/node/AbstractNode.kt
@@ -211,6 +211,6 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
Files.createDirectory(attachmentsDir)
} catch (e: FileAlreadyExistsException) {
}
- return NodeAttachmentService(attachmentsDir)
+ return NodeAttachmentService(attachmentsDir, services.monitoringService.metrics)
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/core/node/services/NodeAttachmentService.kt b/src/main/kotlin/core/node/services/NodeAttachmentService.kt
index 57aa5f6511..962a8a590a 100644
--- a/src/main/kotlin/core/node/services/NodeAttachmentService.kt
+++ b/src/main/kotlin/core/node/services/NodeAttachmentService.kt
@@ -8,6 +8,7 @@
package core.node.services
+import com.codahale.metrics.MetricRegistry
import com.google.common.annotations.VisibleForTesting
import com.google.common.hash.Hashing
import com.google.common.hash.HashingInputStream
@@ -31,12 +32,21 @@ import javax.annotation.concurrent.ThreadSafe
* Stores attachments in the specified local directory, which must exist. Doesn't allow new attachments to be uploaded.
*/
@ThreadSafe
-class NodeAttachmentService(val storePath: Path) : AttachmentStorage, AcceptsFileUpload {
+class NodeAttachmentService(val storePath: Path, val metrics: MetricRegistry) : AttachmentStorage, AcceptsFileUpload {
private val log = loggerFor()
@VisibleForTesting
var checkAttachmentsOnLoad = true
+ private val attachmentCount = metrics.counter("Attachments")
+
+ init {
+ attachmentCount.inc(countAttachments())
+ }
+
+ // Just count all non-directories in the attachment store, and assume the admin hasn't dumped any junk there.
+ private fun countAttachments() = Files.list(storePath).filter { Files.isRegularFile(it) }.count()
+
/**
* If true, newly inserted attachments will be unzipped to a subdirectory of the [storePath]. This is intended for
* human browsing convenience: the attachment itself will still be the file (that is, edits to the extracted directory
@@ -77,22 +87,25 @@ class NodeAttachmentService(val storePath: Path) : AttachmentStorage, AcceptsFil
}
}
+ // Deliberately not an inner class to avoid holding a reference to the attachments service.
+ private class AttachmentImpl(override val id: SecureHash,
+ private val path: Path,
+ private val checkOnLoad: Boolean) : Attachment {
+ override fun open(): InputStream {
+ var stream = Files.newInputStream(path)
+ // This is just an optional safety check. If it slows things down too much it can be disabled.
+ if (id is SecureHash.SHA256 && checkOnLoad)
+ stream = HashCheckingStream(id, path, stream)
+ return stream
+ }
+ override fun equals(other: Any?) = other is Attachment && other.id == id
+ override fun hashCode(): Int = id.hashCode()
+ }
+
override fun openAttachment(id: SecureHash): Attachment? {
val path = storePath.resolve(id.toString())
if (!Files.exists(path)) return null
- return object : Attachment {
- override fun open(): InputStream {
- var stream = Files.newInputStream(path)
- // This is just an optional safety check. If it slows things down too much it can be disabled.
- if (id is SecureHash.SHA256 && checkAttachmentsOnLoad)
- stream = HashCheckingStream(id, path, stream)
- log.debug("Opening attachment $id")
- return stream
- }
- override val id: SecureHash = id
- override fun equals(other: Any?) = other is Attachment && other.id == id
- override fun hashCode(): Int = id.hashCode()
- }
+ return AttachmentImpl(id, path, checkAttachmentsOnLoad)
}
override fun importAttachment(jar: InputStream): SecureHash {
@@ -106,10 +119,12 @@ class NodeAttachmentService(val storePath: Path) : AttachmentStorage, AcceptsFil
try {
// Move into place atomically or fail if that isn't possible. We don't want a half moved attachment to
// be exposed to parallel threads. This gives us thread safety.
- if (!Files.exists(finalPath))
+ if (!Files.exists(finalPath)) {
log.info("Stored new attachment $id")
- else
+ attachmentCount.inc()
+ } else {
log.info("Replacing attachment $id - only bother doing this if you're trying to repair file corruption")
+ }
Files.move(tmp, finalPath, StandardCopyOption.ATOMIC_MOVE)
} finally {
Files.deleteIfExists(tmp)
diff --git a/src/test/kotlin/core/node/NodeAttachmentStorageTest.kt b/src/test/kotlin/core/node/NodeAttachmentStorageTest.kt
index 16dc8ef4c8..e1445d1c4a 100644
--- a/src/test/kotlin/core/node/NodeAttachmentStorageTest.kt
+++ b/src/test/kotlin/core/node/NodeAttachmentStorageTest.kt
@@ -8,6 +8,7 @@
package core.node
+import com.codahale.metrics.MetricRegistry
import com.google.common.jimfs.Configuration
import com.google.common.jimfs.Jimfs
import core.crypto.SecureHash
@@ -40,7 +41,7 @@ class NodeAttachmentStorageTest {
val testJar = makeTestJar()
val expectedHash = SecureHash.sha256(Files.readAllBytes(testJar))
- val storage = NodeAttachmentService(fs.getPath("/"))
+ val storage = NodeAttachmentService(fs.getPath("/"), MetricRegistry())
val id = testJar.use { storage.importAttachment(it) }
assertEquals(expectedHash, id)
@@ -57,7 +58,7 @@ class NodeAttachmentStorageTest {
@Test
fun `duplicates not allowed`() {
val testJar = makeTestJar()
- val storage = NodeAttachmentService(fs.getPath("/"))
+ val storage = NodeAttachmentService(fs.getPath("/"), MetricRegistry())
testJar.use { storage.importAttachment(it) }
assertFailsWith {
testJar.use { storage.importAttachment(it) }
@@ -67,7 +68,7 @@ class NodeAttachmentStorageTest {
@Test
fun `corrupt entry throws exception`() {
val testJar = makeTestJar()
- val storage = NodeAttachmentService(fs.getPath("/"))
+ val storage = NodeAttachmentService(fs.getPath("/"), MetricRegistry())
val id = testJar.use { storage.importAttachment(it) }
// Corrupt the file in the store.
From 07eee0233f88e7bda9e53b418b3e93126ab65bf0 Mon Sep 17 00:00:00 2001
From: Mike Hearn
Date: Fri, 11 Mar 2016 17:55:03 +0100
Subject: [PATCH 06/19] Export wallet balances via JMX.
---
.../core/node/services/NodeWalletService.kt | 36 +++++++++++++------
.../kotlin/core/node/services/Services.kt | 13 +++++++
2 files changed, 39 insertions(+), 10 deletions(-)
diff --git a/src/main/kotlin/core/node/services/NodeWalletService.kt b/src/main/kotlin/core/node/services/NodeWalletService.kt
index 4a5ac90660..3dfe1cb98b 100644
--- a/src/main/kotlin/core/node/services/NodeWalletService.kt
+++ b/src/main/kotlin/core/node/services/NodeWalletService.kt
@@ -8,6 +8,7 @@
package core.node.services
+import com.codahale.metrics.Gauge
import contracts.Cash
import core.*
import core.utilities.loggerFor
@@ -39,16 +40,7 @@ class NodeWalletService(private val services: ServiceHub) : WalletService {
* Returns a snapshot of how much cash we have in each currency, ignoring details like issuer. Note: currencies for
* which we have no cash evaluate to null, not 0.
*/
- override val cashBalances: Map
- get() = mutex.locked { wallet }.let { wallet ->
- wallet.states.
- // Select the states we own which are cash, ignore the rest, take the amounts.
- mapNotNull { (it.state as? Cash.State)?.amount }.
- // Turn into a Map> like { GBP -> (£100, £500, etc), USD -> ($2000, $50) }
- groupBy { it.currency }.
- // Collapse to Map by summing all the amounts of the same currency together.
- mapValues { it.value.sumOrThrow() }
- }
+ override val cashBalances: Map get() = mutex.locked { wallet }.cashBalances
override fun notifyAll(txns: Iterable): Wallet {
val ourKeys = services.keyManagementService.keys.keys
@@ -71,6 +63,7 @@ class NodeWalletService(private val services: ServiceHub) : WalletService {
// time, until we get to the result (this is perhaps a bit inefficient, but it's functional and easily
// unit tested).
wallet = txns.fold(currentWallet) { current, tx -> current.update(tx, ourKeys) }
+ exportCashBalancesViaMetrics(wallet)
return wallet
}
}
@@ -100,6 +93,29 @@ class NodeWalletService(private val services: ServiceHub) : WalletService {
return Wallet(newStates)
}
+ private class BalanceMetric : Gauge {
+ @Volatile var pennies = 0L
+ override fun getValue(): Long? = pennies
+ }
+
+ private val balanceMetrics = HashMap()
+
+ private fun exportCashBalancesViaMetrics(wallet: Wallet) {
+ // This is just for demo purposes. We probably shouldn't expose balances via JMX in a real node as that might
+ // be commercially sensitive info that the sysadmins aren't even meant to know.
+ //
+ // Note: exported as pennies.
+ val m = services.monitoringService.metrics
+ for (balance in wallet.cashBalances) {
+ val metric = balanceMetrics.getOrPut(balance.key) {
+ val newMetric = BalanceMetric()
+ m.register("WalletBalances.${balance.key}Pennies", newMetric)
+ newMetric
+ }
+ metric.pennies = balance.value.pennies
+ }
+ }
+
/**
* Creates a random set of between (by default) 3 and 10 cash states that add up to the given amount and adds them
* to the wallet.
diff --git a/src/main/kotlin/core/node/services/Services.kt b/src/main/kotlin/core/node/services/Services.kt
index e63e41102c..367e106a65 100644
--- a/src/main/kotlin/core/node/services/Services.kt
+++ b/src/main/kotlin/core/node/services/Services.kt
@@ -9,6 +9,7 @@
package core.node.services
import com.codahale.metrics.MetricRegistry
+import contracts.Cash
import core.*
import core.crypto.SecureHash
import core.messaging.MessagingService
@@ -33,6 +34,18 @@ import java.util.*
data class Wallet(val states: List>) {
@Suppress("UNCHECKED_CAST")
inline fun statesOfType() = states.filter { it.state is T } as List>
+
+ /**
+ * Returns a map of how much cash we have in each currency, ignoring details like issuer. Note: currencies for
+ * which we have no cash evaluate to null (not present in map), not 0.
+ */
+ val cashBalances: Map get() = states.
+ // Select the states we own which are cash, ignore the rest, take the amounts.
+ mapNotNull { (it.state as? Cash.State)?.amount }.
+ // Turn into a Map> like { GBP -> (£100, £500, etc), USD -> ($2000, $50) }
+ groupBy { it.currency }.
+ // Collapse to Map by summing all the amounts of the same currency together.
+ mapValues { it.value.sumOrThrow() }
}
/**
From 3533e879537c9996c950ebe73a43495adc3033b3 Mon Sep 17 00:00:00 2001
From: Mike Hearn
Date: Fri, 11 Mar 2016 18:12:18 +0100
Subject: [PATCH 07/19] Take out the fake signing delay in the trader demo.
---
src/main/kotlin/demos/TraderDemo.kt | 13 ++-----------
1 file changed, 2 insertions(+), 11 deletions(-)
diff --git a/src/main/kotlin/demos/TraderDemo.kt b/src/main/kotlin/demos/TraderDemo.kt
index f16327d052..a99ac5e2d5 100644
--- a/src/main/kotlin/demos/TraderDemo.kt
+++ b/src/main/kotlin/demos/TraderDemo.kt
@@ -12,7 +12,6 @@ import co.paralleluniverse.fibers.Suspendable
import com.google.common.net.HostAndPort
import contracts.CommercialPaper
import core.*
-import core.crypto.DigitalSignature
import core.crypto.SecureHash
import core.crypto.generateKeyPair
import core.messaging.LegallyIdentifiableNode
@@ -230,16 +229,8 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort,
progressTracker.currentStep = TRADING
- val seller = object : TwoPartyTradeProtocol.Seller(otherSide, tsa, commercialPaper, 1000.DOLLARS,
- cpOwnerKey, sessionID, progressTracker.childrenFor[TRADING]!!) {
- override fun signWithOurKey(partialTX: SignedTransaction): DigitalSignature.WithKey {
- val s = super.signWithOurKey(partialTX)
- // Fake delay to make it look like we're doing something more intensive than we really are, to show
- // the progress tracking framework.
- Thread.sleep(2000)
- return s
- }
- }
+ val seller = TwoPartyTradeProtocol.Seller(otherSide, tsa, commercialPaper, 1000.DOLLARS, cpOwnerKey, sessionID,
+ progressTracker.childrenFor[TRADING]!!)
val tradeTX: SignedTransaction = subProtocol(seller)
logger.info("Sale completed - we have a happy customer!\n\nFinal transaction is:\n\n${Emoji.renderIfSupported(tradeTX.tx)}")
From bc5f29c5ee0aa09df4619c8e4d114a392cc3f22c Mon Sep 17 00:00:00 2001
From: Mike Hearn
Date: Mon, 14 Mar 2016 16:57:36 +0100
Subject: [PATCH 08/19] Regen docsite
---
docs/build/html/_sources/index.txt | 2 +-
docs/build/html/_sources/inthebox.txt | 14 +-
docs/build/html/_sources/oracles.txt | 186 ++++++
docs/build/html/_static/css/custom.css | 4 +
docs/build/html/api/alltypes/index.html | 78 ++-
docs/build/html/api/api/-config/-init-.html | 16 +
.../-local-date-deserializer/deserialize.html | 15 +
.../-local-date-deserializer/index.html | 25 +
.../-config/-to-string-serializer/index.html | 25 +
.../-to-string-serializer/serialize.html | 15 +
.../api/-config/default-object-mapper.html | 15 +
.../html/api/api/-config/get-context.html | 15 +
docs/build/html/api/api/-config/index.html | 68 ++
docs/build/html/api/api/index.html | 24 +
.../-mock-network-map-service/-init-.html | 14 +
.../-mock-network-map-service/index.html | 36 ++
.../timestamping-nodes.html | 16 +
.../-network-map-service/index.html | 44 ++
.../timestamping-nodes.html | 15 +
docs/build/html/api/core.messaging/index.html | 8 +-
.../-attachment-storage/index.html | 2 +-
.../-monitoring-service/-init-.html | 16 +
.../-monitoring-service/index.html | 40 ++
.../-monitoring-service/metrics.html | 15 +
.../-node-attachment-service/-init-.html | 2 +-
.../acceptable-file-extensions.html | 17 +
.../data-type-prefix.html | 17 +
.../-node-attachment-service/index.html | 32 +-
.../-node-attachment-service/metrics.html | 15 +
.../-node-attachment-service/upload.html | 18 +
.../-node-interest-rates/-oracle/-init-.html | 15 +
.../-oracle/identity.html | 15 +
.../-node-interest-rates/-oracle/index.html | 62 ++
.../-oracle/known-fixes.html | 16 +
.../-node-interest-rates/-oracle/query.html | 15 +
.../-node-interest-rates/-oracle/sign.html | 15 +
.../-node-interest-rates/-service/-init-.html | 15 +
.../-service/acceptable-file-extensions.html | 17 +
.../-service/data-type-prefix.html | 17 +
.../-node-interest-rates/-service/index.html | 77 +++
.../-node-interest-rates/-service/net.html | 15 +
.../-node-interest-rates/-service/oracle.html | 15 +
.../-node-interest-rates/-service/ss.html | 15 +
.../-node-interest-rates/-service/upload.html | 18 +
.../-unknown-fix/-init-.html | 14 +
.../-unknown-fix/fix.html | 15 +
.../-unknown-fix/index.html | 47 ++
.../-unknown-fix/to-string.html | 15 +
.../-node-interest-rates/index.html | 78 +++
.../-node-interest-rates/parse-file.html | 16 +
.../-node-interest-rates/parse-fix-of.html | 16 +
.../-node-interest-rates/parse-one-rate.html | 16 +
.../-service-hub/index.html | 8 +-
.../-service-hub/monitoring-service.html | 15 +
.../-service-hub/network-map-service.html | 2 +-
.../-wallet/cash-balances.html | 17 +
.../api/core.node.services/-wallet/index.html | 8 +
.../html/api/core.node.services/index.html | 18 +-
.../-data-upload-servlet/-init-.html | 15 +
.../-data-upload-servlet/do-post.html | 15 +
.../-data-upload-servlet/index.html | 38 ++
.../html/api/core.node.servlets/index.html | 5 +-
.../_services-that-accept-uploads.html | 15 +
.../api/core.node/-abstract-node/index.html | 18 +
.../make-interest-rate-oracle-service.html | 15 +
.../services-that-accept-uploads.html | 15 +
.../acceptable-file-extensions.html | 16 +
.../data-type-prefix.html | 16 +
.../core.node/-accepts-file-upload/index.html | 69 ++
.../-accepts-file-upload/upload.html | 17 +
.../export-j-m-xto.html | 16 +
.../-default-configuration/index.html | 42 ++
.../-default-configuration/my-legal-name.html | 16 +
.../-default-configuration/to-properties.html | 15 +
.../export-j-m-xto.html | 16 +
.../index.html | 6 +
.../-node-configuration/export-j-m-xto.html | 15 +
.../core.node/-node-configuration/index.html | 12 +
.../build/html/api/core.node/-node/index.html | 18 +
docs/build/html/api/core.node/index.html | 13 +
.../core.protocols/-protocol-logic/index.html | 10 +
.../-progress-tracker/-step/index.html | 18 +
docs/build/html/api/core/-command-data.html | 7 +
docs/build/html/api/core/-fix-of/-init-.html | 15 +
docs/build/html/api/core/-fix-of/for-day.html | 15 +
docs/build/html/api/core/-fix-of/index.html | 50 ++
docs/build/html/api/core/-fix-of/name.html | 15 +
.../build/html/api/core/-fix-of/of-tenor.html | 15 +
docs/build/html/api/core/-fix/-init-.html | 15 +
docs/build/html/api/core/-fix/index.html | 44 ++
docs/build/html/api/core/-fix/of.html | 15 +
docs/build/html/api/core/-fix/value.html | 15 +
docs/build/html/api/core/index.html | 14 +
docs/build/html/api/demos/index.html | 2 +
docs/build/html/api/demos/main.html | 6 +
docs/build/html/api/index-outline.html | 592 +++++++++++++++++-
docs/build/html/api/index.html | 6 +
.../-fix-out-of-range/-init-.html | 14 +
.../-fix-out-of-range/by-amount.html | 15 +
.../-fix-out-of-range/index.html | 36 ++
.../protocols/-rates-fix-protocol/-init-.html | 21 +
.../-q-u-e-r-y-i-n-g/-init-.html | 14 +
.../-q-u-e-r-y-i-n-g/index.html | 53 ++
.../-q-u-e-r-y-i-n-g/name.html | 15 +
.../-query-request/-init-.html | 14 +
.../-query-request/index.html | 48 ++
.../-query-request/queries.html | 15 +
.../-query-request/reply-to.html | 15 +
.../-query-request/session-i-d.html | 15 +
.../-rates-fix-protocol/-s-i-g-n-i-n-g.html | 31 +
.../-sign-request/-init-.html | 14 +
.../-sign-request/index.html | 48 ++
.../-sign-request/reply-to.html | 15 +
.../-sign-request/session-i-d.html | 15 +
.../-rates-fix-protocol/-sign-request/tx.html | 15 +
.../-rates-fix-protocol/-t-o-p-i-c.html | 15 +
.../-rates-fix-protocol/-w-o-r-k-i-n-g.html | 31 +
.../-rates-fix-protocol/before-signing.html | 17 +
.../protocols/-rates-fix-protocol/call.html | 17 +
.../protocols/-rates-fix-protocol/index.html | 203 ++++++
.../-rates-fix-protocol/progress-tracker.html | 24 +
.../protocols/-rates-fix-protocol/query.html | 15 +
.../protocols/-rates-fix-protocol/sign.html | 15 +
.../api/protocols/-rates-fix-protocol/tx.html | 15 +
docs/build/html/api/protocols/index.html | 10 +
docs/build/html/genindex.html | 2 +-
docs/build/html/index.html | 13 +-
docs/build/html/inthebox.html | 16 +-
docs/build/html/oracles.html | 388 ++++++++++++
docs/build/html/search.html | 2 +-
docs/build/html/searchindex.js | 2 +-
131 files changed, 3824 insertions(+), 72 deletions(-)
create mode 100644 docs/build/html/_sources/oracles.txt
create mode 100644 docs/build/html/api/api/-config/-init-.html
create mode 100644 docs/build/html/api/api/-config/-local-date-deserializer/deserialize.html
create mode 100644 docs/build/html/api/api/-config/-local-date-deserializer/index.html
create mode 100644 docs/build/html/api/api/-config/-to-string-serializer/index.html
create mode 100644 docs/build/html/api/api/-config/-to-string-serializer/serialize.html
create mode 100644 docs/build/html/api/api/-config/default-object-mapper.html
create mode 100644 docs/build/html/api/api/-config/get-context.html
create mode 100644 docs/build/html/api/api/-config/index.html
create mode 100644 docs/build/html/api/api/index.html
create mode 100644 docs/build/html/api/core.messaging/-mock-network-map-service/-init-.html
create mode 100644 docs/build/html/api/core.messaging/-mock-network-map-service/index.html
create mode 100644 docs/build/html/api/core.messaging/-mock-network-map-service/timestamping-nodes.html
create mode 100644 docs/build/html/api/core.messaging/-network-map-service/index.html
create mode 100644 docs/build/html/api/core.messaging/-network-map-service/timestamping-nodes.html
create mode 100644 docs/build/html/api/core.node.services/-monitoring-service/-init-.html
create mode 100644 docs/build/html/api/core.node.services/-monitoring-service/index.html
create mode 100644 docs/build/html/api/core.node.services/-monitoring-service/metrics.html
create mode 100644 docs/build/html/api/core.node.services/-node-attachment-service/acceptable-file-extensions.html
create mode 100644 docs/build/html/api/core.node.services/-node-attachment-service/data-type-prefix.html
create mode 100644 docs/build/html/api/core.node.services/-node-attachment-service/metrics.html
create mode 100644 docs/build/html/api/core.node.services/-node-attachment-service/upload.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/-oracle/-init-.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/-oracle/identity.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/-oracle/index.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/-oracle/known-fixes.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/-oracle/query.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/-oracle/sign.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/-service/-init-.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/-service/acceptable-file-extensions.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/-service/data-type-prefix.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/-service/index.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/-service/net.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/-service/oracle.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/-service/ss.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/-service/upload.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/-unknown-fix/-init-.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/-unknown-fix/fix.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/-unknown-fix/index.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/-unknown-fix/to-string.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/index.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/parse-file.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/parse-fix-of.html
create mode 100644 docs/build/html/api/core.node.services/-node-interest-rates/parse-one-rate.html
create mode 100644 docs/build/html/api/core.node.services/-service-hub/monitoring-service.html
create mode 100644 docs/build/html/api/core.node.services/-wallet/cash-balances.html
create mode 100644 docs/build/html/api/core.node.servlets/-data-upload-servlet/-init-.html
create mode 100644 docs/build/html/api/core.node.servlets/-data-upload-servlet/do-post.html
create mode 100644 docs/build/html/api/core.node.servlets/-data-upload-servlet/index.html
create mode 100644 docs/build/html/api/core.node/-abstract-node/_services-that-accept-uploads.html
create mode 100644 docs/build/html/api/core.node/-abstract-node/make-interest-rate-oracle-service.html
create mode 100644 docs/build/html/api/core.node/-abstract-node/services-that-accept-uploads.html
create mode 100644 docs/build/html/api/core.node/-accepts-file-upload/acceptable-file-extensions.html
create mode 100644 docs/build/html/api/core.node/-accepts-file-upload/data-type-prefix.html
create mode 100644 docs/build/html/api/core.node/-accepts-file-upload/index.html
create mode 100644 docs/build/html/api/core.node/-accepts-file-upload/upload.html
create mode 100644 docs/build/html/api/core.node/-default-configuration/export-j-m-xto.html
create mode 100644 docs/build/html/api/core.node/-default-configuration/index.html
create mode 100644 docs/build/html/api/core.node/-default-configuration/my-legal-name.html
create mode 100644 docs/build/html/api/core.node/-default-configuration/to-properties.html
create mode 100644 docs/build/html/api/core.node/-node-configuration-from-properties/export-j-m-xto.html
create mode 100644 docs/build/html/api/core.node/-node-configuration/export-j-m-xto.html
create mode 100644 docs/build/html/api/core/-fix-of/-init-.html
create mode 100644 docs/build/html/api/core/-fix-of/for-day.html
create mode 100644 docs/build/html/api/core/-fix-of/index.html
create mode 100644 docs/build/html/api/core/-fix-of/name.html
create mode 100644 docs/build/html/api/core/-fix-of/of-tenor.html
create mode 100644 docs/build/html/api/core/-fix/-init-.html
create mode 100644 docs/build/html/api/core/-fix/index.html
create mode 100644 docs/build/html/api/core/-fix/of.html
create mode 100644 docs/build/html/api/core/-fix/value.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-fix-out-of-range/-init-.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-fix-out-of-range/by-amount.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-fix-out-of-range/index.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-init-.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-q-u-e-r-y-i-n-g/-init-.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-q-u-e-r-y-i-n-g/index.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-q-u-e-r-y-i-n-g/name.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-query-request/-init-.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-query-request/index.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-query-request/queries.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-query-request/reply-to.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-query-request/session-i-d.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-s-i-g-n-i-n-g.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-sign-request/-init-.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-sign-request/index.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-sign-request/reply-to.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-sign-request/session-i-d.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-sign-request/tx.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-t-o-p-i-c.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/-w-o-r-k-i-n-g.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/before-signing.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/call.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/index.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/progress-tracker.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/query.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/sign.html
create mode 100644 docs/build/html/api/protocols/-rates-fix-protocol/tx.html
create mode 100644 docs/build/html/oracles.html
diff --git a/docs/build/html/_sources/index.txt b/docs/build/html/_sources/index.txt
index 473fa96175..e5a07d6b9b 100644
--- a/docs/build/html/_sources/index.txt
+++ b/docs/build/html/_sources/index.txt
@@ -38,12 +38,12 @@ Read on to learn:
tutorial
protocol-state-machines
+ oracles
.. toctree::
:maxdepth: 2
:caption: Appendix
visualiser
- roadmap
codestyle
diff --git a/docs/build/html/_sources/inthebox.txt b/docs/build/html/_sources/inthebox.txt
index 30be0a9db8..df7b4ad8c5 100644
--- a/docs/build/html/_sources/inthebox.txt
+++ b/docs/build/html/_sources/inthebox.txt
@@ -3,15 +3,16 @@ What's included?
The current prototype consists of a small amount of code that defines:
-* Key data structures
+* Key data structures.
* Algorithms that work with them, such as serialising, hashing, signing, and verification of the signatures.
-* Three smart contracts that implement a notion of a cash claim, a basic commercial paper and a crowdfunding contract.
- These are simplified versions of the real things.
+* Two smart contracts that implement a notion of a cash claim and basic commercial paper (implemented twice, in two
+ different programming languages). These are simplified versions of the real things.
* Unit tests that check the algorithms do what is expected, and which verify the behaviour of the smart contracts.
* API documentation and tutorials (what you're reading)
-* A simple standalone node that uses an embedded message queue broker as its P2P messaging layer
+* A simple standalone node that uses an embedded message queue broker as its P2P messaging layer.
* A trading demo that runs the node in either a listening/buying mode, or a connecting/selling mode, and swaps some
- fake commercial paper assets for some self-issued IOU cash.
+ fake commercial paper assets for some self-issued IOU cash, using a generic *protocol framework*.
+* It also includes two oracles: one for precise timestamping and another for interest rate swaps.
Some things it does not currently include but should gain later are:
@@ -27,8 +28,9 @@ You can browse `the JIRA bug tracker `_.
The prototype's goal is rapid exploration of ideas. Therefore in places it takes shortcuts that a production system
would not in order to boost productivity:
-* It uses a serialization framework instead of a well specified, vendor neutral protocol.
+* It uses an object graph serialization framework instead of a well specified, vendor neutral protocol.
* It uses secp256r1, an obsolete elliptic curve.
+* It uses the default, out of the box Apache Artemis MQ protocol instead of AMQP/1.0 (although switching should be easy)
Contracts
---------
diff --git a/docs/build/html/_sources/oracles.txt b/docs/build/html/_sources/oracles.txt
new file mode 100644
index 0000000000..5457b49fc6
--- /dev/null
+++ b/docs/build/html/_sources/oracles.txt
@@ -0,0 +1,186 @@
+.. highlight:: kotlin
+.. raw:: html
+
+
+
+
+Writing oracle services
+=======================
+
+This article covers *oracles*: network services that link the ledger to the outside world by providing facts that
+affect the validity of transactions.
+
+The current prototype includes two oracles:
+
+1. A timestamping service
+2. An interest rate fixing service
+
+We will examine the similarities and differences in their design, whilst covering how the oracle concept works.
+
+Introduction
+------------
+
+Oracles are a key concept in the block chain/decentralised ledger space. They can be essential for many kinds of
+application, because we often wish to condition a transaction on some fact being true or false, but the ledger itself
+has a design that is essentially functional: all transactions are *pure* and *immutable*. Phrased another way, a
+smart contract cannot perform any input/output or depend on any state outside of the transaction itself. There is no
+way to download a web page or interact with the user, in a smart contract. It must be this way because everyone must
+be able to independently check a transaction and arrive at an identical conclusion for the ledger to maintan its
+integrity: if a transaction could evaluate to "valid" on one computer and then "invalid" a few minutes later on a
+different computer, the entire shared ledger concept wouldn't work.
+
+But it is often essential that transactions do depend on data from the outside world, for example, verifying that an
+interest rate swap is paying out correctly may require data on interest rates, verifying that a loan has reached
+maturity requires knowledge about the current time, knowing which side of a bet receives the payment may require
+arbitrary facts about the real world (e.g. the bankruptcy or solvency of a company or country) ... and so on.
+
+We can solve this problem by introducing services that create digitally signed data structures which assert facts.
+These structures can then be used as an input to a transaction and distributed with the transaction data itself. Because
+the statements are themselves immutable and signed, it is impossible for an oracle to change its mind later and
+invalidate transactions that were previously found to be valid. In contrast, consider what would happen if a contract
+could do an HTTP request: it's possible that an answer would change after being downloaded, resulting in loss of
+consensus (breaks).
+
+The two basic approaches
+------------------------
+
+The architecture provides two ways of implementing oracles with different tradeoffs:
+
+1. Using commands
+2. Using attachments
+
+When a fact is encoded in a command, it is embedded in the transaction itself. The oracle then acts as a co-signer to
+the entire transaction. The oracle's signature is valid only for that transaction, and thus even if a fact (like a
+stock price) does not change, every transaction that incorporates that fact must go back to the oracle for signing.
+
+When a fact is encoded as an attachment, it is a separate object to the transaction which is referred to by hash.
+Nodes download attachments from peers at the same time as they download transactions, unless of course the node has
+already seen that attachment, in which case it won't fetch it again. Contracts have access to the contents of
+attachments and attachments can be digitally signed (in future).
+
+As you can see, both approaches share a few things: they both allow arbitrary binary data to be provided to transactions
+(and thus contracts). The primary difference is whether the data is a freely reusable, standalone object or whether it's
+integrated with a transaction.
+
+Here's a quick way to decide which approach makes more sense for your data source:
+
+* Is your data *continuously changing*, like a stock price, the current time, etc? If yes, use a command.
+* Is your data *commercially valuable*, like a feed which you are not allowed to resell unless it's incorporated into
+ a business deal? If yes, use a command, so you can charge money for signing the same fact in each unique business
+ context.
+* Is your data *very small*, like a single number? If yes, use a command.
+* Is your data *large*, *static* and *commercially worthless*, for instance, a holiday calendar? If yes, use an
+ attachment.
+* Is your data *intended for human consumption*, like a PDF of legal prose, or an Excel spreadsheet? If yes, use an
+ attachment.
+
+Asserting continuously varying data that is publicly known
+----------------------------------------------------------
+
+Let's look at the timestamping oracle that can be found in the ``TimestamperService`` class. This is an example of
+an oracle that uses a command because the current time is a constantly changing fact that everybody knows.
+
+The most obvious way to implement such a service would be:
+
+1. The creator of the transaction that depends on the time reads their local clock
+2. They insert a command with that time into the transaction
+3. They then send it to the oracle for signing.
+
+But this approach has a problem. There will never be exact clock synchronisation between the party creating the
+transaction and the oracle. This is not only due to physics, network latencies etc but because between inserting the
+command and getting the oracle to sign there may be many other steps, like sending the transaction to other parties
+involved in the trade as well, or even requesting human signoff. Thus the time observed by the oracle may be quite
+different to the time observed in step 1. This problem can occur any time an oracle attests to a constantly changing
+value.
+
+.. note:: It is assumed that "true time" for a timestamping oracle means GPS/NaviStar time as defined by the atomic
+ clocks at the US Naval Observatory. This time feed is extremely accurate and available globally for free.
+
+We fix it by including explicit tolerances in the command, which is defined like this:
+
+.. sourcecode:: kotlin
+
+ data class TimestampCommand(val after: Instant?, val before: Instant?) : CommandData
+ init {
+ if (after == null && before == null)
+ throw IllegalArgumentException("At least one of before/after must be specified")
+ if (after != null && before != null)
+ check(after <= before)
+ }
+ }
+
+This defines a class that has two optional fields: before and after, along with a constructor that imposes a couple
+more constraints that cannot be expressed in the type system, namely, that "after" actually is temporally after
+"before", and that at least one bound must be present. A timestamp command that doesn't contain anything is illegal.
+
+Thus we express that the *true value* of the fact "the current time" is actually unknowable. Even when both before and
+after times are included, the transaction could have occurred at any point between those two timestamps. In this case
+"occurrence" could mean the execution date, the value date, the trade date etc ... the oracle doesn't care what precise
+meaning the timestamp has to the contract.
+
+By creating a range that can be either closed or open at one end, we allow all of the following facts to be modelled:
+
+* This transaction occurred at some point after the given time (e.g. after a maturity event)
+* This transaction occurred at any time before the given time (e.g. before a bankruptcy event)
+* This transaction occurred at some point roughly around the given time (e.g. on a specific day)
+
+This same technique can be adapted to other types of oracle.
+
+Asserting occasionally varying data that is not publicly known
+--------------------------------------------------------------
+
+Sometimes you may want a fact that changes, but is not entirely continuous. Additionally the exact value may not be
+public, or may only be semi-public (e.g. easily available to some entities on the network but not all). An example of
+this would be a LIBOR interest rate fix.
+
+In this case, the following design can be used. The oracle service provides a query API which returns the current value,
+and a signing service that signs a transaction if the data in the command matches the answer being returned by the
+query API. Probably the query response contains some sort of timestamp as well, so the service can recognise values
+that were true in the past but no longer are (this is arguably a part of the fact being asserted).
+
+Because the signature covers the transaction, and transactions may end up being forwarded anywhere, the fact itself
+is independently checkable. However, this approach can be useful when the data itself costs money, because the act
+of issuing the signature in the first place can be charged for (e.g. by requiring the submission of a fresh
+``Cash.State`` that has been re-assigned to a key owned by the oracle service). Because the signature covers the
+*transaction* and not only the *fact*, this allows for a kind of weak pseudo-DRM over data feeds. Whilst a smart
+contract could in theory include a transaction parsing and signature checking library, writing a contract in this way
+would be conclusive evidence of intent to disobey the rules of the service (*res ipsa loquitur*). In an environment
+where parties are legally identifiable, usage of such a contract would by itself be sufficient to trigger some sort of
+punishment.
+
+Here is an extract from the ``NodeService.Oracle`` class and supporting types:
+
+.. sourcecode:: kotlin
+
+ /** A [FixOf] identifies the question side of a fix: what day, tenor and type of fix ("LIBOR", "EURIBOR" etc) */
+ data class FixOf(val name: String, val forDay: LocalDate, val ofTenor: Duration)
+
+ /** A [Fix] represents a named interest rate, on a given day, for a given duration. It can be embedded in a tx. */
+ data class Fix(val of: FixOf, val value: BigDecimal) : CommandData
+
+ class Oracle {
+ fun query(queries: List): List
+
+ fun sign(wtx: WireTransaction): DigitalSignature.LegallyIdentifiable
+ }
+
+Because the fix contains a timestamp (the ``forDay`` field), there can be an arbitrary delay between a fix being
+requested via ``query`` and the signature being requested via ``sign``.
+
+Implementing oracles in the framework
+-------------------------------------
+
+Implementation involves the following steps:
+
+1. Defining a high level oracle class, that exposes the basic API operations.
+2. Defining a lower level service class, that binds network messages to the API.
+3. Defining a protocol using the :doc:`protocol-state-machines` framework to make it easy for a client to interact
+ with the oracle.
+
+An example of how to do this can be found in the ``NodeInterestRates.Oracle``, ``NodeInterestRates.Service`` and
+``RateFixProtocol`` classes. The exact details of how this code works will change in future, so for now consulting
+the protocols tutorial and the code for the server-side oracles implementation will have to suffice. There will be more
+detail added once the platform APIs have settled down.
+
+Currently, there's no network map service, so the location and identity keys of an oracle must be distributed out of
+band.
\ No newline at end of file
diff --git a/docs/build/html/_static/css/custom.css b/docs/build/html/_static/css/custom.css
index d4c211d508..2fd187f700 100644
--- a/docs/build/html/_static/css/custom.css
+++ b/docs/build/html/_static/css/custom.css
@@ -28,4 +28,8 @@
.wy-nav-content {
max-width: 1000px;
+}
+
+p {
+ font-size: 100%; /* Get rid of RTD rule that assumes nobody changes their browser font size */
}
\ No newline at end of file
diff --git a/docs/build/html/api/alltypes/index.html b/docs/build/html/api/alltypes/index.html
index 6081064eec..c0c31b9358 100644
--- a/docs/build/html/api/alltypes/index.html
+++ b/docs/build/html/api/alltypes/index.html
@@ -26,6 +26,13 @@ I/O), or a mock implementation suitable for unit test environments.
Primary purpose is to install Kotlin extensions for Jackson ObjectMapper so data classes work
+and to organise serializers / deserializers for java.time.* classes as necessary
This class sets up network message handlers for requests from peers for data keyed by hash. It is a piece of simple
@@ -185,6 +201,12 @@ glue that sits between the network layer and the database layer.
A wrapper around a digital signature. The covering field is a generic tag usable by whatever is interpreting the
@@ -254,6 +276,20 @@ attachments are saved to local storage automatically.
Provides access to various metrics and ways to notify monitoring services of things, for sysadmin purposes.
+This is not an interface because it is too lightweight to bother mocking out.
A network map contains lists of nodes on the network along with information about their identity keys, services
they provide and host names or IP addresses where they can be connected to. A reasonable architecture for the
@@ -436,6 +480,14 @@ loads important data off disk and starts listening for connections.
An interest rates service is an oracle that signs transactions which contain embedded assertions about an interest
+rate fix (e.g. LIBOR, EURIBOR ...).
This class implements the server side of the timestamping protocol, using the local clock. A future version might
@@ -533,6 +585,16 @@ For any given flow there is only one PSM, even if that protocol invokes subproto
This protocol queries the given oracle for an interest rate fix, and if it is within the given tolerance embeds the
+fix in the transaction and then proceeds to get the oracle to sign it. Although the call method combines the query
+and signing step, you can run the steps individually by constructing this object and then using the public methods
+for each step.
Primary purpose is to install Kotlin extensions for Jackson ObjectMapper so data classes work
+and to organise serializers / deserializers for java.time.* classes as necessary
Primary purpose is to install Kotlin extensions for Jackson ObjectMapper so data classes work
+and to organise serializers / deserializers for java.time.* classes as necessary
Primary purpose is to install Kotlin extensions for Jackson ObjectMapper so data classes work
+and to organise serializers / deserializers for java.time.* classes as necessary
Primary purpose is to install Kotlin extensions for Jackson ObjectMapper so data classes work
+and to organise serializers / deserializers for java.time.* classes as necessary
A network map contains lists of nodes on the network along with information about their identity keys, services
+they provide and host names or IP addresses where they can be connected to. A reasonable architecture for the
+network map service might be one like the Tor directory authorities, where several nodes linked by RAFT or Paxos
+elect a leader and that leader distributes signed documents describing the network layout. Those documents can
+then be cached by every node and thus a network map can be retrieved given only a single successful peer connection.
+
This interface assumes fast, synchronous access to an in-memory map.
+
+abstractval timestampingNodes: List<LegallyIdentifiableNode>
+
+
+
+
diff --git a/docs/build/html/api/core.messaging/index.html b/docs/build/html/api/core.messaging/index.html
index 4133d052eb..f0c7ccc18e 100644
--- a/docs/build/html/api/core.messaging/index.html
+++ b/docs/build/html/api/core.messaging/index.html
@@ -72,15 +72,15 @@ may let you cast the returned future to an object that lets you get status info.
A network map contains lists of nodes on the network along with information about their identity keys, services
+interface NetworkMapService
A network map contains lists of nodes on the network along with information about their identity keys, services
they provide and host names or IP addresses where they can be connected to. A reasonable architecture for the
network map service might be one like the Tor directory authorities, where several nodes linked by RAFT or Paxos
elect a leader and that leader distributes signed documents describing the network layout. Those documents can
diff --git a/docs/build/html/api/core.node.services/-attachment-storage/index.html b/docs/build/html/api/core.node.services/-attachment-storage/index.html
index 026b293d73..fb413bd55f 100644
--- a/docs/build/html/api/core.node.services/-attachment-storage/index.html
+++ b/docs/build/html/api/core.node.services/-attachment-storage/index.html
@@ -42,7 +42,7 @@ on Attachment.
Provides access to various metrics and ways to notify monitoring services of things, for sysadmin purposes.
+This is not an interface because it is too lightweight to bother mocking out.
Provides access to various metrics and ways to notify monitoring services of things, for sysadmin purposes.
+This is not an interface because it is too lightweight to bother mocking out.
Provides access to various metrics and ways to notify monitoring services of things, for sysadmin purposes.
+This is not an interface because it is too lightweight to bother mocking out.
If true, newly inserted attachments will be unzipped to a subdirectory of the storePath. This is intended for
@@ -54,6 +61,19 @@ will not have any effect).