Docs: fix a few typos and rewrap a few code samples in the state machines article.

This commit is contained in:
Mike Hearn 2015-12-15 15:52:07 +01:00
parent bf647f6c15
commit e3cfe0ae49
4 changed files with 80 additions and 59 deletions

View File

@ -57,7 +57,7 @@ Theory
A *continuation* is a suspended stack frame stored in a regular object that can be passed around, serialised,
unserialised and resumed from where it was suspended. This may sound abstract but don't worry, the examples below
will make it clearer. The JVM does not natively support continuations, so we implement them using a a library called
will make it clearer. The JVM does not natively support continuations, so we implement them using a library called
JavaFlow which works through behind-the-scenes bytecode rewriting. You don't have to know how this works to benefit
from it, however.
@ -153,9 +153,9 @@ Let's unpack what this code does:
to be unguessable. 63 bits is good enough.
Alright, so using this protocol shouldn't be too hard: in the simplest case we can just pass in the details of the trade
to either runBuyer or runSeller, depending on who we are, and then call ``.get()`` on the resulting future to block the
calling thread until the protocol has finished. Or we could register a callback on the returned future that will be
invoked when it's done, where we could e.g. update a user interface.
to either runBuyer or runSeller, depending on who we are, and then call ``.resultFuture.get()`` on resulting object to
block the calling thread until the protocol has finished. Or we could register a callback on the returned future that
will be invoked when it's done, where we could e.g. update a user interface.
The only tricky part is how to get one of these things. We need a ``StateMachineManager``. Where does that come from
and why do we need one?
@ -221,7 +221,8 @@ It could be as simple as a chat room or as complex as a 24/7 exchange.
.. sourcecode:: kotlin
// This object is serialised to the network and is the first protocol message the seller sends to the buyer.
// This object is serialised to the network and is the first protocol message
// the seller sends to the buyer.
class SellerTradeInfo(
val assetForSale: StateAndRef<OwnableState>,
val price: Amount,
@ -244,8 +245,8 @@ Next we add some code to the ``SellerImpl.call`` method:
// Make the first message we'll send to kick off the protocol.
val hello = SellerTradeInfo(args.assetToSell, args.price, args.myKeyPair.public, sessionID)
// Zero is a special session ID that is being listened to by the buyer (i.e. before a session is started).
val partialTX = sendAndReceive<SignedWireTransaction>(TRADE_TOPIC, args.buyerSessionID, sessionID, hello)
val partialTX = sendAndReceive<SignedWireTransaction>(TRADE_TOPIC, args.buyerSessionID,
sessionID, hello)
logger().trace { "Received partially signed transaction" }
That's pretty straightforward. We generate a session ID to identify what's happening on the seller side, fill out
@ -347,16 +348,18 @@ OK, let's do the same for the buyer side:
if (!args.typeToBuy.isInstance(tradeRequest.assetForSale.state))
throw AssetMismatchException(args.typeToBuy.name, assetTypeName)
// TODO: Either look up the stateref here in our local db, or accept a long chain of states and
// validate them to audit the other side and ensure it actually owns the state we are being offered!
// For now, just assume validity!
// TODO: Either look up the stateref here in our local db, or accept a long chain
// of states and validate them to audit the other side and ensure it actually owns
// the state we are being offered! For now, just assume validity!
// Generate the shared transaction that both sides will sign, using the data we have.
val ptx = PartialTransaction()
// Add input and output states for the movement of cash, by using the Cash contract to generate the states.
// Add input and output states for the movement of cash, by using the Cash contract
// to generate the states.
val wallet = serviceHub.walletService.currentWallet
val cashStates = wallet.statesOfType<Cash.State>()
val cashSigningPubKeys = Cash().craftSpend(ptx, tradeRequest.price, tradeRequest.sellerOwnerKey, cashStates)
val cashSigningPubKeys = Cash().craftSpend(ptx, tradeRequest.price,
tradeRequest.sellerOwnerKey, cashStates)
// Add inputs/outputs/a command for the movement of the asset.
ptx.addInputState(tradeRequest.assetForSale.ref)
// Just pick some new public key for now.
@ -378,14 +381,17 @@ OK, let's do the same for the buyer side:
logger().trace { "Sending partially signed transaction to seller" }
// TODO: Protect against the buyer terminating here and leaving us in the lurch without the final tx.
// TODO: Protect against a malicious buyer sending us back a different transaction to the one we built.
// TODO: Protect against the buyer terminating here and leaving us in the lurch without
// the final tx.
// TODO: Protect against a malicious buyer sending us back a different transaction to
// the one we built.
val fullySigned = sendAndReceive<TimestampedWireTransaction>(TRADE_TOPIC,
tradeRequest.sessionID, args.sessionID, stx)
logger().trace { "Got fully signed transaction, verifying ... "}
val ltx = fullySigned.verifyToLedgerTransaction(serviceHub.timestampingService, serviceHub.identityService)
val ltx = fullySigned.verifyToLedgerTransaction(serviceHub.timestampingService,
serviceHub.identityService)
logger().trace { "Fully signed transaction was valid. Trade complete! :-)" }
@ -396,7 +402,8 @@ OK, let's do the same for the buyer side:
This code is fairly straightforward. Here are some things to pay attention to:
1. We do some sanity checking on the received message to ensure we're being offered what we expected to be offered.
2. We create a cash spend in the normal way, by using ``Cash().craftSpend``.
2. We create a cash spend in the normal way, by using ``Cash().craftSpend``. See the contracts tutorial if this isn't
clear.
3. We access the *service hub* when we need it to access things that are transient and may change or be recreated
whilst a protocol is suspended, things like the wallet or the timestamping service. Remember that a protocol may
be suspended when it waits to receive a message across node or computer restarts, so objects representing a service
@ -411,6 +418,6 @@ the fact that it takes minimal resources and can survive node restarts.
If you do this then next time your protocol waits to receive an object, the system will try and serialise all your
local variables and end up trying to serialise, e.g. the timestamping service, which doesn't make any conceptual
sense. The ``serviceHub`` field is defined by the ``ProtocolStateMachine`` superclass and is marked transient so
this problem doesn't occur. It's also restored for you after a protocol state machine is restored after a node
this problem doesn't occur. It's also restored for you when a protocol state machine is restored after a node
restart.

View File

@ -188,7 +188,7 @@ construction of them that automatically handles many of the concerns outlined ab
<h2>Theory<a class="headerlink" href="#theory" title="Permalink to this headline"></a></h2>
<p>A <em>continuation</em> is a suspended stack frame stored in a regular object that can be passed around, serialised,
unserialised and resumed from where it was suspended. This may sound abstract but don&#8217;t worry, the examples below
will make it clearer. The JVM does not natively support continuations, so we implement them using a a library called
will make it clearer. The JVM does not natively support continuations, so we implement them using a library called
JavaFlow which works through behind-the-scenes bytecode rewriting. You don&#8217;t have to know how this works to benefit
from it, however.</p>
<p>We use continuations for the following reasons:</p>
@ -274,13 +274,13 @@ and returns it, with a <code class="docutils literal"><span class="pre">StateMac
</ul>
<div class="admonition note">
<p class="first admonition-title">Note</p>
<p class="last">Session IDs keep different traffic streams separated, so for security they must be large and random enough</p>
<p class="last">Session IDs keep different traffic streams separated, so for security they must be large and random enough
to be unguessable. 63 bits is good enough.</p>
</div>
<p>to be unguessable. 63 bits is good enough.</p>
<p>Alright, so using this protocol shouldn&#8217;t be too hard: in the simplest case we can just pass in the details of the trade
to either runBuyer or runSeller, depending on who we are, and then call <code class="docutils literal"><span class="pre">.get()</span></code> on the resulting future to block the
calling thread until the protocol has finished. Or we could register a callback on the returned future that will be
invoked when it&#8217;s done, where we could e.g. update a user interface.</p>
to either runBuyer or runSeller, depending on who we are, and then call <code class="docutils literal"><span class="pre">.resultFuture.get()</span></code> on resulting object to
block the calling thread until the protocol has finished. Or we could register a callback on the returned future that
will be invoked when it&#8217;s done, where we could e.g. update a user interface.</p>
<p>The only tricky part is how to get one of these things. We need a <code class="docutils literal"><span class="pre">StateMachineManager</span></code>. Where does that come from
and why do we need one?</p>
</div>
@ -335,7 +335,8 @@ each side will use.</p>
we want to trade. Remember: this data comes from whatever system was used to find the trading partner to begin with.
It could be as simple as a chat room or as complex as a 24/7 exchange.</p>
<div class="codeset container">
<div class="highlight-kotlin"><div class="highlight"><pre><span class="c1">// This object is serialised to the network and is the first protocol message the seller sends to the buyer.</span>
<div class="highlight-kotlin"><div class="highlight"><pre><span class="c1">// This object is serialised to the network and is the first protocol message</span>
<span class="c1">// the seller sends to the buyer.</span>
<span class="k">class</span> <span class="nc">SellerTradeInfo</span><span class="p">(</span>
<span class="k">val</span> <span class="py">assetForSale</span><span class="p">:</span> <span class="n">StateAndRef</span><span class="p">&lt;</span><span class="n">OwnableState</span><span class="p">&gt;,</span>
<span class="k">val</span> <span class="py">price</span><span class="p">:</span> <span class="n">Amount</span><span class="p">,</span>
@ -355,8 +356,8 @@ trade&#8217;s messages, and a pointer to where the asset that is being sold can
<span class="c1">// Make the first message we&#39;ll send to kick off the protocol.</span>
<span class="k">val</span> <span class="py">hello</span> <span class="p">=</span> <span class="n">SellerTradeInfo</span><span class="p">(</span><span class="n">args</span><span class="p">.</span><span class="n">assetToSell</span><span class="p">,</span> <span class="n">args</span><span class="p">.</span><span class="n">price</span><span class="p">,</span> <span class="n">args</span><span class="p">.</span><span class="n">myKeyPair</span><span class="p">.</span><span class="k">public</span><span class="p">,</span> <span class="n">sessionID</span><span class="p">)</span>
<span class="c1">// Zero is a special session ID that is being listened to by the buyer (i.e. before a session is started).</span>
<span class="k">val</span> <span class="py">partialTX</span> <span class="p">=</span> <span class="n">sendAndReceive</span><span class="p">&lt;</span><span class="n">SignedWireTransaction</span><span class="p">&gt;(</span><span class="n">TRADE_TOPIC</span><span class="p">,</span> <span class="n">args</span><span class="p">.</span><span class="n">buyerSessionID</span><span class="p">,</span> <span class="n">sessionID</span><span class="p">,</span> <span class="n">hello</span><span class="p">)</span>
<span class="k">val</span> <span class="py">partialTX</span> <span class="p">=</span> <span class="n">sendAndReceive</span><span class="p">&lt;</span><span class="n">SignedWireTransaction</span><span class="p">&gt;(</span><span class="n">TRADE_TOPIC</span><span class="p">,</span> <span class="n">args</span><span class="p">.</span><span class="n">buyerSessionID</span><span class="p">,</span>
<span class="n">sessionID</span><span class="p">,</span> <span class="n">hello</span><span class="p">)</span>
<span class="n">logger</span><span class="p">().</span><span class="n">trace</span> <span class="p">{</span> <span class="s">&quot;Received partially signed transaction&quot;</span> <span class="p">}</span>
</pre></div>
</div>
@ -453,16 +454,18 @@ forms.</p>
if (!args.typeToBuy.isInstance(tradeRequest.assetForSale.state))
throw AssetMismatchException(args.typeToBuy.name, assetTypeName)
// TODO: Either look up the stateref here in our local db, or accept a long chain of states and
// validate them to audit the other side and ensure it actually owns the state we are being offered!
// For now, just assume validity!
// TODO: Either look up the stateref here in our local db, or accept a long chain
// of states and validate them to audit the other side and ensure it actually owns
// the state we are being offered! For now, just assume validity!
// Generate the shared transaction that both sides will sign, using the data we have.
val ptx = PartialTransaction()
// Add input and output states for the movement of cash, by using the Cash contract to generate the states.
// Add input and output states for the movement of cash, by using the Cash contract
// to generate the states.
val wallet = serviceHub.walletService.currentWallet
val cashStates = wallet.statesOfType&lt;Cash.State&gt;()
val cashSigningPubKeys = Cash().craftSpend(ptx, tradeRequest.price, tradeRequest.sellerOwnerKey, cashStates)
val cashSigningPubKeys = Cash().craftSpend(ptx, tradeRequest.price,
tradeRequest.sellerOwnerKey, cashStates)
// Add inputs/outputs/a command for the movement of the asset.
ptx.addInputState(tradeRequest.assetForSale.ref)
// Just pick some new public key for now.
@ -484,14 +487,17 @@ forms.</p>
logger().trace { &quot;Sending partially signed transaction to seller&quot; }
// TODO: Protect against the buyer terminating here and leaving us in the lurch without the final tx.
// TODO: Protect against a malicious buyer sending us back a different transaction to the one we built.
// TODO: Protect against the buyer terminating here and leaving us in the lurch without
// the final tx.
// TODO: Protect against a malicious buyer sending us back a different transaction to
// the one we built.
val fullySigned = sendAndReceive&lt;TimestampedWireTransaction&gt;(TRADE_TOPIC,
tradeRequest.sessionID, args.sessionID, stx)
logger().trace { &quot;Got fully signed transaction, verifying ... &quot;}
val ltx = fullySigned.verifyToLedgerTransaction(serviceHub.timestampingService, serviceHub.identityService)
val ltx = fullySigned.verifyToLedgerTransaction(serviceHub.timestampingService,
serviceHub.identityService)
logger().trace { &quot;Fully signed transaction was valid. Trade complete! :-)&quot; }
@ -504,7 +510,8 @@ forms.</p>
<p>This code is fairly straightforward. Here are some things to pay attention to:</p>
<ol class="arabic simple">
<li>We do some sanity checking on the received message to ensure we&#8217;re being offered what we expected to be offered.</li>
<li>We create a cash spend in the normal way, by using <code class="docutils literal"><span class="pre">Cash().craftSpend</span></code>.</li>
<li>We create a cash spend in the normal way, by using <code class="docutils literal"><span class="pre">Cash().craftSpend</span></code>. See the contracts tutorial if this isn&#8217;t
clear.</li>
<li>We access the <em>service hub</em> when we need it to access things that are transient and may change or be recreated
whilst a protocol is suspended, things like the wallet or the timestamping service. Remember that a protocol may
be suspended when it waits to receive a message across node or computer restarts, so objects representing a service
@ -520,7 +527,7 @@ the fact that it takes minimal resources and can survive node restarts.</p>
If you do this then next time your protocol waits to receive an object, the system will try and serialise all your
local variables and end up trying to serialise, e.g. the timestamping service, which doesn&#8217;t make any conceptual
sense. The <code class="docutils literal"><span class="pre">serviceHub</span></code> field is defined by the <code class="docutils literal"><span class="pre">ProtocolStateMachine</span></code> superclass and is marked transient so
this problem doesn&#8217;t occur. It&#8217;s also restored for you after a protocol state machine is restored after a node
this problem doesn&#8217;t occur. It&#8217;s also restored for you when a protocol state machine is restored after a node
restart.</p>
</div>
</div>

File diff suppressed because one or more lines are too long

View File

@ -57,7 +57,7 @@ Theory
A *continuation* is a suspended stack frame stored in a regular object that can be passed around, serialised,
unserialised and resumed from where it was suspended. This may sound abstract but don't worry, the examples below
will make it clearer. The JVM does not natively support continuations, so we implement them using a a library called
will make it clearer. The JVM does not natively support continuations, so we implement them using a library called
JavaFlow which works through behind-the-scenes bytecode rewriting. You don't have to know how this works to benefit
from it, however.
@ -153,9 +153,9 @@ Let's unpack what this code does:
to be unguessable. 63 bits is good enough.
Alright, so using this protocol shouldn't be too hard: in the simplest case we can just pass in the details of the trade
to either runBuyer or runSeller, depending on who we are, and then call ``.get()`` on the resulting future to block the
calling thread until the protocol has finished. Or we could register a callback on the returned future that will be
invoked when it's done, where we could e.g. update a user interface.
to either runBuyer or runSeller, depending on who we are, and then call ``.resultFuture.get()`` on resulting object to
block the calling thread until the protocol has finished. Or we could register a callback on the returned future that
will be invoked when it's done, where we could e.g. update a user interface.
The only tricky part is how to get one of these things. We need a ``StateMachineManager``. Where does that come from
and why do we need one?
@ -221,7 +221,8 @@ It could be as simple as a chat room or as complex as a 24/7 exchange.
.. sourcecode:: kotlin
// This object is serialised to the network and is the first protocol message the seller sends to the buyer.
// This object is serialised to the network and is the first protocol message
// the seller sends to the buyer.
class SellerTradeInfo(
val assetForSale: StateAndRef<OwnableState>,
val price: Amount,
@ -244,8 +245,8 @@ Next we add some code to the ``SellerImpl.call`` method:
// Make the first message we'll send to kick off the protocol.
val hello = SellerTradeInfo(args.assetToSell, args.price, args.myKeyPair.public, sessionID)
// Zero is a special session ID that is being listened to by the buyer (i.e. before a session is started).
val partialTX = sendAndReceive<SignedWireTransaction>(TRADE_TOPIC, args.buyerSessionID, sessionID, hello)
val partialTX = sendAndReceive<SignedWireTransaction>(TRADE_TOPIC, args.buyerSessionID,
sessionID, hello)
logger().trace { "Received partially signed transaction" }
That's pretty straightforward. We generate a session ID to identify what's happening on the seller side, fill out
@ -347,16 +348,18 @@ OK, let's do the same for the buyer side:
if (!args.typeToBuy.isInstance(tradeRequest.assetForSale.state))
throw AssetMismatchException(args.typeToBuy.name, assetTypeName)
// TODO: Either look up the stateref here in our local db, or accept a long chain of states and
// validate them to audit the other side and ensure it actually owns the state we are being offered!
// For now, just assume validity!
// TODO: Either look up the stateref here in our local db, or accept a long chain
// of states and validate them to audit the other side and ensure it actually owns
// the state we are being offered! For now, just assume validity!
// Generate the shared transaction that both sides will sign, using the data we have.
val ptx = PartialTransaction()
// Add input and output states for the movement of cash, by using the Cash contract to generate the states.
// Add input and output states for the movement of cash, by using the Cash contract
// to generate the states.
val wallet = serviceHub.walletService.currentWallet
val cashStates = wallet.statesOfType<Cash.State>()
val cashSigningPubKeys = Cash().craftSpend(ptx, tradeRequest.price, tradeRequest.sellerOwnerKey, cashStates)
val cashSigningPubKeys = Cash().craftSpend(ptx, tradeRequest.price,
tradeRequest.sellerOwnerKey, cashStates)
// Add inputs/outputs/a command for the movement of the asset.
ptx.addInputState(tradeRequest.assetForSale.ref)
// Just pick some new public key for now.
@ -378,14 +381,17 @@ OK, let's do the same for the buyer side:
logger().trace { "Sending partially signed transaction to seller" }
// TODO: Protect against the buyer terminating here and leaving us in the lurch without the final tx.
// TODO: Protect against a malicious buyer sending us back a different transaction to the one we built.
// TODO: Protect against the buyer terminating here and leaving us in the lurch without
// the final tx.
// TODO: Protect against a malicious buyer sending us back a different transaction to
// the one we built.
val fullySigned = sendAndReceive<TimestampedWireTransaction>(TRADE_TOPIC,
tradeRequest.sessionID, args.sessionID, stx)
logger().trace { "Got fully signed transaction, verifying ... "}
val ltx = fullySigned.verifyToLedgerTransaction(serviceHub.timestampingService, serviceHub.identityService)
val ltx = fullySigned.verifyToLedgerTransaction(serviceHub.timestampingService,
serviceHub.identityService)
logger().trace { "Fully signed transaction was valid. Trade complete! :-)" }
@ -396,7 +402,8 @@ OK, let's do the same for the buyer side:
This code is fairly straightforward. Here are some things to pay attention to:
1. We do some sanity checking on the received message to ensure we're being offered what we expected to be offered.
2. We create a cash spend in the normal way, by using ``Cash().craftSpend``.
2. We create a cash spend in the normal way, by using ``Cash().craftSpend``. See the contracts tutorial if this isn't
clear.
3. We access the *service hub* when we need it to access things that are transient and may change or be recreated
whilst a protocol is suspended, things like the wallet or the timestamping service. Remember that a protocol may
be suspended when it waits to receive a message across node or computer restarts, so objects representing a service
@ -411,6 +418,6 @@ the fact that it takes minimal resources and can survive node restarts.
If you do this then next time your protocol waits to receive an object, the system will try and serialise all your
local variables and end up trying to serialise, e.g. the timestamping service, which doesn't make any conceptual
sense. The ``serviceHub`` field is defined by the ``ProtocolStateMachine`` superclass and is marked transient so
this problem doesn't occur. It's also restored for you after a protocol state machine is restored after a node
this problem doesn't occur. It's also restored for you when a protocol state machine is restored after a node
restart.