mirror of
https://github.com/corda/corda.git
synced 2025-06-17 22:58:19 +00:00
StateMachine rewrite
This commit is contained in:
@ -416,68 +416,6 @@ Our side of the flow must mirror these calls. We could do this as follows:
|
||||
:end-before: DOCEND 08
|
||||
:dedent: 12
|
||||
|
||||
Why sessions?
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Before ``FlowSession`` s were introduced the send/receive API looked a bit different. They were functions on
|
||||
``FlowLogic`` and took the address ``Party`` as argument. The platform internally maintained a mapping from ``Party`` to
|
||||
session, hiding sessions from the user completely.
|
||||
|
||||
Although this is a convenient API it introduces subtle issues where a message that was originally meant for a specific
|
||||
session may end up in another.
|
||||
|
||||
Consider the following contrived example using the old ``Party`` based API:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/LaunchSpaceshipFlow.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART LaunchSpaceshipFlow
|
||||
:end-before: DOCEND LaunchSpaceshipFlow
|
||||
|
||||
The intention of the flows is very clear: LaunchSpaceshipFlow asks the president whether a spaceship should be launched.
|
||||
It is expecting a boolean reply. The president in return first tells the secretary that they need coffee, which is also
|
||||
communicated with a boolean. Afterwards the president replies to the launcher that they don't want to launch.
|
||||
|
||||
However the above can go horribly wrong when the ``launcher`` happens to be the same party ``getSecretary`` returns. In
|
||||
this case the boolean meant for the secretary will be received by the launcher!
|
||||
|
||||
This indicates that ``Party`` is not a good identifier for the communication sequence, and indeed the ``Party`` based
|
||||
API may introduce ways for an attacker to fish for information and even trigger unintended control flow like in the
|
||||
above case.
|
||||
|
||||
Hence we introduced ``FlowSession``, which identifies the communication sequence. With ``FlowSession`` s the above set
|
||||
of flows would look like this:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/LaunchSpaceshipFlow.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART LaunchSpaceshipFlowCorrect
|
||||
:end-before: DOCEND LaunchSpaceshipFlowCorrect
|
||||
|
||||
Note how the president is now explicit about which session it wants to send to.
|
||||
|
||||
Porting from the old Party-based API
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In the old API the first ``send`` or ``receive`` to a ``Party`` was the one kicking off the counter-flow. This is now
|
||||
explicit in the ``initiateFlow`` function call. To port existing code:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART FlowSession porting
|
||||
:end-before: DOCEND FlowSession porting
|
||||
:dedent: 8
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART FlowSession porting
|
||||
:end-before: DOCEND FlowSession porting
|
||||
:dedent: 12
|
||||
|
||||
Subflows
|
||||
--------
|
||||
Subflows are pieces of reusable flows that may be run by calling ``FlowLogic.subFlow``. There are two broad categories
|
||||
|
@ -582,13 +582,6 @@ public class FlowCookbookJava {
|
||||
SignedTransaction notarisedTx2 = subFlow(new FinalityFlow(fullySignedTx, additionalParties, FINALISATION.childProgressTracker()));
|
||||
// DOCEND 10
|
||||
|
||||
// DOCSTART FlowSession porting
|
||||
send(regulator, new Object()); // Old API
|
||||
// becomes
|
||||
FlowSession session = initiateFlow(regulator);
|
||||
session.send(new Object());
|
||||
// DOCEND FlowSession porting
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -572,13 +572,6 @@ class InitiatorFlow(val arg1: Boolean, val arg2: Int, private val counterparty:
|
||||
val additionalParties: Set<Party> = setOf(regulator)
|
||||
val notarisedTx2: SignedTransaction = subFlow(FinalityFlow(fullySignedTx, additionalParties, FINALISATION.childProgressTracker()))
|
||||
// DOCEND 10
|
||||
|
||||
// DOCSTART FlowSession porting
|
||||
send(regulator, Any()) // Old API
|
||||
// becomes
|
||||
val session = initiateFlow(regulator)
|
||||
session.send(Any())
|
||||
// DOCEND FlowSession porting
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,99 +0,0 @@
|
||||
package net.corda.docs
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowSession
|
||||
import net.corda.core.flows.InitiatedBy
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.utilities.unwrap
|
||||
|
||||
// DOCSTART LaunchSpaceshipFlow
|
||||
@InitiatingFlow
|
||||
class LaunchSpaceshipFlow : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
val shouldLaunchSpaceship = receive<Boolean>(getPresident()).unwrap { it }
|
||||
if (shouldLaunchSpaceship) {
|
||||
launchSpaceship()
|
||||
}
|
||||
}
|
||||
|
||||
fun launchSpaceship() {
|
||||
}
|
||||
|
||||
fun getPresident(): Party {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
|
||||
@InitiatedBy(LaunchSpaceshipFlow::class)
|
||||
@InitiatingFlow
|
||||
class PresidentSpaceshipFlow(val launcher: Party) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
val needCoffee = true
|
||||
send(getSecretary(), needCoffee)
|
||||
val shouldLaunchSpaceship = false
|
||||
send(launcher, shouldLaunchSpaceship)
|
||||
}
|
||||
|
||||
fun getSecretary(): Party {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
|
||||
@InitiatedBy(PresidentSpaceshipFlow::class)
|
||||
class SecretaryFlow(val president: Party) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
// DOCEND LaunchSpaceshipFlow
|
||||
|
||||
// DOCSTART LaunchSpaceshipFlowCorrect
|
||||
@InitiatingFlow
|
||||
class LaunchSpaceshipFlowCorrect : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
val presidentSession = initiateFlow(getPresident())
|
||||
val shouldLaunchSpaceship = presidentSession.receive<Boolean>().unwrap { it }
|
||||
if (shouldLaunchSpaceship) {
|
||||
launchSpaceship()
|
||||
}
|
||||
}
|
||||
|
||||
fun launchSpaceship() {
|
||||
}
|
||||
|
||||
fun getPresident(): Party {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
|
||||
@InitiatedBy(LaunchSpaceshipFlowCorrect::class)
|
||||
@InitiatingFlow
|
||||
class PresidentSpaceshipFlowCorrect(val launcherSession: FlowSession) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
val needCoffee = true
|
||||
val secretarySession = initiateFlow(getSecretary())
|
||||
secretarySession.send(needCoffee)
|
||||
val shouldLaunchSpaceship = false
|
||||
launcherSession.send(shouldLaunchSpaceship)
|
||||
}
|
||||
|
||||
fun getSecretary(): Party {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
|
||||
@InitiatedBy(PresidentSpaceshipFlowCorrect::class)
|
||||
class SecretaryFlowCorrect(val presidentSession: FlowSession) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
// DOCEND LaunchSpaceshipFlowCorrect
|
Reference in New Issue
Block a user