Merged in andrius-notary-doc (pull request #555)

This commit is contained in:
Andrius Dagys 2016-11-28 15:16:59 +00:00
commit a26ca37168
6 changed files with 176 additions and 57 deletions

View File

@ -96,7 +96,7 @@ Our platform is flexible and we currently support both validating and non-valida
.. note:: In the non-validating model the "denial of state" attack is partially alleviated by requiring the calling
party to authenticate and storing its identity for the request. The conflict information returned by the notary
specifies the consuming transaction ID along with the identity of the party that had requested the commit. If the
conflicting transaction is valid, the current one gets aborted; if not a dispute can be raised and the input states
conflicting transaction is valid, the current one gets aborted; if not - a dispute can be raised and the input states
of the conflicting invalid transaction are "un-committed" (to be covered by legal process).
.. note:: At present all notaries can see the entire contents of a transaction, but we have a separate piece of work to
@ -124,7 +124,7 @@ time observed in step 1.
For this reason, times in transactions are specified as time *windows*, not absolute times. Time windows can be
open-ended, i.e. specify only one of "before" and "after" or they can be fully bounded. If a time window needs to
be converted to an absolute time for e.g. display purposes, there is a utility method on ``Timestamp`` to
calculate the mid point - but in a distributed system there can never be "true time", only an approximation of it.
calculate the mid point -- but in a distributed system there can never be "true time", only an approximation of it.
In this way 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. Here
@ -138,58 +138,4 @@ By creating a range that can be either closed or open at one end, we allow all o
* This transaction occurred at some point roughly around the given time (e.g. on a specific day)
.. note:: It is assumed that the time feed for a notary is 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.
Running a notary service
------------------------
At present we have two basic implementations that store committed input states in memory:
- ``SimpleNotaryService`` -- commits the provided transaction without any validation
- ``ValidatingNotaryService`` -- retrieves and validates the whole transaction history (including the given transaction) before committing
Obtaining a signature
---------------------
Once a transaction is built and ready to be finalised, normally you would call ``FinalityFlow`` passing in a
``SignedTransaction`` (including signatures from the participants) and a list of participants to notify. This requests a
notary signature if needed, and then sends a copy of the notarised transaction to all participants for them to store.
``FinalityFlow`` delegates to ``NotaryFlow.Client`` followed by ``BroadcastTransactionFlow`` to do the
actual work of notarising and broadcasting the transaction. For example:
.. sourcecode:: kotlin
fun finaliseTransaction(serviceHub: ServiceHubInternal, ptx: TransactionBuilder, participants: Set<Party>)
: ListenableFuture<Unit> {
// We conclusively cannot have all the signatures, as the notary has not signed yet
val tx = ptx.toSignedTransaction(checkSufficientSignatures = false)
// The empty set would be the trigger events, which are not used here
val flow = FinalityFlow(tx, emptySet(), participants)
return serviceHub.startFlow("flow.finalisation", flow)
}
To manually obtain a signature from a notary you can call ``NotaryFlow.Client`` directly. The flow will work out
which notary needs to be called based on the input states and the timestamp command. For example, the following snippet
can be used when writing a custom flow:
.. sourcecode:: kotlin
fun getNotarySignature(wtx: WireTransaction): DigitalSignature.LegallyIdentifiable {
return subFlow(NotaryFlow.Client(wtx))
}
On conflict the ``NotaryFlow`` with throw a ``NotaryException`` containing the conflict details:
.. sourcecode:: kotlin
/** Specifies the consuming transaction for the conflicting input state */
data class Conflict(val stateHistory: Map<StateRef, ConsumingTx>)
/**
* Specifies the transaction id, the position of the consumed state in the inputs, and
* the caller identity requesting the commit
*/
data class ConsumingTx(val id: SecureHash, val inputIndex: Int, val requestingParty: Party)
Conflict handling and resolution is currently the responsibility of the flow author.
clocks at the US Naval Observatory. This time feed is extremely accurate and available globally for free.

View File

@ -71,6 +71,8 @@ Read on to learn:
tutorial-clientrpc-api
flow-state-machines
flow-testing
running-a-notary
using-a-notary
oracles
tutorial-attachments
event-scheduling

View File

@ -26,6 +26,8 @@ messaging service via the ``ServiceHub`` object that is provided to your app. En
identified at the lowest level using ``SingleMessageRecipient`` which may be e.g. an IP address, or in future
versions perhaps a routing path through the network.
.. _network-map-service:
Network Map Service
-------------------

View File

@ -0,0 +1,29 @@
Running a notary service
------------------------
At present we have several prototype notary implementations:
1. ``SimpleNotaryService`` (single node) -- commits the provided transaction input states without any validation.
2. ``ValidatingNotaryService`` (single node) -- retrieves and validates the whole transaction history
(including the given transaction) before committing.
3. ``RaftValidatingNotaryService`` (distributed) -- functionally equivalent to ``ValidatingNotaryService``, but stores
the committed states in a distributed collection replicated and persisted in a Raft cluster. For the consensus layer
we are using the `Copycat <http://atomix.io/copycat/>`_ framework.
To have a node run a notary service, you need to set appropriate configuration values before starting it
(see :doc:`corda-configuration-files` for reference).
For ``SimpleNotaryService``, simply add the following service id to the list of advertised services:
.. parsed-literal::
extraAdvertisedServiceIds: "net.corda.notary.simple"
For ``ValidatingNotaryService`` it is:
.. parsed-literal::
extraAdvertisedServiceIds: "net.corda.notary.validating"
Setting up a ``RaftValidatingNotaryService`` is currently slightly more involved and is not recommended for prototyping
purposes. There is work in progress to simplify it. To see it in action, however, you can try out the :ref:`notary-demo`.

View File

@ -117,6 +117,8 @@ To run the demo run:
Now open http://localhost:10005/web/simmvaluationdemo and http://localhost:10007/web/simmvaluationdemo to view the two nodes that this
will have started respectively. You can now use the demo by creating trades and agreeing the valuations.
.. _notary-demo:
Distributed Notary demo
-----------------------

View File

@ -0,0 +1,138 @@
Using a notary service
----------------------
This tutorial describes how to assign a notary to a newly issued state, and how to get a transaction notarised by
obtaining a signature of the required notary. It assumes some familiarity with *flows* and how to write them, as described
in :doc:`flow-state-machines`.
Assigning a notary
==================
The first step is to choose a notary and obtain its identity. Identities of all notaries on the network are kept by
the :ref:`network-map-service`. The network map cache exposes two methods for obtaining a notary:
.. sourcecode:: kotlin
/**
* Gets a notary identity by the given name.
*/
fun getNotary(name: String): Party?
/**
* Returns a notary identity advertised by any of the nodes on the network (chosen at random)
*
* @param type Limits the result to notaries of the specified type (optional)
*/
fun getAnyNotary(type: ServiceType? = null): Party?
Currently notaries can only be differentiated by name and type, but in the future the network map service will be
able to provide more metadata, such as location or legal identities of the nodes operating it.
Now, let's say we want to issue an asset and assign it to a notary named "Notary A".
The first step is to obtain the notary identity -- ``Party``:
.. sourcecode:: kotlin
val ourNotary: Party = serviceHub.networkMapCache.getNotary("Central Bank Notary")
Then we initialise the transaction builder:
.. sourcecode:: kotlin
val builder: TransactionBuilder = TransactionType.General.Builder(notary = ourNotary)
For any output state we add to this transaction builder, ``ourNotary`` will be assigned as its notary.
Next we create a state object and assign ourselves as the owner. For this example we'll use a
``DummyContract.State``, which is a simple state that just maintains an integer and can change ownership.
.. sourcecode:: kotlin
val myIdentity = serviceHub.myInfo.legalIdentity
val state = DummyContract.SingleOwnerState(magicNumber = 42, owner = myIdentity.owningKey)
Then we add the state as the transaction output along with the relevant command. The state will automatically be assigned
to our previously specified "Notary A".
.. sourcecode:: kotlin
builder.addOutputState(state)
val createCommand = DummyContract.Commands.Create()
builder.addCommand(Command(createCommand, myIdentity))
We then sign the transaction, build and record it to our transaction storage:
.. sourcecode:: kotlin
val mySigningKey: KeyPair = serviceHub.legalIdentityKey
builder.signWith(mySigningKey)
val issueTransaction = builder.toSignedTransaction()
serviceHub.recordTransactions(issueTransaction)
The transaction is recorded and we now have a state (asset) in possession that we can transfer to someone else. Note
that the issuing transaction does not need to be notarised, as it doesn't consume any input states.
Notarising a transaction
========================
Following our example for the previous section, let's say we now want to transfer our issued state to Alice.
First we obtain a reference to the state, which will be the input to our "move" transaction:
.. sourcecode:: kotlin
val stateRef = StateRef(txhash = issueTransaction.id, index = 0)
Then we create a new state -- a copy of our state but with the owner set to Alice. This is a bit more involved so
we just use a helper that handles it for us. We also assume that we already have the ``Party`` for Alice, ``aliceParty``.
.. sourcecode:: kotlin
val inputState = StateAndRef(sate, stateRef)
val moveTransactionBuilder = DummyContract.move(inputState, newOwner = aliceParty.owningKey)
The ``DummyContract.move()`` method will a new transaction builder with our old state as the input, a new state
with Alice as the owner, and a relevant contract command for "move".
Again we sign the transaction, and build it:
.. sourcecode:: kotlin
moveTransactionBuilder.signWith(mySigningKey)
// We build it without checking if all signatures are present, because we know that the notary signature is missing
val moveTransaction = builder.toSignedTransaction(checkSufficientSignatures = false)
Next we need to obtain a signature from the notary for the transaction to be valid. Prior to signing, the notary will
commit our old (input) state so it cannot be used again.
To manually obtain a signature from a notary we can run the ``NotaryFlow.Client`` flow. The flow will work out
which notary needs to be called based on the input states (and the timestamp command, if it's present).
.. sourcecode:: kotlin
// The subFlow() helper is available within the context of a Flow
val notarySignature: DigitalSignature = subFlow(NotaryFlow.Client(moveTransaction))
.. note:: If our input state has already been consumed in another transaction, then ``NotaryFlow`` with throw a ``NotaryException``
containing the conflict details:
.. sourcecode:: kotlin
/** Specifies the consuming transaction for the conflicting input state */
data class Conflict(val stateHistory: Map<StateRef, ConsumingTx>)
/**
* Specifies the transaction id, the position of the consumed state in the inputs, and
* the caller identity requesting the commit
*/
data class ConsumingTx(val id: SecureHash, val inputIndex: Int, val requestingParty: Party)
Conflict handling and resolution is currently the responsibility of the flow author.
Note that instead of calling the notary directly, we would normally call ``FinalityFlow`` passing in the ``SignedTransaction``
(including signatures from the participants) and a list of participants to notify. The flow will request a notary signature
if needed, record the notarised transaction, and then send a copy of the transaction to all participants for them to store.
``FinalityFlow`` delegates to ``NotaryFlow.Client`` followed by ``BroadcastTransactionFlow`` to do the
actual work of notarising and broadcasting the transaction. For example:
.. sourcecode:: kotlin
subFlow(FinalityFlow(moveTransaction, setOf(aliceParty))