mirror of
https://github.com/corda/corda.git
synced 2025-01-20 03:36:29 +00:00
Merged in andrius-notary-doc (pull request #555)
This commit is contained in:
commit
a26ca37168
@ -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.
|
@ -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
|
||||
|
@ -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
|
||||
-------------------
|
||||
|
||||
|
29
docs/source/running-a-notary.rst
Normal file
29
docs/source/running-a-notary.rst
Normal 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`.
|
@ -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
|
||||
-----------------------
|
||||
|
||||
|
138
docs/source/using-a-notary.rst
Normal file
138
docs/source/using-a-notary.rst
Normal 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))
|
Loading…
Reference in New Issue
Block a user