From 2f37024af7da0c9a35c637736a5c4da6132b195c Mon Sep 17 00:00:00 2001 From: Andrius Dagys Date: Fri, 25 Nov 2016 15:11:19 +0000 Subject: [PATCH] Added tutorials on how to use & run a notary. --- docs/source/consensus.rst | 60 +------------ docs/source/index.rst | 2 + docs/source/messaging.rst | 2 + docs/source/running-a-notary.rst | 29 +++++++ docs/source/running-the-demos.rst | 2 + docs/source/using-a-notary.rst | 138 ++++++++++++++++++++++++++++++ 6 files changed, 176 insertions(+), 57 deletions(-) create mode 100644 docs/source/running-a-notary.rst create mode 100644 docs/source/using-a-notary.rst diff --git a/docs/source/consensus.rst b/docs/source/consensus.rst index 7dd620ab83..92c7accc0f 100644 --- a/docs/source/consensus.rst +++ b/docs/source/consensus.rst @@ -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) - : ListenableFuture { - // 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) - - /** - * 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. \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index 13cbaea05a..4469b53b4d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -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 diff --git a/docs/source/messaging.rst b/docs/source/messaging.rst index 47b759e1da..91d7856d8b 100644 --- a/docs/source/messaging.rst +++ b/docs/source/messaging.rst @@ -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 ------------------- diff --git a/docs/source/running-a-notary.rst b/docs/source/running-a-notary.rst new file mode 100644 index 0000000000..e8d7bcbff2 --- /dev/null +++ b/docs/source/running-a-notary.rst @@ -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 `_ 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`. \ No newline at end of file diff --git a/docs/source/running-the-demos.rst b/docs/source/running-the-demos.rst index b7513c3d37..fe1fb1bdf7 100644 --- a/docs/source/running-the-demos.rst +++ b/docs/source/running-the-demos.rst @@ -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 ----------------------- diff --git a/docs/source/using-a-notary.rst b/docs/source/using-a-notary.rst new file mode 100644 index 0000000000..22dda98f50 --- /dev/null +++ b/docs/source/using-a-notary.rst @@ -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) + + /** + * 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)) \ No newline at end of file