Update docs for new simpler protocol framework API.

This commit is contained in:
Mike Hearn 2016-01-13 13:54:10 +01:00
parent 3d051ced3a
commit d9c6df51f4
6 changed files with 525 additions and 535 deletions

View File

@ -43,7 +43,7 @@ Actor frameworks can solve some of the above but they are often tightly bound to
we would like to keep a clean separation. Additionally, they are typically not type safe, and don't make persistence or
writing sequential code much easier.
To put these problems in perspective the *payment channel protocol* in the bitcoinj library, which allows bitcoins to
To put these problems in perspective, the *payment channel protocol* in the bitcoinj library, which allows bitcoins to
be temporarily moved off-chain and traded at high speed between two parties in private, consists of about 7000 lines of
Java and took over a month of full time work to develop. Most of that code is concerned with the details of persistence,
message passing, lifecycle management, error handling and callback management. Because the business logic is quite
@ -56,10 +56,10 @@ 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 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.
unserialised and resumed from where it was suspended. This concept is sometimes referred to as "fibers". 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 library called Quasar which works through behind-the-scenes
bytecode rewriting. You don't have to know how this works to benefit from it, however.
We use continuations for the following reasons:
@ -98,73 +98,135 @@ represents an atomic asset swap.
Note that it's the *seller* who initiates contact with the buyer, not vice-versa as you might imagine.
We start by defining an abstract base class to encapsulate the protocol. This is what code that invokes the protocol
will see:
We start by defining a wrapper that namespaces the protocol code, two functions to start either the buy or sell side
of the protocol, and two classes that will contain the protocol definition. We also pick what data will be used by
each side.
.. container:: codeset
.. sourcecode:: kotlin
abstract class TwoPartyTradeProtocol {
class SellerInitialArgs(
object TwoPartyTradeProtocol {
val TRADE_TOPIC = "platform.trade"
fun runSeller(smm: StateMachineManager, timestampingAuthority: LegallyIdentifiableNode,
otherSide: SingleMessageRecipient, assetToSell: StateAndRef<OwnableState>, price: Amount,
myKeyPair: KeyPair, buyerSessionID: Long): ListenableFuture<Pair<WireTransaction, LedgerTransaction>> {
val seller = Seller(otherSide, timestampingAuthority, assetToSell, price, myKeyPair, buyerSessionID)
smm.add("$TRADE_TOPIC.seller", seller)
return seller.resultFuture
}
fun runBuyer(smm: StateMachineManager, timestampingAuthority: LegallyIdentifiableNode,
otherSide: SingleMessageRecipient, acceptablePrice: Amount, typeToBuy: Class<out OwnableState>,
sessionID: Long): ListenableFuture<Pair<WireTransaction, LedgerTransaction>> {
val buyer = Buyer(otherSide, timestampingAuthority.identity, acceptablePrice, typeToBuy, sessionID)
smm.add("$TRADE_TOPIC.buyer", buyer)
return buyer.resultFuture
}
class Seller(val otherSide: SingleMessageRecipient,
val timestampingAuthority: LegallyIdentifiableNode,
val assetToSell: StateAndRef<OwnableState>,
val price: Amount,
val myKeyPair: KeyPair,
val buyerSessionID: Long
)
val buyerSessionID: Long) : ProtocolStateMachine<Pair<WireTransaction, LedgerTransaction>>() {
@Suspendable
override fun call(): Pair<WireTransaction, LedgerTransaction> {
TODO()
}
}
abstract fun runSeller(otherSide: SingleMessageRecipient, args: SellerInitialArgs): Seller
class BuyerInitialArgs(
val acceptablePrice: Amount,
val typeToBuy: Class<out OwnableState>,
// This object is serialised to the network and is the first protocol message the seller sends to the buyer.
private class SellerTradeInfo(
val assetForSale: StateAndRef<OwnableState>,
val price: Amount,
val sellerOwnerKey: PublicKey,
val sessionID: Long
)
abstract fun runBuyer(otherSide: SingleMessageRecipient, args: BuyerInitialArgs): Buyer
abstract class Buyer : ProtocolStateMachine<BuyerInitialArgs, Pair<TimestampedWireTransaction, LedgerTransaction>>()
abstract class Seller : ProtocolStateMachine<SellerInitialArgs, Pair<TimestampedWireTransaction, LedgerTransaction>>()
private class UnacceptablePriceException(val givenPrice: Amount) : Exception()
private class AssetMismatchException(val expectedTypeName: String, val typeName: String) : Exception() {
override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName"
}
companion object {
@JvmStatic fun create(smm: StateMachineManager): TwoPartyTradeProtocol {
return TwoPartyTradeProtocolImpl(smm)
// The buyer's side of the protocol. See note above Seller to learn about the caveats here.
class Buyer(val otherSide: SingleMessageRecipient,
val timestampingAuthority: Party,
val acceptablePrice: Amount,
val typeToBuy: Class<out OwnableState>,
val sessionID: Long) : ProtocolStateMachine<Pair<WireTransaction, LedgerTransaction>>() {
@Suspendable
override fun call(): Pair<WireTransaction, LedgerTransaction> {
TODO()
}
}
}
Let's unpack what this code does:
- It defines a several classes nested inside the main ``TwoPartyTradeProtocol`` class, and a couple of methods, one to
run the buyer side of the protocol and one to run the seller side.
- Two of the classes are simply wrappers for parameters to the trade; things like what is being sold, what the price
of the asset is, how much the buyer is willing to pay and so on. The ``myKeyPair`` field is simply the public key
that the seller wishes the buyer to send the cash to. The session ID field is sent from buyer to seller when the
trade is being set up and is used to keep messages separated on the network, and stop malicious entities trying to
interfere with the message stream.
- The other two classes define empty abstract classes called ``Buyer`` and ``Seller``. These inherit from a class
called ``ProtocolStateMachine`` and provide two type parameters: the arguments class we just defined for each side
and the type of the object that the protocol finally produces (this doesn't have to be identical for each side, even
though in this case it is).
- Finally it simply defines a static method that creates an instance of an object that inherits from this base class
and returns it, with a ``StateMachineManager`` as an instance. The Impl class will be defined below.
- It defines a several classes nested inside the main ``TwoPartyTradeProtocol`` singleton, and a couple of methods, one
to run the buyer side of the protocol and one to run the seller side.
- It defines the "trade topic", which is just a string that namespaces this protocol. The prefix "platform." is reserved
by the DLG, but you can define your own protocols using standard Java-style reverse DNS notation.
- The ``runBuyer`` and ``runSeller`` methods take a number of parameters that specialise the protocol for this run,
use them to construct a ``Buyer`` or ``Seller`` object respectively, and then add the new instances to the
``StateMachineManager``. The purpose of this class is described below. The ``smm.add`` method takes a logger name as
the first parameter, this is just a standard JDK logging identifier string, and the instance to add.
Going through the data needed to become a seller, we have:
- ``timestampingAuthority: LegallyIdentifiableNode`` - a reference to a node on the P2P network that acts as a trusted
timestamper. The use of timestamping is described in :doc:`data-model`.
- ``otherSide: SingleMessageRecipient`` - the network address of the node with which you are trading.
- ``assetToSell: StateAndRef<OwnableState>`` - a pointer to the ledger entry that represents the thing being sold.
- ``price: Amount`` - the agreed on price that the asset is being sold for.
- ``myKeyPair: KeyPair`` - the key pair that controls the asset being sold. It will be used to sign the transaction.
- ``buyerSessionID: Long`` - a unique number that identifies this trade to the buyer. It is expected that the buyer
knows that the trade is going to take place and has sent you such a number already. (This field may go away in a future
iteration of the framework)
.. note:: 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.
And for the buyer:
- ``acceptablePrice: Amount`` - the price that was agreed upon out of band. If the seller specifies a price less than
or equal to this, then the trade will go ahead.
- ``typeToBuy: Class<out OwnableState>`` - the type of state that is being purchased. This is used to check that the
sell side of the protocol isn't trying to sell us the wrong thing, whether by accident or on purpose.
- ``sessionID: Long`` - the session ID that was handed to the seller in order to start the protocol.
The run methods return a ``ListenableFuture`` that will complete when the protocol has finished.
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 ``.resultFuture.get()`` on resulting object to
to either runBuyer or runSeller, depending on who we are, and then call ``.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?
Finally, we define a couple of exceptions, and a class that will be used as a protocol message called ``SellerTradeInfo``.
Suspendable methods
-------------------
The ``call`` method of the buyer/seller classes is marked with the ``@Suspendable`` annotation. What does this mean?
As mentioned above, our protocol framework will at points suspend the code and serialise it to disk. For this to work,
any methods on the call stack must have been pre-marked as ``@Suspendable`` so the bytecode rewriter knows to modify
the underlying code to support this new feature. A protocol is suspended when calling either ``receive``, ``send`` or
``sendAndReceive`` which we will learn more about below. For now, just be aware that when one of these methods is
invoked, all methods on the stack must have been marked. If you forget, then in the unit test environment you will
get a useful error message telling you which methods you didn't mark. The fix is simple enough: just add the annotation
and try again.
.. note:: A future version of Java is likely to remove this pre-marking requirement completely.
The state machine manager
-------------------------
The SMM is a class responsible for taking care of all running protocols in a node. It knows how to register handlers
with a ``MessagingService`` and iterate the right state machine when the time comes. It provides the
with a ``MessagingService`` and iterate the right state machine when messages arrive. It provides the
send/receive/sendAndReceive calls that let the code request network interaction and it will store a serialised copy of
each state machine before it's suspended to wait for the network.
@ -175,66 +237,8 @@ unit tests to see how it's done.
Implementing the seller
-----------------------
.. container:: codeset
.. sourcecode:: kotlin
private class TwoPartyTradeProtocolImpl(private val smm: StateMachineManager) : TwoPartyTradeProtocol() {
companion object {
val TRADE_TOPIC = "com.r3cev.protocols.trade"
}
class SellerImpl : Seller() {
override fun call(args: SellerInitialArgs): Pair<TimestampedWireTransaction, LedgerTransaction> {
TODO()
}
}
class BuyerImpl : Buyer() {
override fun call(args: BuyerInitialArgs): Pair<TimestampedWireTransaction, LedgerTransaction> {
TODO()
}
}
override fun runSeller(otherSide: SingleMessageRecipient, args: SellerInitialArgs): Seller {
return smm.add(otherSide, args, "$TRADE_TOPIC.seller", SellerImpl::class.java)
}
override fun runBuyer(otherSide: SingleMessageRecipient, args: BuyerInitialArgs): Buyer {
return smm.add(otherSide, args, "$TRADE_TOPIC.buyer", BuyerImpl::class.java)
}
}
We start with a skeleton on which we will build the protocol. Putting things in a *companion object* in Kotlin is like
declaring them as static members in Java. Here, we define a "topic" that will identify trade related messages that
arrive at a node (see :doc:`messaging` for details).
The runSeller and runBuyer methods simply start the state machines, passing in a reference to the classes and the topics
each side will use.
Now let's try implementing the seller side. Firstly, we're going to need a message to send to the buyer describing what
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.
.. container:: codeset
.. sourcecode:: kotlin
// 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,
val sellerOwnerKey: PublicKey,
val buyerSessionID: Long
)
That's simple enough: our opening protocol message will be serialised before being sent over the wire, and it contains
the details that were agreed so we can double check them. It also contains a session ID so we can identify this
trade's messages, and a pointer to where the asset that is being sold can be found on the ledger.
Next we add some code to the ``SellerImpl.call`` method:
Let's implement the ``Seller.call`` method. This will be invoked by the platform when the protocol is started by the
``StateMachineManager``.
.. container:: codeset
@ -243,25 +247,24 @@ Next we add some code to the ``SellerImpl.call`` method:
val sessionID = random63BitValue()
// Make the first message we'll send to kick off the protocol.
val hello = SellerTradeInfo(args.assetToSell, args.price, args.myKeyPair.public, sessionID)
val hello = SellerTradeInfo(assetToSell, price, myKeyPair.public, sessionID)
val partialTX = sendAndReceive<SignedWireTransaction>(TRADE_TOPIC, args.buyerSessionID,
sessionID, hello)
val partialTX = sendAndReceive(TRADE_TOPIC, buyerSessionID, sessionID, hello, SignedWireTransaction::class.java)
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
the initial protocol message, and then call ``sendAndReceive``. This function takes a few arguments:
- A type argument, which is the object we're expecting to receive from the other side.
- The topic string that ensures the message is routed to the right bit of code in the other side's node.
- The session IDs that ensure the messages don't get mixed up with other simultaneous trades.
- And finally, the thing to send. It'll be serialised and sent automatically.
- The thing to send. It'll be serialised and sent automatically.
- Finally a type argument, which is the kind of object we're expecting to receive from the other side.
Once sendAndReceive is called, the call method will be suspended into a continuation. When it gets back we'll do a log
message. The buyer is supposed to send us a transaction with all the right inputs/outputs/commands in return, with their
cash put into the transaction and their signature on it authorising the movement of the cash.
.. note:: There are a few rules you need to bear in mind when writing a class that will be used as a continuation.
.. note:: There are a couple of rules you need to bear in mind when writing a class that will be used as a continuation.
The first is that anything on the stack when the function is suspended will be stored into the heap and kept alive by
the garbage collector. So try to avoid keeping enormous data structures alive unless you really have to.
@ -270,11 +273,6 @@ cash put into the transaction and their signature on it authorising the movement
doing things like creating threads from inside these calls would be a bad idea. They should only contain business
logic.
The third rule to bear in mind is that you can't declare variables or methods in these classes and access
them from outside of the class, due to the bytecode rewriting and classloader tricks that are used to make this all
work. If you want access to something inside the BuyerImpl or SellerImpl classes, you must define a super-interface
or super-class (like ``Buyer``/``Seller``) and put what you want to access there.
OK, let's keep going:
.. container:: codeset
@ -294,14 +292,10 @@ OK, let's keep going:
// and fully audit the transaction chains to convince ourselves that it is actually valid.
// - This tx may include output states that impose odd conditions on the movement of the cash,
// once we implement state pairing.
//
// but the goal of this code is not to be fully secure, but rather, just to find good ways to
// express protocol state machines on top of the messaging layer.
}
val ourSignature = args.myKeyPair.signWithECDSA(partialTX.txBits.bits)
val fullySigned: SignedWireTransaction = partialTX.copy(sigs = partialTX.sigs + ourSignature)
// We should run it through our full TransactionGroup of all transactions here.
fullySigned.verify()
val timestamped: TimestampedWireTransaction = fullySigned.toTimestampedTransaction(serviceHub.timestampingService)
logger().trace { "Built finished transaction, sending back to secondary!" }
@ -313,7 +307,8 @@ OK, let's keep going:
Here, we see some assertions and signature checking to satisfy ourselves that we're not about to sign something
incorrect. Once we're happy, we calculate a signature over the transaction to authorise the movement of the asset
we are selling, and then we verify things to make sure it's all OK. Finally, we request timestamping of the
transaction, and send the now finalised and validated transaction back to the buyer.
transaction, in case the contracts governing the asset we're selling require it, and send the now finalised and
validated transaction back to the buyer.
.. warning:: This code is **not secure**. Other than not checking for all possible invalid constructions, if the
seller stops before sending the finalised transaction to the buyer, the seller is left with a valid transaction
@ -332,10 +327,10 @@ OK, let's do the same for the buyer side:
.. sourcecode:: kotlin
class BuyerImpl : Buyer() {
override fun call(args: BuyerInitialArgs): Pair<TimestampedWireTransaction, LedgerTransaction> {
@Suspendable
override fun call(): Pair<TimestampedWireTransaction, LedgerTransaction> {
// Wait for a trade request to come in on our pre-provided session ID.
val tradeRequest = receive<SellerTradeInfo>(TRADE_TOPIC, args.sessionID)
val tradeRequest = receive(TRADE_TOPIC, args.sessionID, SellerTradeInfo::class.java)
// What is the seller trying to sell us?
val assetTypeName = tradeRequest.assetForSale.state.javaClass.name
@ -343,10 +338,10 @@ OK, let's do the same for the buyer side:
// Check the start message for acceptability.
check(tradeRequest.sessionID > 0)
if (tradeRequest.price > args.acceptablePrice)
if (tradeRequest.price > acceptablePrice)
throw UnacceptablePriceException(tradeRequest.price)
if (!args.typeToBuy.isInstance(tradeRequest.assetForSale.state))
throw AssetMismatchException(args.typeToBuy.name, assetTypeName)
if (!typeToBuy.isInstance(tradeRequest.assetForSale.state))
throw AssetMismatchException(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
@ -385,8 +380,8 @@ OK, let's do the same for the buyer side:
// 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)
val fullySigned = sendAndReceive(TRADE_TOPIC, tradeRequest.sessionID, sessionID, stx,
TimestampedWireTransaction::class.java)
logger().trace { "Got fully signed transaction, verifying ... "}
@ -397,9 +392,8 @@ OK, let's do the same for the buyer side:
return Pair(fullySigned, ltx)
}
}
This code is fairly straightforward. Here are some things to pay attention to:
This code is longer but still 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``. See the contracts tutorial if this isn't

View File

@ -201,6 +201,7 @@ prove or disprove the following hypothesis:</p>
<li class="toctree-l2"><a class="reference internal" href="protocol-state-machines.html#introduction">Introduction</a></li>
<li class="toctree-l2"><a class="reference internal" href="protocol-state-machines.html#theory">Theory</a></li>
<li class="toctree-l2"><a class="reference internal" href="protocol-state-machines.html#a-two-party-trading-protocol">A two party trading protocol</a></li>
<li class="toctree-l2"><a class="reference internal" href="protocol-state-machines.html#suspendable-methods">Suspendable methods</a></li>
<li class="toctree-l2"><a class="reference internal" href="protocol-state-machines.html#the-state-machine-manager">The state machine manager</a></li>
<li class="toctree-l2"><a class="reference internal" href="protocol-state-machines.html#implementing-the-seller">Implementing the seller</a></li>
<li class="toctree-l2"><a class="reference internal" href="protocol-state-machines.html#implementing-the-buyer">Implementing the buyer</a></li>

View File

@ -95,6 +95,7 @@
<li class="toctree-l2"><a class="reference internal" href="#introduction">Introduction</a></li>
<li class="toctree-l2"><a class="reference internal" href="#theory">Theory</a></li>
<li class="toctree-l2"><a class="reference internal" href="#a-two-party-trading-protocol">A two party trading protocol</a></li>
<li class="toctree-l2"><a class="reference internal" href="#suspendable-methods">Suspendable methods</a></li>
<li class="toctree-l2"><a class="reference internal" href="#the-state-machine-manager">The state machine manager</a></li>
<li class="toctree-l2"><a class="reference internal" href="#implementing-the-seller">Implementing the seller</a></li>
<li class="toctree-l2"><a class="reference internal" href="#implementing-the-buyer">Implementing the buyer</a></li>
@ -183,7 +184,7 @@ when actually they need to receive/send another.</li>
<p>Actor frameworks can solve some of the above but they are often tightly bound to a particular messaging layer, and
we would like to keep a clean separation. Additionally, they are typically not type safe, and don&#8217;t make persistence or
writing sequential code much easier.</p>
<p>To put these problems in perspective the <em>payment channel protocol</em> in the bitcoinj library, which allows bitcoins to
<p>To put these problems in perspective, the <em>payment channel protocol</em> in the bitcoinj library, which allows bitcoins to
be temporarily moved off-chain and traded at high speed between two parties in private, consists of about 7000 lines of
Java and took over a month of full time work to develop. Most of that code is concerned with the details of persistence,
message passing, lifecycle management, error handling and callback management. Because the business logic is quite
@ -194,10 +195,10 @@ construction of them that automatically handles many of the concerns outlined ab
<div class="section" id="theory">
<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 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>
unserialised and resumed from where it was suspended. This concept is sometimes referred to as &#8220;fibers&#8221;. 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 library called Quasar 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>
<ul class="simple">
<li>It allows us to write code that is free of callbacks, that looks like ordinary sequential code.</li>
@ -230,71 +231,132 @@ it lacks a signature from S authorising movement of the asset.</li>
<p>Assuming no malicious termination, they both end the protocol being in posession of a valid, signed transaction that
represents an atomic asset swap.</p>
<p>Note that it&#8217;s the <em>seller</em> who initiates contact with the buyer, not vice-versa as you might imagine.</p>
<p>We start by defining an abstract base class to encapsulate the protocol. This is what code that invokes the protocol
will see:</p>
<p>We start by defining a wrapper that namespaces the protocol code, two functions to start either the buy or sell side
of the protocol, and two classes that will contain the protocol definition. We also pick what data will be used by
each side.</p>
<div class="codeset container">
<div class="highlight-kotlin"><div class="highlight"><pre>abstract class TwoPartyTradeProtocol {
class SellerInitialArgs(
val assetToSell: StateAndRef&lt;OwnableState&gt;,
val price: Amount,
val myKeyPair: KeyPair,
val buyerSessionID: Long
)
<div class="highlight-kotlin"><div class="highlight"><pre><span class="k">object</span> <span class="nc">TwoPartyTradeProtocol</span> <span class="p">{</span>
<span class="k">val</span> <span class="py">TRADE_TOPIC</span> <span class="p">=</span> <span class="s">&quot;platform.trade&quot;</span>
abstract fun runSeller(otherSide: SingleMessageRecipient, args: SellerInitialArgs): Seller
<span class="k">fun</span> <span class="nf">runSeller</span><span class="p">(</span><span class="n">smm</span><span class="p">:</span> <span class="n">StateMachineManager</span><span class="p">,</span> <span class="n">timestampingAuthority</span><span class="p">:</span> <span class="n">LegallyIdentifiableNode</span><span class="p">,</span>
<span class="n">otherSide</span><span class="p">:</span> <span class="n">SingleMessageRecipient</span><span class="p">,</span> <span class="n">assetToSell</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="n">price</span><span class="p">:</span> <span class="n">Amount</span><span class="p">,</span>
<span class="n">myKeyPair</span><span class="p">:</span> <span class="n">KeyPair</span><span class="p">,</span> <span class="n">buyerSessionID</span><span class="p">:</span> <span class="n">Long</span><span class="p">):</span> <span class="n">ListenableFuture</span><span class="p">&lt;</span><span class="n">Pair</span><span class="p">&lt;</span><span class="n">WireTransaction</span><span class="p">,</span> <span class="n">LedgerTransaction</span><span class="p">&gt;&gt;</span> <span class="p">{</span>
<span class="k">val</span> <span class="py">seller</span> <span class="p">=</span> <span class="n">Seller</span><span class="p">(</span><span class="n">otherSide</span><span class="p">,</span> <span class="n">timestampingAuthority</span><span class="p">,</span> <span class="n">assetToSell</span><span class="p">,</span> <span class="n">price</span><span class="p">,</span> <span class="n">myKeyPair</span><span class="p">,</span> <span class="n">buyerSessionID</span><span class="p">)</span>
<span class="n">smm</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="s">&quot;$TRADE_TOPIC.seller&quot;</span><span class="p">,</span> <span class="n">seller</span><span class="p">)</span>
<span class="k">return</span> <span class="n">seller</span><span class="p">.</span><span class="n">resultFuture</span>
<span class="p">}</span>
class BuyerInitialArgs(
val acceptablePrice: Amount,
val typeToBuy: Class&lt;out OwnableState&gt;,
val sessionID: Long
)
<span class="k">fun</span> <span class="nf">runBuyer</span><span class="p">(</span><span class="n">smm</span><span class="p">:</span> <span class="n">StateMachineManager</span><span class="p">,</span> <span class="n">timestampingAuthority</span><span class="p">:</span> <span class="n">LegallyIdentifiableNode</span><span class="p">,</span>
<span class="n">otherSide</span><span class="p">:</span> <span class="n">SingleMessageRecipient</span><span class="p">,</span> <span class="n">acceptablePrice</span><span class="p">:</span> <span class="n">Amount</span><span class="p">,</span> <span class="n">typeToBuy</span><span class="p">:</span> <span class="n">Class</span><span class="p">&lt;</span><span class="k">out</span> <span class="n">OwnableState</span><span class="p">&gt;,</span>
<span class="n">sessionID</span><span class="p">:</span> <span class="n">Long</span><span class="p">):</span> <span class="n">ListenableFuture</span><span class="p">&lt;</span><span class="n">Pair</span><span class="p">&lt;</span><span class="n">WireTransaction</span><span class="p">,</span> <span class="n">LedgerTransaction</span><span class="p">&gt;&gt;</span> <span class="p">{</span>
<span class="k">val</span> <span class="py">buyer</span> <span class="p">=</span> <span class="n">Buyer</span><span class="p">(</span><span class="n">otherSide</span><span class="p">,</span> <span class="n">timestampingAuthority</span><span class="p">.</span><span class="n">identity</span><span class="p">,</span> <span class="n">acceptablePrice</span><span class="p">,</span> <span class="n">typeToBuy</span><span class="p">,</span> <span class="n">sessionID</span><span class="p">)</span>
<span class="n">smm</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="s">&quot;$TRADE_TOPIC.buyer&quot;</span><span class="p">,</span> <span class="n">buyer</span><span class="p">)</span>
<span class="k">return</span> <span class="n">buyer</span><span class="p">.</span><span class="n">resultFuture</span>
<span class="p">}</span>
abstract fun runBuyer(otherSide: SingleMessageRecipient, args: BuyerInitialArgs): Buyer
<span class="k">class</span> <span class="nc">Seller</span><span class="p">(</span><span class="k">val</span> <span class="py">otherSide</span><span class="p">:</span> <span class="n">SingleMessageRecipient</span><span class="p">,</span>
<span class="k">val</span> <span class="py">timestampingAuthority</span><span class="p">:</span> <span class="n">LegallyIdentifiableNode</span><span class="p">,</span>
<span class="k">val</span> <span class="py">assetToSell</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>
<span class="k">val</span> <span class="py">myKeyPair</span><span class="p">:</span> <span class="n">KeyPair</span><span class="p">,</span>
<span class="k">val</span> <span class="py">buyerSessionID</span><span class="p">:</span> <span class="n">Long</span><span class="p">)</span> <span class="p">:</span> <span class="n">ProtocolStateMachine</span><span class="p">&lt;</span><span class="n">Pair</span><span class="p">&lt;</span><span class="n">WireTransaction</span><span class="p">,</span> <span class="n">LedgerTransaction</span><span class="p">&gt;&gt;()</span> <span class="p">{</span>
<span class="n">@Suspendable</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">call</span><span class="p">():</span> <span class="n">Pair</span><span class="p">&lt;</span><span class="n">WireTransaction</span><span class="p">,</span> <span class="n">LedgerTransaction</span><span class="p">&gt;</span> <span class="p">{</span>
<span class="n">TODO</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
abstract class Buyer : ProtocolStateMachine&lt;BuyerInitialArgs, Pair&lt;TimestampedWireTransaction, LedgerTransaction&gt;&gt;()
abstract class Seller : ProtocolStateMachine&lt;SellerInitialArgs, Pair&lt;TimestampedWireTransaction, LedgerTransaction&gt;&gt;()
<span class="c1">// This object is serialised to the network and is the first protocol message the seller sends to the buyer.</span>
<span class="k">private</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>
<span class="k">val</span> <span class="py">sellerOwnerKey</span><span class="p">:</span> <span class="n">PublicKey</span><span class="p">,</span>
<span class="k">val</span> <span class="py">sessionID</span><span class="p">:</span> <span class="n">Long</span>
<span class="p">)</span>
companion object {
@JvmStatic fun create(smm: StateMachineManager): TwoPartyTradeProtocol {
return TwoPartyTradeProtocolImpl(smm)
}
}
}
<span class="k">private</span> <span class="k">class</span> <span class="nc">UnacceptablePriceException</span><span class="p">(</span><span class="k">val</span> <span class="py">givenPrice</span><span class="p">:</span> <span class="n">Amount</span><span class="p">)</span> <span class="p">:</span> <span class="n">Exception</span><span class="p">()</span>
<span class="k">private</span> <span class="k">class</span> <span class="nc">AssetMismatchException</span><span class="p">(</span><span class="k">val</span> <span class="py">expectedTypeName</span><span class="p">:</span> <span class="n">String</span><span class="p">,</span> <span class="k">val</span> <span class="py">typeName</span><span class="p">:</span> <span class="n">String</span><span class="p">)</span> <span class="p">:</span> <span class="n">Exception</span><span class="p">()</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">toString</span><span class="p">()</span> <span class="p">=</span> <span class="s">&quot;The submitted asset didn&#39;t match the expected type: $expectedTypeName vs $typeName&quot;</span>
<span class="p">}</span>
<span class="c1">// The buyer&#39;s side of the protocol. See note above Seller to learn about the caveats here.</span>
<span class="k">class</span> <span class="nc">Buyer</span><span class="p">(</span><span class="k">val</span> <span class="py">otherSide</span><span class="p">:</span> <span class="n">SingleMessageRecipient</span><span class="p">,</span>
<span class="k">val</span> <span class="py">timestampingAuthority</span><span class="p">:</span> <span class="n">Party</span><span class="p">,</span>
<span class="k">val</span> <span class="py">acceptablePrice</span><span class="p">:</span> <span class="n">Amount</span><span class="p">,</span>
<span class="k">val</span> <span class="py">typeToBuy</span><span class="p">:</span> <span class="n">Class</span><span class="p">&lt;</span><span class="k">out</span> <span class="n">OwnableState</span><span class="p">&gt;,</span>
<span class="k">val</span> <span class="py">sessionID</span><span class="p">:</span> <span class="n">Long</span><span class="p">)</span> <span class="p">:</span> <span class="n">ProtocolStateMachine</span><span class="p">&lt;</span><span class="n">Pair</span><span class="p">&lt;</span><span class="n">WireTransaction</span><span class="p">,</span> <span class="n">LedgerTransaction</span><span class="p">&gt;&gt;()</span> <span class="p">{</span>
<span class="n">@Suspendable</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">call</span><span class="p">():</span> <span class="n">Pair</span><span class="p">&lt;</span><span class="n">WireTransaction</span><span class="p">,</span> <span class="n">LedgerTransaction</span><span class="p">&gt;</span> <span class="p">{</span>
<span class="n">TODO</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
</div>
</div>
<p>Let&#8217;s unpack what this code does:</p>
<ul class="simple">
<li>It defines a several classes nested inside the main <code class="docutils literal"><span class="pre">TwoPartyTradeProtocol</span></code> class, and a couple of methods, one to
run the buyer side of the protocol and one to run the seller side.</li>
<li>Two of the classes are simply wrappers for parameters to the trade; things like what is being sold, what the price
of the asset is, how much the buyer is willing to pay and so on. The <code class="docutils literal"><span class="pre">myKeyPair</span></code> field is simply the public key
that the seller wishes the buyer to send the cash to. The session ID field is sent from buyer to seller when the
trade is being set up and is used to keep messages separated on the network, and stop malicious entities trying to
interfere with the message stream.</li>
<li>The other two classes define empty abstract classes called <code class="docutils literal"><span class="pre">Buyer</span></code> and <code class="docutils literal"><span class="pre">Seller</span></code>. These inherit from a class
called <code class="docutils literal"><span class="pre">ProtocolStateMachine</span></code> and provide two type parameters: the arguments class we just defined for each side
and the type of the object that the protocol finally produces (this doesn&#8217;t have to be identical for each side, even
though in this case it is).</li>
<li>Finally it simply defines a static method that creates an instance of an object that inherits from this base class
and returns it, with a <code class="docutils literal"><span class="pre">StateMachineManager</span></code> as an instance. The Impl class will be defined below.</li>
<li>It defines a several classes nested inside the main <code class="docutils literal"><span class="pre">TwoPartyTradeProtocol</span></code> singleton, and a couple of methods, one
to run the buyer side of the protocol and one to run the seller side.</li>
<li>It defines the &#8220;trade topic&#8221;, which is just a string that namespaces this protocol. The prefix &#8220;platform.&#8221; is reserved
by the DLG, but you can define your own protocols using standard Java-style reverse DNS notation.</li>
<li>The <code class="docutils literal"><span class="pre">runBuyer</span></code> and <code class="docutils literal"><span class="pre">runSeller</span></code> methods take a number of parameters that specialise the protocol for this run,
use them to construct a <code class="docutils literal"><span class="pre">Buyer</span></code> or <code class="docutils literal"><span class="pre">Seller</span></code> object respectively, and then add the new instances to the
<code class="docutils literal"><span class="pre">StateMachineManager</span></code>. The purpose of this class is described below. The <code class="docutils literal"><span class="pre">smm.add</span></code> method takes a logger name as
the first parameter, this is just a standard JDK logging identifier string, and the instance to add.</li>
</ul>
<p>Going through the data needed to become a seller, we have:</p>
<ul class="simple">
<li><code class="docutils literal"><span class="pre">timestampingAuthority:</span> <span class="pre">LegallyIdentifiableNode</span></code> - a reference to a node on the P2P network that acts as a trusted
timestamper. The use of timestamping is described in <a class="reference internal" href="data-model.html"><em>Data model</em></a>.</li>
<li><code class="docutils literal"><span class="pre">otherSide:</span> <span class="pre">SingleMessageRecipient</span></code> - the network address of the node with which you are trading.</li>
<li><code class="docutils literal"><span class="pre">assetToSell:</span> <span class="pre">StateAndRef&lt;OwnableState&gt;</span></code> - a pointer to the ledger entry that represents the thing being sold.</li>
<li><code class="docutils literal"><span class="pre">price:</span> <span class="pre">Amount</span></code> - the agreed on price that the asset is being sold for.</li>
<li><code class="docutils literal"><span class="pre">myKeyPair:</span> <span class="pre">KeyPair</span></code> - the key pair that controls the asset being sold. It will be used to sign the transaction.</li>
<li><code class="docutils literal"><span class="pre">buyerSessionID:</span> <span class="pre">Long</span></code> - a unique number that identifies this trade to the buyer. It is expected that the buyer
knows that the trade is going to take place and has sent you such a number already. (This field may go away in a future
iteration of the framework)</li>
</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
to be unguessable. 63 bits is good enough.</p>
</div>
<p>And for the buyer:</p>
<ul class="simple">
<li><code class="docutils literal"><span class="pre">acceptablePrice:</span> <span class="pre">Amount</span></code> - the price that was agreed upon out of band. If the seller specifies a price less than
or equal to this, then the trade will go ahead.</li>
<li><code class="docutils literal"><span class="pre">typeToBuy:</span> <span class="pre">Class&lt;out</span> <span class="pre">OwnableState&gt;</span></code> - the type of state that is being purchased. This is used to check that the
sell side of the protocol isn&#8217;t trying to sell us the wrong thing, whether by accident or on purpose.</li>
<li><code class="docutils literal"><span class="pre">sessionID:</span> <span class="pre">Long</span></code> - the session ID that was handed to the seller in order to start the protocol.</li>
</ul>
<p>The run methods return a <code class="docutils literal"><span class="pre">ListenableFuture</span></code> that will complete when the protocol has finished.</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">.resultFuture.get()</span></code> on resulting object to
to either runBuyer or runSeller, depending on who we are, and then call <code class="docutils literal"><span class="pre">.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>
<p>Finally, we define a couple of exceptions, and a class that will be used as a protocol message called <code class="docutils literal"><span class="pre">SellerTradeInfo</span></code>.</p>
</div>
<div class="section" id="suspendable-methods">
<h2>Suspendable methods<a class="headerlink" href="#suspendable-methods" title="Permalink to this headline"></a></h2>
<p>The <code class="docutils literal"><span class="pre">call</span></code> method of the buyer/seller classes is marked with the <code class="docutils literal"><span class="pre">&#64;Suspendable</span></code> annotation. What does this mean?</p>
<p>As mentioned above, our protocol framework will at points suspend the code and serialise it to disk. For this to work,
any methods on the call stack must have been pre-marked as <code class="docutils literal"><span class="pre">&#64;Suspendable</span></code> so the bytecode rewriter knows to modify
the underlying code to support this new feature. A protocol is suspended when calling either <code class="docutils literal"><span class="pre">receive</span></code>, <code class="docutils literal"><span class="pre">send</span></code> or
<code class="docutils literal"><span class="pre">sendAndReceive</span></code> which we will learn more about below. For now, just be aware that when one of these methods is
invoked, all methods on the stack must have been marked. If you forget, then in the unit test environment you will
get a useful error message telling you which methods you didn&#8217;t mark. The fix is simple enough: just add the annotation
and try again.</p>
<div class="admonition note">
<p class="first admonition-title">Note</p>
<p class="last">A future version of Java is likely to remove this pre-marking requirement completely.</p>
</div>
</div>
<div class="section" id="the-state-machine-manager">
<h2>The state machine manager<a class="headerlink" href="#the-state-machine-manager" title="Permalink to this headline"></a></h2>
<p>The SMM is a class responsible for taking care of all running protocols in a node. It knows how to register handlers
with a <code class="docutils literal"><span class="pre">MessagingService</span></code> and iterate the right state machine when the time comes. It provides the
with a <code class="docutils literal"><span class="pre">MessagingService</span></code> and iterate the right state machine when messages arrive. It provides the
send/receive/sendAndReceive calls that let the code request network interaction and it will store a serialised copy of
each state machine before it&#8217;s suspended to wait for the network.</p>
<p>To get a <code class="docutils literal"><span class="pre">StateMachineManager</span></code>, you currently have to build one by passing in a <code class="docutils literal"><span class="pre">ServiceHub</span></code> and a thread or thread
@ -303,68 +365,15 @@ unit tests to see how it&#8217;s done.</p>
</div>
<div class="section" id="implementing-the-seller">
<h2>Implementing the seller<a class="headerlink" href="#implementing-the-seller" title="Permalink to this headline"></a></h2>
<div class="codeset container">
<div class="highlight-kotlin"><div class="highlight"><pre>private class TwoPartyTradeProtocolImpl(private val smm: StateMachineManager) : TwoPartyTradeProtocol() {
companion object {
val TRADE_TOPIC = &quot;com.r3cev.protocols.trade&quot;
}
class SellerImpl : Seller() {
override fun call(args: SellerInitialArgs): Pair&lt;TimestampedWireTransaction, LedgerTransaction&gt; {
TODO()
}
}
class BuyerImpl : Buyer() {
override fun call(args: BuyerInitialArgs): Pair&lt;TimestampedWireTransaction, LedgerTransaction&gt; {
TODO()
}
}
override fun runSeller(otherSide: SingleMessageRecipient, args: SellerInitialArgs): Seller {
return smm.add(otherSide, args, &quot;$TRADE_TOPIC.seller&quot;, SellerImpl::class.java)
}
override fun runBuyer(otherSide: SingleMessageRecipient, args: BuyerInitialArgs): Buyer {
return smm.add(otherSide, args, &quot;$TRADE_TOPIC.buyer&quot;, BuyerImpl::class.java)
}
}
</pre></div>
</div>
</div>
<p>We start with a skeleton on which we will build the protocol. Putting things in a <em>companion object</em> in Kotlin is like
declaring them as static members in Java. Here, we define a &#8220;topic&#8221; that will identify trade related messages that
arrive at a node (see <a class="reference internal" href="messaging.html"><em>Networking and messaging</em></a> for details).</p>
<p>The runSeller and runBuyer methods simply start the state machines, passing in a reference to the classes and the topics
each side will use.</p>
<p>Now let&#8217;s try implementing the seller side. Firstly, we&#8217;re going to need a message to send to the buyer describing what
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</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>
<span class="k">val</span> <span class="py">sellerOwnerKey</span><span class="p">:</span> <span class="n">PublicKey</span><span class="p">,</span>
<span class="k">val</span> <span class="py">buyerSessionID</span><span class="p">:</span> <span class="n">Long</span>
<span class="p">)</span>
</pre></div>
</div>
</div>
<p>That&#8217;s simple enough: our opening protocol message will be serialised before being sent over the wire, and it contains
the details that were agreed so we can double check them. It also contains a session ID so we can identify this
trade&#8217;s messages, and a pointer to where the asset that is being sold can be found on the ledger.</p>
<p>Next we add some code to the <code class="docutils literal"><span class="pre">SellerImpl.call</span></code> method:</p>
<p>Let&#8217;s implement the <code class="docutils literal"><span class="pre">Seller.call</span></code> method. This will be invoked by the platform when the protocol is started by the
<code class="docutils literal"><span class="pre">StateMachineManager</span></code>.</p>
<div class="codeset container">
<div class="highlight-kotlin"><div class="highlight"><pre><span class="k">val</span> <span class="py">sessionID</span> <span class="p">=</span> <span class="n">random63BitValue</span><span class="p">()</span>
<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="k">val</span> <span class="py">hello</span> <span class="p">=</span> <span class="n">SellerTradeInfo</span><span class="p">(</span><span class="n">assetToSell</span><span class="p">,</span> <span class="n">price</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="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">(</span><span class="n">TRADE_TOPIC</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">SignedWireTransaction</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</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>
@ -372,27 +381,23 @@ trade&#8217;s messages, and a pointer to where the asset that is being sold can
<p>That&#8217;s pretty straightforward. We generate a session ID to identify what&#8217;s happening on the seller side, fill out
the initial protocol message, and then call <code class="docutils literal"><span class="pre">sendAndReceive</span></code>. This function takes a few arguments:</p>
<ul class="simple">
<li>A type argument, which is the object we&#8217;re expecting to receive from the other side.</li>
<li>The topic string that ensures the message is routed to the right bit of code in the other side&#8217;s node.</li>
<li>The session IDs that ensure the messages don&#8217;t get mixed up with other simultaneous trades.</li>
<li>And finally, the thing to send. It&#8217;ll be serialised and sent automatically.</li>
<li>The thing to send. It&#8217;ll be serialised and sent automatically.</li>
<li>Finally a type argument, which is the kind of object we&#8217;re expecting to receive from the other side.</li>
</ul>
<p>Once sendAndReceive is called, the call method will be suspended into a continuation. When it gets back we&#8217;ll do a log
message. The buyer is supposed to send us a transaction with all the right inputs/outputs/commands in return, with their
cash put into the transaction and their signature on it authorising the movement of the cash.</p>
<div class="admonition note">
<p class="first admonition-title">Note</p>
<p>There are a few rules you need to bear in mind when writing a class that will be used as a continuation.
<p>There are a couple of rules you need to bear in mind when writing a class that will be used as a continuation.
The first is that anything on the stack when the function is suspended will be stored into the heap and kept alive by
the garbage collector. So try to avoid keeping enormous data structures alive unless you really have to.</p>
<p>The second is that as well as being kept on the heap, objects reachable from the stack will be serialised. The state
<p class="last">The second is that as well as being kept on the heap, objects reachable from the stack will be serialised. The state
of the function call may be resurrected much later! Kryo doesn&#8217;t require objects be marked as serialisable, but even so,
doing things like creating threads from inside these calls would be a bad idea. They should only contain business
logic.</p>
<p class="last">The third rule to bear in mind is that you can&#8217;t declare variables or methods in these classes and access
them from outside of the class, due to the bytecode rewriting and classloader tricks that are used to make this all
work. If you want access to something inside the BuyerImpl or SellerImpl classes, you must define a super-interface
or super-class (like <code class="docutils literal"><span class="pre">Buyer</span></code>/<code class="docutils literal"><span class="pre">Seller</span></code>) and put what you want to access there.</p>
</div>
<p>OK, let&#8217;s keep going:</p>
<div class="codeset container">
@ -409,14 +414,10 @@ or super-class (like <code class="docutils literal"><span class="pre">Buyer</spa
<span class="c1">// and fully audit the transaction chains to convince ourselves that it is actually valid.</span>
<span class="c1">// - This tx may include output states that impose odd conditions on the movement of the cash,</span>
<span class="c1">// once we implement state pairing.</span>
<span class="c1">//</span>
<span class="c1">// but the goal of this code is not to be fully secure, but rather, just to find good ways to</span>
<span class="c1">// express protocol state machines on top of the messaging layer.</span>
<span class="p">}</span>
<span class="k">val</span> <span class="py">ourSignature</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="n">signWithECDSA</span><span class="p">(</span><span class="n">partialTX</span><span class="p">.</span><span class="n">txBits</span><span class="p">.</span><span class="n">bits</span><span class="p">)</span>
<span class="k">val</span> <span class="py">fullySigned</span><span class="p">:</span> <span class="n">SignedWireTransaction</span> <span class="p">=</span> <span class="n">partialTX</span><span class="p">.</span><span class="n">copy</span><span class="p">(</span><span class="n">sigs</span> <span class="p">=</span> <span class="n">partialTX</span><span class="p">.</span><span class="n">sigs</span> <span class="p">+</span> <span class="n">ourSignature</span><span class="p">)</span>
<span class="c1">// We should run it through our full TransactionGroup of all transactions here.</span>
<span class="n">fullySigned</span><span class="p">.</span><span class="n">verify</span><span class="p">()</span>
<span class="k">val</span> <span class="py">timestamped</span><span class="p">:</span> <span class="n">TimestampedWireTransaction</span> <span class="p">=</span> <span class="n">fullySigned</span><span class="p">.</span><span class="n">toTimestampedTransaction</span><span class="p">(</span><span class="n">serviceHub</span><span class="p">.</span><span class="n">timestampingService</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;Built finished transaction, sending back to secondary!&quot;</span> <span class="p">}</span>
@ -430,7 +431,8 @@ or super-class (like <code class="docutils literal"><span class="pre">Buyer</spa
<p>Here, we see some assertions and signature checking to satisfy ourselves that we&#8217;re not about to sign something
incorrect. Once we&#8217;re happy, we calculate a signature over the transaction to authorise the movement of the asset
we are selling, and then we verify things to make sure it&#8217;s all OK. Finally, we request timestamping of the
transaction, and send the now finalised and validated transaction back to the buyer.</p>
transaction, in case the contracts governing the asset we&#8217;re selling require it, and send the now finalised and
validated transaction back to the buyer.</p>
<div class="admonition warning">
<p class="first admonition-title">Warning</p>
<p class="last">This code is <strong>not secure</strong>. Other than not checking for all possible invalid constructions, if the
@ -445,10 +447,10 @@ forms.</p>
<h2>Implementing the buyer<a class="headerlink" href="#implementing-the-buyer" title="Permalink to this headline"></a></h2>
<p>OK, let&#8217;s do the same for the buyer side:</p>
<div class="codeset container">
<div class="highlight-kotlin"><div class="highlight"><pre>class BuyerImpl : Buyer() {
override fun call(args: BuyerInitialArgs): Pair&lt;TimestampedWireTransaction, LedgerTransaction&gt; {
<div class="highlight-kotlin"><div class="highlight"><pre>@Suspendable
override fun call(): Pair&lt;TimestampedWireTransaction, LedgerTransaction&gt; {
// Wait for a trade request to come in on our pre-provided session ID.
val tradeRequest = receive&lt;SellerTradeInfo&gt;(TRADE_TOPIC, args.sessionID)
val tradeRequest = receive(TRADE_TOPIC, args.sessionID, SellerTradeInfo::class.java)
// What is the seller trying to sell us?
val assetTypeName = tradeRequest.assetForSale.state.javaClass.name
@ -456,10 +458,10 @@ forms.</p>
// Check the start message for acceptability.
check(tradeRequest.sessionID &gt; 0)
if (tradeRequest.price &gt; args.acceptablePrice)
if (tradeRequest.price &gt; acceptablePrice)
throw UnacceptablePriceException(tradeRequest.price)
if (!args.typeToBuy.isInstance(tradeRequest.assetForSale.state))
throw AssetMismatchException(args.typeToBuy.name, assetTypeName)
if (!typeToBuy.isInstance(tradeRequest.assetForSale.state))
throw AssetMismatchException(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
@ -498,8 +500,8 @@ forms.</p>
// 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)
val fullySigned = sendAndReceive(TRADE_TOPIC, tradeRequest.sessionID, sessionID, stx,
TimestampedWireTransaction::class.java)
logger().trace { &quot;Got fully signed transaction, verifying ... &quot;}
@ -509,12 +511,11 @@ forms.</p>
logger().trace { &quot;Fully signed transaction was valid. Trade complete! :-)&quot; }
return Pair(fullySigned, ltx)
}
}
</pre></div>
</div>
</div>
<p>This code is fairly straightforward. Here are some things to pay attention to:</p>
<p>This code is longer but still 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>. See the contracts tutorial if this isn&#8217;t

File diff suppressed because one or more lines are too long

View File

@ -43,7 +43,7 @@ Actor frameworks can solve some of the above but they are often tightly bound to
we would like to keep a clean separation. Additionally, they are typically not type safe, and don't make persistence or
writing sequential code much easier.
To put these problems in perspective the *payment channel protocol* in the bitcoinj library, which allows bitcoins to
To put these problems in perspective, the *payment channel protocol* in the bitcoinj library, which allows bitcoins to
be temporarily moved off-chain and traded at high speed between two parties in private, consists of about 7000 lines of
Java and took over a month of full time work to develop. Most of that code is concerned with the details of persistence,
message passing, lifecycle management, error handling and callback management. Because the business logic is quite
@ -56,10 +56,10 @@ 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 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.
unserialised and resumed from where it was suspended. This concept is sometimes referred to as "fibers". 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 library called Quasar which works through behind-the-scenes
bytecode rewriting. You don't have to know how this works to benefit from it, however.
We use continuations for the following reasons:
@ -98,73 +98,135 @@ represents an atomic asset swap.
Note that it's the *seller* who initiates contact with the buyer, not vice-versa as you might imagine.
We start by defining an abstract base class to encapsulate the protocol. This is what code that invokes the protocol
will see:
We start by defining a wrapper that namespaces the protocol code, two functions to start either the buy or sell side
of the protocol, and two classes that will contain the protocol definition. We also pick what data will be used by
each side.
.. container:: codeset
.. sourcecode:: kotlin
abstract class TwoPartyTradeProtocol {
class SellerInitialArgs(
object TwoPartyTradeProtocol {
val TRADE_TOPIC = "platform.trade"
fun runSeller(smm: StateMachineManager, timestampingAuthority: LegallyIdentifiableNode,
otherSide: SingleMessageRecipient, assetToSell: StateAndRef<OwnableState>, price: Amount,
myKeyPair: KeyPair, buyerSessionID: Long): ListenableFuture<Pair<WireTransaction, LedgerTransaction>> {
val seller = Seller(otherSide, timestampingAuthority, assetToSell, price, myKeyPair, buyerSessionID)
smm.add("$TRADE_TOPIC.seller", seller)
return seller.resultFuture
}
fun runBuyer(smm: StateMachineManager, timestampingAuthority: LegallyIdentifiableNode,
otherSide: SingleMessageRecipient, acceptablePrice: Amount, typeToBuy: Class<out OwnableState>,
sessionID: Long): ListenableFuture<Pair<WireTransaction, LedgerTransaction>> {
val buyer = Buyer(otherSide, timestampingAuthority.identity, acceptablePrice, typeToBuy, sessionID)
smm.add("$TRADE_TOPIC.buyer", buyer)
return buyer.resultFuture
}
class Seller(val otherSide: SingleMessageRecipient,
val timestampingAuthority: LegallyIdentifiableNode,
val assetToSell: StateAndRef<OwnableState>,
val price: Amount,
val myKeyPair: KeyPair,
val buyerSessionID: Long
)
val buyerSessionID: Long) : ProtocolStateMachine<Pair<WireTransaction, LedgerTransaction>>() {
@Suspendable
override fun call(): Pair<WireTransaction, LedgerTransaction> {
TODO()
}
}
abstract fun runSeller(otherSide: SingleMessageRecipient, args: SellerInitialArgs): Seller
class BuyerInitialArgs(
val acceptablePrice: Amount,
val typeToBuy: Class<out OwnableState>,
// This object is serialised to the network and is the first protocol message the seller sends to the buyer.
private class SellerTradeInfo(
val assetForSale: StateAndRef<OwnableState>,
val price: Amount,
val sellerOwnerKey: PublicKey,
val sessionID: Long
)
abstract fun runBuyer(otherSide: SingleMessageRecipient, args: BuyerInitialArgs): Buyer
abstract class Buyer : ProtocolStateMachine<BuyerInitialArgs, Pair<TimestampedWireTransaction, LedgerTransaction>>()
abstract class Seller : ProtocolStateMachine<SellerInitialArgs, Pair<TimestampedWireTransaction, LedgerTransaction>>()
class UnacceptablePriceException(val givenPrice: Amount) : Exception()
class AssetMismatchException(val expectedTypeName: String, val typeName: String) : Exception() {
override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName"
}
companion object {
@JvmStatic fun create(smm: StateMachineManager): TwoPartyTradeProtocol {
return TwoPartyTradeProtocolImpl(smm)
// The buyer's side of the protocol. See note above Seller to learn about the caveats here.
class Buyer(val otherSide: SingleMessageRecipient,
val timestampingAuthority: Party,
val acceptablePrice: Amount,
val typeToBuy: Class<out OwnableState>,
val sessionID: Long) : ProtocolStateMachine<Pair<WireTransaction, LedgerTransaction>>() {
@Suspendable
override fun call(): Pair<WireTransaction, LedgerTransaction> {
TODO()
}
}
}
Let's unpack what this code does:
- It defines a several classes nested inside the main ``TwoPartyTradeProtocol`` class, and a couple of methods, one to
run the buyer side of the protocol and one to run the seller side.
- Two of the classes are simply wrappers for parameters to the trade; things like what is being sold, what the price
of the asset is, how much the buyer is willing to pay and so on. The ``myKeyPair`` field is simply the public key
that the seller wishes the buyer to send the cash to. The session ID field is sent from buyer to seller when the
trade is being set up and is used to keep messages separated on the network, and stop malicious entities trying to
interfere with the message stream.
- The other two classes define empty abstract classes called ``Buyer`` and ``Seller``. These inherit from a class
called ``ProtocolStateMachine`` and provide two type parameters: the arguments class we just defined for each side
and the type of the object that the protocol finally produces (this doesn't have to be identical for each side, even
though in this case it is).
- Finally it simply defines a static method that creates an instance of an object that inherits from this base class
and returns it, with a ``StateMachineManager`` as an instance. The Impl class will be defined below.
- It defines a several classes nested inside the main ``TwoPartyTradeProtocol`` singleton, and a couple of methods, one
to run the buyer side of the protocol and one to run the seller side.
- It defines the "trade topic", which is just a string that namespaces this protocol. The prefix "platform." is reserved
by the DLG, but you can define your own protocols using standard Java-style reverse DNS notation.
- The ``runBuyer`` and ``runSeller`` methods take a number of parameters that specialise the protocol for this run,
use them to construct a ``Buyer`` or ``Seller`` object respectively, and then add the new instances to the
``StateMachineManager``. The purpose of this class is described below. The ``smm.add`` method takes a logger name as
the first parameter, this is just a standard JDK logging identifier string, and the instance to add.
Going through the data needed to become a seller, we have:
- ``timestampingAuthority: LegallyIdentifiableNode`` - a reference to a node on the P2P network that acts as a trusted
timestamper. The use of timestamping is described in :doc:`data-model`.
- ``otherSide: SingleMessageRecipient`` - the network address of the node with which you are trading.
- ``assetToSell: StateAndRef<OwnableState>`` - a pointer to the ledger entry that represents the thing being sold.
- ``price: Amount`` - the agreed on price that the asset is being sold for.
- ``myKeyPair: KeyPair`` - the key pair that controls the asset being sold. It will be used to sign the transaction.
- ``buyerSessionID: Long`` - a unique number that identifies this trade to the buyer. It is expected that the buyer
knows that the trade is going to take place and has sent you such a number already. (This field may go away in a future
iteration of the framework)
.. note:: 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.
And for the buyer:
- ``acceptablePrice: Amount`` - the price that was agreed upon out of band. If the seller specifies a price less than
or equal to this, then the trade will go ahead.
- ``typeToBuy: Class<out OwnableState>`` - the type of state that is being purchased. This is used to check that the
sell side of the protocol isn't trying to sell us the wrong thing, whether by accident or on purpose.
- ``sessionID: Long`` - the session ID that was handed to the seller in order to start the protocol.
The run methods return a ``ListenableFuture`` that will complete when the protocol has finished.
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 ``.resultFuture.get()`` on resulting object to
to either runBuyer or runSeller, depending on who we are, and then call ``.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?
Finally, we define a couple of exceptions, and a class that will be used as a protocol message called ``SellerTradeInfo``.
Suspendable methods
-------------------
The ``call`` method of the buyer/seller classes is marked with the ``@Suspendable`` annotation. What does this mean?
As mentioned above, our protocol framework will at points suspend the code and serialise it to disk. For this to work,
any methods on the call stack must have been pre-marked as ``@Suspendable`` so the bytecode rewriter knows to modify
the underlying code to support this new feature. A protocol is suspended when calling either ``receive``, ``send`` or
``sendAndReceive`` which we will learn more about below. For now, just be aware that when one of these methods is
invoked, all methods on the stack must have been marked. If you forget, then in the unit test environment you will
get a useful error message telling you which methods you didn't mark. The fix is simple enough: just add the annotation
and try again.
.. note:: A future version of Java is likely to remove this pre-marking requirement completely.
The state machine manager
-------------------------
The SMM is a class responsible for taking care of all running protocols in a node. It knows how to register handlers
with a ``MessagingService`` and iterate the right state machine when the time comes. It provides the
with a ``MessagingService`` and iterate the right state machine when messages arrive. It provides the
send/receive/sendAndReceive calls that let the code request network interaction and it will store a serialised copy of
each state machine before it's suspended to wait for the network.
@ -175,66 +237,8 @@ unit tests to see how it's done.
Implementing the seller
-----------------------
.. container:: codeset
.. sourcecode:: kotlin
private class TwoPartyTradeProtocolImpl(private val smm: StateMachineManager) : TwoPartyTradeProtocol() {
companion object {
val TRADE_TOPIC = "com.r3cev.protocols.trade"
}
class SellerImpl : Seller() {
override fun call(args: SellerInitialArgs): Pair<TimestampedWireTransaction, LedgerTransaction> {
TODO()
}
}
class BuyerImpl : Buyer() {
override fun call(args: BuyerInitialArgs): Pair<TimestampedWireTransaction, LedgerTransaction> {
TODO()
}
}
override fun runSeller(otherSide: SingleMessageRecipient, args: SellerInitialArgs): Seller {
return smm.add(otherSide, args, "$TRADE_TOPIC.seller", SellerImpl::class.java)
}
override fun runBuyer(otherSide: SingleMessageRecipient, args: BuyerInitialArgs): Buyer {
return smm.add(otherSide, args, "$TRADE_TOPIC.buyer", BuyerImpl::class.java)
}
}
We start with a skeleton on which we will build the protocol. Putting things in a *companion object* in Kotlin is like
declaring them as static members in Java. Here, we define a "topic" that will identify trade related messages that
arrive at a node (see :doc:`messaging` for details).
The runSeller and runBuyer methods simply start the state machines, passing in a reference to the classes and the topics
each side will use.
Now let's try implementing the seller side. Firstly, we're going to need a message to send to the buyer describing what
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.
.. container:: codeset
.. sourcecode:: kotlin
// 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,
val sellerOwnerKey: PublicKey,
val buyerSessionID: Long
)
That's simple enough: our opening protocol message will be serialised before being sent over the wire, and it contains
the details that were agreed so we can double check them. It also contains a session ID so we can identify this
trade's messages, and a pointer to where the asset that is being sold can be found on the ledger.
Next we add some code to the ``SellerImpl.call`` method:
Let's implement the ``Seller.call`` method. This will be invoked by the platform when the protocol is started by the
``StateMachineManager``.
.. container:: codeset
@ -243,25 +247,24 @@ Next we add some code to the ``SellerImpl.call`` method:
val sessionID = random63BitValue()
// Make the first message we'll send to kick off the protocol.
val hello = SellerTradeInfo(args.assetToSell, args.price, args.myKeyPair.public, sessionID)
val hello = SellerTradeInfo(assetToSell, price, myKeyPair.public, sessionID)
val partialTX = sendAndReceive<SignedWireTransaction>(TRADE_TOPIC, args.buyerSessionID,
sessionID, hello)
val partialTX = sendAndReceive(TRADE_TOPIC, buyerSessionID, sessionID, hello, SignedWireTransaction::class.java)
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
the initial protocol message, and then call ``sendAndReceive``. This function takes a few arguments:
- A type argument, which is the object we're expecting to receive from the other side.
- The topic string that ensures the message is routed to the right bit of code in the other side's node.
- The session IDs that ensure the messages don't get mixed up with other simultaneous trades.
- And finally, the thing to send. It'll be serialised and sent automatically.
- The thing to send. It'll be serialised and sent automatically.
- Finally a type argument, which is the kind of object we're expecting to receive from the other side.
Once sendAndReceive is called, the call method will be suspended into a continuation. When it gets back we'll do a log
message. The buyer is supposed to send us a transaction with all the right inputs/outputs/commands in return, with their
cash put into the transaction and their signature on it authorising the movement of the cash.
.. note:: There are a few rules you need to bear in mind when writing a class that will be used as a continuation.
.. note:: There are a couple of rules you need to bear in mind when writing a class that will be used as a continuation.
The first is that anything on the stack when the function is suspended will be stored into the heap and kept alive by
the garbage collector. So try to avoid keeping enormous data structures alive unless you really have to.
@ -270,11 +273,6 @@ cash put into the transaction and their signature on it authorising the movement
doing things like creating threads from inside these calls would be a bad idea. They should only contain business
logic.
The third rule to bear in mind is that you can't declare variables or methods in these classes and access
them from outside of the class, due to the bytecode rewriting and classloader tricks that are used to make this all
work. If you want access to something inside the BuyerImpl or SellerImpl classes, you must define a super-interface
or super-class (like ``Buyer``/``Seller``) and put what you want to access there.
OK, let's keep going:
.. container:: codeset
@ -294,14 +292,10 @@ OK, let's keep going:
// and fully audit the transaction chains to convince ourselves that it is actually valid.
// - This tx may include output states that impose odd conditions on the movement of the cash,
// once we implement state pairing.
//
// but the goal of this code is not to be fully secure, but rather, just to find good ways to
// express protocol state machines on top of the messaging layer.
}
val ourSignature = args.myKeyPair.signWithECDSA(partialTX.txBits.bits)
val fullySigned: SignedWireTransaction = partialTX.copy(sigs = partialTX.sigs + ourSignature)
// We should run it through our full TransactionGroup of all transactions here.
fullySigned.verify()
val timestamped: TimestampedWireTransaction = fullySigned.toTimestampedTransaction(serviceHub.timestampingService)
logger().trace { "Built finished transaction, sending back to secondary!" }
@ -313,7 +307,8 @@ OK, let's keep going:
Here, we see some assertions and signature checking to satisfy ourselves that we're not about to sign something
incorrect. Once we're happy, we calculate a signature over the transaction to authorise the movement of the asset
we are selling, and then we verify things to make sure it's all OK. Finally, we request timestamping of the
transaction, and send the now finalised and validated transaction back to the buyer.
transaction, in case the contracts governing the asset we're selling require it, and send the now finalised and
validated transaction back to the buyer.
.. warning:: This code is **not secure**. Other than not checking for all possible invalid constructions, if the
seller stops before sending the finalised transaction to the buyer, the seller is left with a valid transaction
@ -332,10 +327,10 @@ OK, let's do the same for the buyer side:
.. sourcecode:: kotlin
class BuyerImpl : Buyer() {
override fun call(args: BuyerInitialArgs): Pair<TimestampedWireTransaction, LedgerTransaction> {
@Suspendable
override fun call(): Pair<TimestampedWireTransaction, LedgerTransaction> {
// Wait for a trade request to come in on our pre-provided session ID.
val tradeRequest = receive<SellerTradeInfo>(TRADE_TOPIC, args.sessionID)
val tradeRequest = receive(TRADE_TOPIC, args.sessionID, SellerTradeInfo::class.java)
// What is the seller trying to sell us?
val assetTypeName = tradeRequest.assetForSale.state.javaClass.name
@ -343,10 +338,10 @@ OK, let's do the same for the buyer side:
// Check the start message for acceptability.
check(tradeRequest.sessionID > 0)
if (tradeRequest.price > args.acceptablePrice)
if (tradeRequest.price > acceptablePrice)
throw UnacceptablePriceException(tradeRequest.price)
if (!args.typeToBuy.isInstance(tradeRequest.assetForSale.state))
throw AssetMismatchException(args.typeToBuy.name, assetTypeName)
if (!typeToBuy.isInstance(tradeRequest.assetForSale.state))
throw AssetMismatchException(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
@ -385,8 +380,8 @@ OK, let's do the same for the buyer side:
// 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)
val fullySigned = sendAndReceive(TRADE_TOPIC, tradeRequest.sessionID, sessionID, stx,
TimestampedWireTransaction::class.java)
logger().trace { "Got fully signed transaction, verifying ... "}
@ -397,9 +392,8 @@ OK, let's do the same for the buyer side:
return Pair(fullySigned, ltx)
}
}
This code is fairly straightforward. Here are some things to pay attention to:
This code is longer but still 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``. See the contracts tutorial if this isn't

View File

@ -126,8 +126,8 @@ object TwoPartyTradeProtocol {
)
private class UnacceptablePriceException(val givenPrice: Amount) : Exception()
private class AssetMismatchException(val expectedTypeName: String, val typeName: String) : Exception() {
class UnacceptablePriceException(val givenPrice: Amount) : Exception()
class AssetMismatchException(val expectedTypeName: String, val typeName: String) : Exception() {
override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName"
}