payload: {"action":"created","comment":{"url":"https://api.github.com/repos/corda/corda/pulls/comments/275856018","pull_request_review_id":227269711,"id":275856018,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDI3NTg1NjAxOA==","diff_hunk":"@@ -0,0 +1,588 @@\n+Flow framework internals\n+========================\n+\n+Quasar\n+------\n+\n+Instrumentation\n+^^^^^^^^^^^^^^^\n+\n+Quasar rewrites bytecode to achieve a couple of things:\n+\n+#. Collect the contents of the execution stack. It uses thread-local datastructures for this.\n+#. Create a way to jump from suspension points. It uses an un-catchable exception throw for this.\n+#. Create a way to jump into suspension points. It uses a sequence of switch statements for this.\n+\n+To this end Quasar transforms the JVM bytecode of ``@Suspendable`` functions. Take the following as an example:\n+\n+.. code-block:: kotlin\n+\n+   @Suspendable\n+   fun s0(a): Int {\n+     val b = ..\n+     n1()\n+     s1()\n+\n+     for (..) {\n+       val c = ..\n+       n2()\n+       s2(b)\n+     }\n+\n+     n3()\n+     s3()\n+\n+     return 1;\n+   }\n+\n+   fun n1() { .. }\n+   fun n2() { .. }\n+   fun n3() { .. }\n+   fun n4() { .. }\n+   @Suspendable fun s1() { .. }\n+   @Suspendable fun s2(b) { .. }\n+   @Suspendable fun s3() { .. }\n+\n+Quasar's javaagent, when loading bytecode, will look for functions with the ``@Suspendable`` annotation. Furthermore within these functions\n+it will look for callsites of other ``@Suspendable`` functions (which is why it's important to annotate interface/abstract class methods\n+**as well as** implementations). Note how ``n1``-``n4`` are thus not instrumented, and their callsites aren't relevant in the instrumentation\n+of ``s0``.\n+\n+Disregarding any potential optimizations, quasar will then do the following transformation of ``s0``:\n+\n+.. note:: The following code is pseudo-Kotlin code and includes non-existent constructs like arbitrary code labels and ``goto``.\n+\n+.. code-block:: kotlin\n+\n+   // Quasar uses this annotation to store some metadata about the function, and to check whether the function has been instrumented already\n+   @Instrumented\n+   fun s0(a): Int {\n+     // A variable to temporarily store s0's return value later on\n+     var __return = null\n+     // A variable to indicate whether we are being resumed (we are jumping into a suspension's continuation), or this is a \"regular\" call.\n+     lateinit var __resumed: Boolean\n+     // Retrieve the Quasar-internal execution stack, which is stored in a thread-local.\n+     var __stack = co.paralleluniverse.fibers.Stack.getStack()\n+     if (__stack == null) {\n+       // There's no stack, execute as a regular function\n+       goto lMethodStart\n+     }\n+\n+     // We are being resumed, we are jumping into the suspension point\n+     __resumed = true\n+     // Retrieve the integer that indicates which part of this function we should be jumping into, stored in a thread-local.\n+     val __entry = co.paralleluniverse.fibers.Stack.nextMethodEntry()\n+     when (__entry) {\n+       0 -> {\n+         TODO\n+       }\n+       1 -> {\n+         TODO\n+       }\n+       2 -> {\n+         TODO\n+       }\n+       else -> {\n+         // The entry value is not recognized, the function may be called in a non-suspending capacity.\n+         val __isFunctionCalledAsSuspendable = co.paralleluniverse.fibers.Stack.isFirstInStackOrPushed()\n+         if (_isFunctionCalledAsSuspendable) {\n+           goto lMethodStart\n+         }\n+         __stack = null\n+       }\n+     }\n+\n+     // The first code block, starting from the original non-transformed function start.\n+     lMethodStart:\n+     // This try-catch handles the Quasar-specific SuspendExecution exception. Quasar prevents the catching of this exception in user code.\n+     try {\n+       __resumed = false\n+       val b = ..\n+       TODO\n+     } catch (e: SuspendExecution {\n+       TODO\n+     }\n+   }\n+\n+.. note:: The Quasar javaagent code doing the above rewriting can be found\n+   `here <https://github.com/puniverse/quasar/blob/db0ac29f55bc0515023d67ab86a2178c5e6eeb94/quasar-core/src/main/java/co/paralleluniverse/fibers/instrument/InstrumentMethod.java#L328>`_.\n+   Note that only the main parts of the instrumentation are shown above, the actual transformation is more complex and involves handling\n+   corner cases and optimizations.\n+\n+Fibers\n+^^^^^^\n+\n+The above instrumentation allows the implementation of *co-operative* scheduling. That is, ``@Suspendable`` code can yield its execution by\n+throwing a ``SuspendExecution`` exception. This exception throw takes care of handing the control flow to a top-level try-catch, which then\n+has access to the thread-locally constructed execution stack, as well as a way to return to the suspension point using the \"method entry\"\n+list.\n+\n+A ``Fiber`` thus is nothing more than a data structure holding the execution stack, the method entry list, as well as various bookkeeping\n+data related to the management of the ``Fiber``, e.g. its state enum or identifier.\n+\n+The main try-catch that handles the yielding may be found `here <https://github.com/puniverse/quasar/blob/db0ac29f55bc0515023d67ab86a2178c5e6eeb94/quasar-core/src/main/java/co/paralleluniverse/fibers/Fiber.java#L790>`_.\n+\n+.. note:: For those adventurous enough to explore the implementation, the execution stack and method entry list are merged into two growing\n+   arrays in ``Stack``, one holding ``Object`` s (``dataObject``, for structured objects), the other holding ``long`` s (``dataLong``, for\n+   primitive values). The arrays always have the same length, and they both contain values for each stack frame. The primitive stack\n+   additionally has a \"metadata\" slot for each stack frame, this is where the \"method entry\" value is put, as well as frame size data.\n+\n+Checkpoints\n+-----------\n+\n+The main idea behind checkpoints is to utilize the ``Fiber`` data structure and treat it as a serializable object capturing the state of a\n+running computation. Whenever a Corda-suspendable API is hit, we capture the execution stack and corresponding entry list, and serialize\n+it using `Kryo <https://github.com/EsotericSoftware/kryo>`_, a reflection-based serialization library capable of serializing unstructured\n+data. We thus get a handle to an arbitrary suspended computation.\n+\n+In the flow state machine there is a strict separation of the user-code's state, and the flow framework's internal state. The former is the\n+serialized ``Fiber``, and the latter consists of structured objects.\n+\n+The definition of a ``Checkpoint`` can be found `here <https://github.com/corda/corda/blob/dc4644643247d86b14165944f6925c2d2561eabc/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineState.kt#L55>`_.\n+\n+The \"user state\" can be found in ``FlowState``. It is either\n+\n+#. ``Unstarted``: in this case there's no ``Fiber`` to serialize yet, we serialize the ``FlowLogic`` instead.\n+#. ``Started``: in this case the flow has been started already, and has been suspended on some IO. We store the ``FlowIORequest`` and the\n+   serialized ``Fiber``.\n+\n+The rest of the ``Checkpoint`` deals with internal bookkeeping. Sessions, the subflow-stack, errors. Note how all data structures are\n+read-only. This is deliberate, to enable easier reasoning. Any \"modification\" of the checkpoint therefore implies making a shallow copy.\n+\n+The state machine\n+-----------------\n+\n+The internals of the flow framework were designed as a state machine. A flow is a strange event loop that has a state, and goes through\n+state transitions triggered by events. The transitions may be either\n+\n+#. User transitions, when we hand control to user-defined code in the cordapp. This may transition to a suspension point, the end of the\n+   flow, or may abort exceptionally.\n+#. Internal transitions, where we keep strict track of side-effects and failure conditions.\n+\n+The core data structures of the state machine are:\n+\n+#. ``StateMachineState``: this is the full state of the state machine. It includes the ``Checkpoint`` (the persisted part of the state), and\n+   other non-persisted state, most importantly the list of pending ``DeduplicationHandler`` s, to be described later.\n+#. ``Event``: Every state transition is triggered by one of these. These may be external events, notifying the state machine of something,\n+   or internal events, for example suspensions.\n+#. ``Action``: These are created by internal state transitions. These transitions do not inherently execute any side-effects, instead, they\n+   create a list of ``Action`` s, which are later executed.\n+#. ``FlowContinuation``: indicates how the state machine should proceed after a transition. It can resume to user code, throw an exception,\n+   keep processing events or abort the flow completely.\n+\n+The state machine is a **pure** function that when given an ``Event`` and an initial ``StateMachineState`` returns the next state, a list of\n+``Action`` s to execute, and a ``FlowContinuation`` to indicate how to proceed:\n+\n+.. code-block:: kotlin\n+\n+   // https://github.com/corda/corda/blob/c04a448bf391fb73f9b60cc41e8b5f0c23f81470/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/TransitionResult.kt#L15\n+   data class TransitionResult(\n+           val newState: StateMachineState,\n+           val actions: List<Action> = emptyList(),\n+           val continuation: FlowContinuation = FlowContinuation.ProcessEvents\n+   )\n+\n+   // https://github.com/corda/corda/blob/c04a448bf391fb73f9b60cc41e8b5f0c23f81470/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/StateMachine.kt#L12\n+   fun transition(event: Event, state: StateMachineState): TransitionResult\n+\n+The top-level entry point for the state machine transitions is in ``TopLevelTransition``.\n+\n+As an example let's examine message delivery. This transition will be triggered by a ``DeliverSessionMessage`` event, defined like this:\n+\n+.. code-block:: kotlin\n+\n+    data class DeliverSessionMessage(\n+            val sessionMessage: ExistingSessionMessage,\n+            override val deduplicationHandler: DeduplicationHandler,\n+            val sender: Party\n+    ) : Event(), GeneratedByExternalEvent\n+\n+The event then goes through ``TopLevelTransition``, which then passes it to ``DeliverSessionMessageTransition``. This transition inspects\n+the event, then does the relevant bookkeeping, updating sessions, buffering messages etc. Note that we don't do any checkpoint persistence,\n+and we don't return control to the user code afterwards, we simply schedule a ``DoRemainingWork`` and return a ``ProcessEvents``\n+continuation. This means that it's going to be the next transition that decides whether the received message is \"relevant\" to the current\n+suspension, and whether control should thus be returned to user code with the message.\n+\n+FlowStateMachineImpl\n+--------------------\n+\n+The state machine is a pure function, so what is the \"driver\" of it, that actually executes the transitions and side-effects? This is what\n+``FlowStateMachineImpl`` is doing, which is a ``Fiber``. This class requires great care when it's modified, as the programmer must be aware\n+of what's on the stack, what fields get persisted as part of the ``Checkpoint``, and how the control flow is wired.\n+\n+The usual way to implement state machines is to create a simple event loop that keeps popping events from a queue, and executes the\n+resulting transitions. With flows however this isn't so simple, because control must be returned to suspending operations. Therefore the\n+eventloop is split up into several smaller eventloops, executed when \"we get the chance\", i.e. when users call API functions. Whenever the\n+flow calls a Flow API function, control is handed to the flow framework, that's when we can process events, until a ``FlowContinuation``\n+indicates that control should be returned to user code.\n+\n+There are two functions that aid the above:\n+\n+#. ``FlowStateMachineImpl.processEventsUntilFlowIsResumed``: as the name suggests this is a loop that keeps popping and processing events\n+   from the flow's event queue, until a ``FlowContinuation.Resume`` or some continuation other than ``ProcessEvents`` is returned.\n+#. ``FlowStateMachineImpl.processEventImmediately``: this function skips the event queue and processes an event immediately. There are\n+   certain transitions (e.g. subflow enter/exit) that must be done this way, otherwise the event ordering can cause problems.\n+\n+The two main functions that call the above are the top-level ``run``, which is the entry point of the flow, and ``suspend``, which every\n+blocking API call eventually calls.\n+\n+Suspensions\n+-----------\n+\n+Let's take a look at ``suspend``, which is the most delicate/brittle function in this class, and most probably the whole flow framework.\n+Examining it will reveal a lot about how flows and fibers work.\n+\n+.. code-block:: kotlin\n+\n+    @Suspendable\n+    override fun <R : Any> suspend(ioRequest: FlowIORequest<R>, maySkipCheckpoint: Boolean): R {\n+\n+First off, the type signature. We pass in a ``FlowIORequest<R>``, which is an encapsulation of the IO action we're about to suspend on. It\n+is a sealed class with members like ``Send``/``Receive``/``ExecuteAsyncOperation``. It is serializable, and will be part of the\n+``Checkpoint``. In fact, it is doubly-serialized, as it is in ``FlowState`` in a typed form, but is also present in the Fiber's stack, as a\n+part of ``suspend``'s stack frame.\n+\n+We also pass a ``maySkipCheckpoint`` boolean which if true will prevent the checkpoint from being persisted.\n+\n+The function returns ``R``, but the runtime control flow achieving this \"return\" is quite tricky. When the Fiber suspends a\n+``SuspendExecution`` exception will be thrown, and when the fiber is resumed this ``suspend`` function will be re-entered, however this time\n+in a \"different capacity\", indicated by Quasar's implicitly stored method entry, which will jump to the end of the suspension. This is\n+repeated several times as this function has two suspension points, one of them possibly executing multiple times, as we will see later.\n+\n+.. code-block:: kotlin\n+\n+        val serializationContext = TransientReference(getTransientField(TransientValues::checkpointSerializationContext))\n+        val transaction = extractThreadLocalTransaction()\n+\n+These lines extract some data required for the suspension. Note that both local variables are ``TransientReference`` s, which means the\n+referred-to object will not be serialized as part of the stack frame. During resumption from a deserialized checkpoint these local variables\n+will thus be null, however at that point these objects will not be required anymore.\n+\n+The first line gets the serialization context from a ``TransientValues`` datastructure, which is where all objects live that are required\n+for the flow's functioning but which we don't want to persist. This means all of these values must be re-initialized each time we are\n+restoring a flow from a persisted checkpoint.\n+\n+.. code-block:: kotlin\n+\n+        parkAndSerialize { _, _ ->\n+\n+This is the Quasar API that does the actual suspension. The passed in lambda will not be executed in the current ``suspend`` frame, but\n+rather is stored temporarily in the internal ``Fiber`` structure, and will be run in the outer Quasar try-catch as a \"post park\" action\n+after the catch of the ``SuspendExecution`` exception. See `Fiber.java <https://github.com/puniverse/quasar/blob/db0ac29f55bc0515023d67ab86a2178c5e6eeb94/quasar-core/src/main/java/co/paralleluniverse/fibers/Fiber.java#L804>`_ for details.\n+\n+This means that within this lambda the Fiber will have already technically parked, but it hasn't yet properly yielded to the enclosing\n+scheduler.\n+\n+.. code-block:: kotlin\n+\n+            setLoggingContext()\n+\n+Thread-locals are treated in a special way when Quasar suspends/resumes. Through use of `reflection and JDK-internal unsafe operations <https://github.com/puniverse/quasar/blob/db0ac29f55bc0515023d67ab86a2178c5e6eeb94/quasar-core/src/main/java/co/paralleluniverse/concurrent/util/ThreadAccess.java>`_\n+it accesses all ThreadLocals in the current thread and swaps them with ones stored in the Fiber data structure. In essence for each thread\n+that executes as a Fiber we have two sets of thread locals, one set belongs to the original \"non-Quasar\" thread, and the other belongs to\n+the Fiber. During Fiber execution the latter is active, this is swapped with the former during suspension, and swapped back during resume.\n+Note that during resume these thread-locals may actually be restored to a *different* thread than the original.\n+\n+In the ``parkAndSerialize`` closure the Fiber is partially parked, and at this point the thread locals are already swapped out. This means\n+that data stored in ``ThreadLocal`` s that we still need must be re-initialized somehow. In the above case this is the logging MDC.\n+\n+.. code-block:: kotlin\n+\n+            // Will skip checkpoint if there are any idempotent flows in the subflow stack.\n+            val skipPersistingCheckpoint = containsIdempotentFlows() || maySkipCheckpoint\n+\n+            contextTransactionOrNull = transaction.value\n+            val event = try {\n+                Event.Suspend(\n+                        ioRequest = ioRequest,\n+                        maySkipCheckpoint = skipPersistingCheckpoint,\n+                        fiber = this.checkpointSerialize(context = serializationContext.value)\n+                )\n+            } catch (exception: Exception) {\n+                Event.Error(exception)\n+            }\n+\n+A couple of things happen here. First we determine whether this suspension's subflow stack contains an ``IdempotentFlow``, to determine\n+whether to skip checkpoints. An idempotent flow is a subflow that's safe to replay from the beginning. This means that no checkpoint will be\n+persisted during its execution, as replaying from the previous checkpoint should yield the same results semantically. As an example the\n+notary client flow is an ``IdempotentFlow``, as notarisation is idempotent, and may be safely replayed.\n+\n+We then set another thread-local, the database transaction, which was also swapped out during the park, and we made it available to the\n+closure temporarily using a ``TransientReference`` earlier. The database transaction is used during serialization of the fiber and\n+persistence of the checkpoint.\n+\n+We then create the ``Suspend`` event, which includes the IO request and the serialized Fiber. If there's an exception during serialization\n+we create an ``Error`` event instead. Note how every condition, including error conditions are treated as \"normal control flow\" in the state\n+machine, we must be extra careful as these conditions are also exposed to the user and are part of our API guarantees.\n+\n+.. code-block:: kotlin\n+\n+            // We must commit the database transaction before returning from this closure otherwise Quasar may schedule\n+            // other fibers, so we process the event immediately\n+            val continuation = processEventImmediately(\n+                    event,\n+                    isDbTransactionOpenOnEntry = true,\n+                    isDbTransactionOpenOnExit = false\n+            )\n+            require(continuation == FlowContinuation.ProcessEvents){\"Expected a continuation of type ${FlowContinuation.ProcessEvents}, found $continuation \"}\n+\n+We first process the suspension event ASAP, as we must commit the underlying database transaction before the closure ends.\n+\n+.. note::\n+\n+   The call to ``processEventImmediately`` here reveals why the transition execution is structured in such an unintuitive way, why we are\n+   not simply using an event loop. In an earlier iteration of the flow framework a separate thread pool was handling events and state\n+   transitions, the state machine transitions' execution was completely offloaded, and the Fiber itself was only concerned with the\n+   execution of user code and creation of suspension events.\n+\n+   However later it turned out that under any considerable load this structuring results in heavy resource leakage, and in the case of\n+   database transactions, deadlocks. The reason for this is simply that resource management is often tied to thread lifetime, for example in\n+   the case of serialization buffers, network buffers, database connections. Quasar multiplexes threads across many many more Fibers,\n+   however this also explodes thread-bound resources allocated/retained, which are now Fiber-bound. This means that if we want to take\n+   advantage of Quasar's green threading we must make sure to release any thread-local resources before yielding, otherwise we will leak.\n+\n+   To give a specific example, if we processed the above ``Suspend`` event in another thread or even just after this closure, the underlying\n+   database connection would leak through a proper Fiber yield, meaning it would not be closed until the Fiber is scheduled again or until\n+   the processing thread picks it up and closes it. In the case of database transactions we use Hikari to pool the connections, which means\n+   that the flow framework would quickly exhaust the connection pool, which would thus cause a proper thread block of the Fiber-executing\n+   threads trying to acquire a connection. This in turn means there would be absolutely no chance of the fibers retaining the connections\n+   getting scheduled again, effectively deadlocking the executor threadpool.\n+\n+.. code-block:: kotlin\n+\n+            unpark(SERIALIZER_BLOCKER)\n+        }\n+        return uncheckedCast(processEventsUntilFlowIsResumed(\n+                isDbTransactionOpenOnEntry = false,\n+                isDbTransactionOpenOnExit = true\n+        ))\n+\n+As the last step in the park closure we unpark the Fiber we are currently parking. This effectively causes an \"immediate\" re-enter of the\n+fiber, and therefore the ``suspend`` function, but this time jumping over the park and executing the next statement. Of course this re-enter\n+may happen much later, perhaps even on a different thread.\n+\n+We then enter a mini event-loop, which also does Quasar yields, processing the flow's event queue until a transition continuation indicates\n+that control can be returned to user code . Practically this means that when a flow is waiting on an IO action it won't actually be blocked\n+in the ``parkAndSerialize`` call, but rather in this event loop, popping from the event queue.\n+\n+.. note::\n+\n+   The ``processEvent*`` calls do explicit checks of database transaction state on entry and exit. This is because Quasar yields make\n+   reasoning about resource usage difficult, as they detach resource lifetime from lexical scoping, or in fact any other scoping that\n+   programmers are used to. These internal checks ensure that we are aware of which code blocks have a transaction open and which ones\n+   don't. Incidentally these checks also seem to catch instrumentation/missing ``@Suspendable``-annotation problems.\n+\n+Event processing\n+----------------\n+\n+The processing of an event consists of two steps:\n+\n+#. Calculating a transition. This is the pure ``StateMachineState`` + ``Event`` -> ``TransitionResult`` function.\n+#. Executing the transition. This is done by a ``TransitionExecutor``, which in turn uses an ``ActionExecutor`` for individual ``Action``s.\n+\n+This structuring allows the introspection and interception of state machine transitions through the registering of ``TransitionExecutor``\n+interceptors. These interceptors are ``TransitionExecutor`` s that have access to a delegate. When they receive a new transition they can\n+inspect it, pass it to the delegate, and do something specific to the interceptor.\n+\n+For example checkpoint deserializability is checked by such an `interceptor <https://github.com/corda/corda/blob/76d738c4529fd7bdfabcfd1b61d500f9259978f7/node/src/main/kotlin/net/corda/node/services/statemachine/interceptors/FiberDeserializationCheckingInterceptor.kt#L18>`_.\n+It inspects a transition, and if it contains a Fiber checkpoint then it checks whether it's deserializable in a separate thread.\n+\n+The transition calculation is done in the ``net.corda.node.services.statemachine.transitions`` package, the top-level entry point being\n+``TopLevelTransition``. There is a ``TransitionBuilder`` helper that makes the transition definitions a bit more readable. It contains a\n+``currentState`` field that may be updated with new ``StateMachineState`` instances as the event is being processed, and has some helper\n+functions for common functionality, for example for erroring the state machine with some error condition.\n+\n+Here are a couple of highlighted transitions:\n+\n+Suspend\n+^^^^^^^\n+\n+Handling of ``Event.Suspend`` is quite straightforward and is done `here <https://github.com/corda/corda/blob/26855967989557e4c078bb08dd528231d30fad8b/node/src/main/kotlin/net/corda/node/services/statemachine/transitions/TopLevelTransition.kt#L143>`_.\n+We take the serialized ``Fiber`` and the IO request and create a new checkpoint, then depending on whether we should persist or not we\n+either simply commit the database transaction and schedule a ``DoRemainingWork`` (to be explained later), or we persist the checkpoint, run\n+the ``DeduplicationHandler`` inside-tx hooks, commit, then run the after-tx hooks, and schedule a ``DoRemainingWork``.\n+\n+Every checkpoint persistence implies the above steps, in this specific order.\n+\n+DoRemainingWork","path":"docs/source/contributing-flow-internals.rst","position":407,"original_position":407,"commit_id":"ef0f76deedf75a825ff7c9c04beeb14c52d1ec80","original_commit_id":"511d8a6a63dad5cf13c634fd2b97de158cd01e9e","user":{"login":"exFalso","id":1042674,"node_id":"MDQ6VXNlcjEwNDI2NzQ=","avatar_url":"https://avatars0.githubusercontent.com/u/1042674?v=4","gravatar_id":"","url":"https://api.github.com/users/exFalso","html_url":"https://github.com/exFalso","followers_url":"https://api.github.com/users/exFalso/followers","following_url":"https://api.github.com/users/exFalso/following{/other_user}","gists_url":"https://api.github.com/users/exFalso/gists{/gist_id}","starred_url":"https://api.github.com/users/exFalso/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/exFalso/subscriptions","organizations_url":"https://api.github.com/users/exFalso/orgs","repos_url":"https://api.github.com/users/exFalso/repos","events_url":"https://api.github.com/users/exFalso/events{/privacy}","received_events_url":"https://api.github.com/users/exFalso/received_events","type":"User","site_admin":false},"body":"Yes, the generated docs are outdated","created_at":"2019-04-16T15:23:51Z","updated_at":"2019-04-16T15:23:52Z","html_url":"https://github.com/corda/corda/pull/4991#discussion_r275856018","pull_request_url":"https://api.github.com/repos/corda/corda/pulls/4991","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/corda/corda/pulls/comments/275856018"},"html":{"href":"https://github.com/corda/corda/pull/4991#discussion_r275856018"},"pull_request":{"href":"https://api.github.com/repos/corda/corda/pulls/4991"}},"in_reply_to_id":274309056},"pull_request":{"url":"https://api.github.com/repos/corda/corda/pulls/4991","id":267846706,"node_id":"MDExOlB1bGxSZXF1ZXN0MjY3ODQ2NzA2","html_url":"https://github.com/corda/corda/pull/4991","diff_url":"https://github.com/corda/corda/pull/4991.diff","patch_url":"https://github.com/corda/corda/pull/4991.patch","issue_url":"https://api.github.com/repos/corda/corda/issues/4991","number":4991,"state":"open","locked":false,"title":"Documentation of flow framework internals","user":{"login":"exFalso","id":1042674,"node_id":"MDQ6VXNlcjEwNDI2NzQ=","avatar_url":"https://avatars0.githubusercontent.com/u/1042674?v=4","gravatar_id":"","url":"https://api.github.com/users/exFalso","html_url":"https://github.com/exFalso","followers_url":"https://api.github.com/users/exFalso/followers","following_url":"https://api.github.com/users/exFalso/following{/other_user}","gists_url":"https://api.github.com/users/exFalso/gists{/gist_id}","starred_url":"https://api.github.com/users/exFalso/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/exFalso/subscriptions","organizations_url":"https://api.github.com/users/exFalso/orgs","repos_url":"https://api.github.com/users/exFalso/repos","events_url":"https://api.github.com/users/exFalso/events{/privacy}","received_events_url":"https://api.github.com/users/exFalso/received_events","type":"User","site_admin":false},"body":"This is by no means complete. Any topics that need expanding?","created_at":"2019-04-05T14:30:21Z","updated_at":"2019-04-16T15:23:52Z","closed_at":null,"merged_at":null,"merge_commit_sha":"ea24f4db2c2ac723b4e1dbba033c0e8ebc7ddac0","assignee":null,"assignees":[],"requested_reviewers":[{"login":"mikehearn","id":971089,"node_id":"MDQ6VXNlcjk3MTA4OQ==","avatar_url":"https://avatars2.githubusercontent.com/u/971089?v=4","gravatar_id":"","url":"https://api.github.com/users/mikehearn","html_url":"https://github.com/mikehearn","followers_url":"https://api.github.com/users/mikehearn/followers","following_url":"https://api.github.com/users/mikehearn/following{/other_user}","gists_url":"https://api.github.com/users/mikehearn/gists{/gist_id}","starred_url":"https://api.github.com/users/mikehearn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mikehearn/subscriptions","organizations_url":"https://api.github.com/users/mikehearn/orgs","repos_url":"https://api.github.com/users/mikehearn/repos","events_url":"https://api.github.com/users/mikehearn/events{/privacy}","received_events_url":"https://api.github.com/users/mikehearn/received_events","type":"User","site_admin":false},{"login":"mnesbit","id":20210228,"node_id":"MDQ6VXNlcjIwMjEwMjI4","avatar_url":"https://avatars2.githubusercontent.com/u/20210228?v=4","gravatar_id":"","url":"https://api.github.com/users/mnesbit","html_url":"https://github.com/mnesbit","followers_url":"https://api.github.com/users/mnesbit/followers","following_url":"https://api.github.com/users/mnesbit/following{/other_user}","gists_url":"https://api.github.com/users/mnesbit/gists{/gist_id}","starred_url":"https://api.github.com/users/mnesbit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mnesbit/subscriptions","organizations_url":"https://api.github.com/users/mnesbit/orgs","repos_url":"https://api.github.com/users/mnesbit/repos","events_url":"https://api.github.com/users/mnesbit/events{/privacy}","received_events_url":"https://api.github.com/users/mnesbit/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/corda/corda/pulls/4991/commits","review_comments_url":"https://api.github.com/repos/corda/corda/pulls/4991/comments","review_comment_url":"https://api.github.com/repos/corda/corda/pulls/comments{/number}","comments_url":"https://api.github.com/repos/corda/corda/issues/4991/comments","statuses_url":"https://api.github.com/repos/corda/corda/statuses/ef0f76deedf75a825ff7c9c04beeb14c52d1ec80","head":{"label":"corda:aslemmer-flow-framework-internals-documentation","ref":"aslemmer-flow-framework-internals-documentation","sha":"ef0f76deedf75a825ff7c9c04beeb14c52d1ec80","user":{"login":"corda","id":22600631,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIyNjAwNjMx","avatar_url":"https://avatars1.githubusercontent.com/u/22600631?v=4","gravatar_id":"","url":"https://api.github.com/users/corda","html_url":"https://github.com/corda","followers_url":"https://api.github.com/users/corda/followers","following_url":"https://api.github.com/users/corda/following{/other_user}","gists_url":"https://api.github.com/users/corda/gists{/gist_id}","starred_url":"https://api.github.com/users/corda/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/corda/subscriptions","organizations_url":"https://api.github.com/users/corda/orgs","repos_url":"https://api.github.com/users/corda/repos","events_url":"https://api.github.com/users/corda/events{/privacy}","received_events_url":"https://api.github.com/users/corda/received_events","type":"Organization","site_admin":false},"repo":{"id":70137417,"node_id":"MDEwOlJlcG9zaXRvcnk3MDEzNzQxNw==","name":"corda","full_name":"corda/corda","private":false,"owner":{"login":"corda","id":22600631,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIyNjAwNjMx","avatar_url":"https://avatars1.githubusercontent.com/u/22600631?v=4","gravatar_id":"","url":"https://api.github.com/users/corda","html_url":"https://github.com/corda","followers_url":"https://api.github.com/users/corda/followers","following_url":"https://api.github.com/users/corda/following{/other_user}","gists_url":"https://api.github.com/users/corda/gists{/gist_id}","starred_url":"https://api.github.com/users/corda/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/corda/subscriptions","organizations_url":"https://api.github.com/users/corda/orgs","repos_url":"https://api.github.com/users/corda/repos","events_url":"https://api.github.com/users/corda/events{/privacy}","received_events_url":"https://api.github.com/users/corda/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/corda/corda","description":"Corda is an open source blockchain project, designed for business from the start. Only Corda allows you to build interoperable blockchain networks that transact in strict privacy. Corda's smart contract technology allows businesses to transact directly, with value.","fork":false,"url":"https://api.github.com/repos/corda/corda","forks_url":"https://api.github.com/repos/corda/corda/forks","keys_url":"https://api.github.com/repos/corda/corda/keys{/key_id}","collaborators_url":"https://api.github.com/repos/corda/corda/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/corda/corda/teams","hooks_url":"https://api.github.com/repos/corda/corda/hooks","issue_events_url":"https://api.github.com/repos/corda/corda/issues/events{/number}","events_url":"https://api.github.com/repos/corda/corda/events","assignees_url":"https://api.github.com/repos/corda/corda/assignees{/user}","branches_url":"https://api.github.com/repos/corda/corda/branches{/branch}","tags_url":"https://api.github.com/repos/corda/corda/tags","blobs_url":"https://api.github.com/repos/corda/corda/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/corda/corda/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/corda/corda/git/refs{/sha}","trees_url":"https://api.github.com/repos/corda/corda/git/trees{/sha}","statuses_url":"https://api.github.com/repos/corda/corda/statuses/{sha}","languages_url":"https://api.github.com/repos/corda/corda/languages","stargazers_url":"https://api.github.com/repos/corda/corda/stargazers","contributors_url":"https://api.github.com/repos/corda/corda/contributors","subscribers_url":"https://api.github.com/repos/corda/corda/subscribers","subscription_url":"https://api.github.com/repos/corda/corda/subscription","commits_url":"https://api.github.com/repos/corda/corda/commits{/sha}","git_commits_url":"https://api.github.com/repos/corda/corda/git/commits{/sha}","comments_url":"https://api.github.com/repos/corda/corda/comments{/number}","issue_comment_url":"https://api.github.com/repos/corda/corda/issues/comments{/number}","contents_url":"https://api.github.com/repos/corda/corda/contents/{+path}","compare_url":"https://api.github.com/repos/corda/corda/compare/{base}...{head}","merges_url":"https://api.github.com/repos/corda/corda/merges","archive_url":"https://api.github.com/repos/corda/corda/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/corda/corda/downloads","issues_url":"https://api.github.com/repos/corda/corda/issues{/number}","pulls_url":"https://api.github.com/repos/corda/corda/pulls{/number}","milestones_url":"https://api.github.com/repos/corda/corda/milestones{/number}","notifications_url":"https://api.github.com/repos/corda/corda/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/corda/corda/labels{/name}","releases_url":"https://api.github.com/repos/corda/corda/releases{/id}","deployments_url":"https://api.github.com/repos/corda/corda/deployments","created_at":"2016-10-06T08:46:29Z","updated_at":"2019-04-16T14:31:20Z","pushed_at":"2019-04-16T15:17:32Z","git_url":"git://github.com/corda/corda.git","ssh_url":"git@github.com:corda/corda.git","clone_url":"https://github.com/corda/corda.git","svn_url":"https://github.com/corda/corda","homepage":"https://www.corda.net","size":178688,"stargazers_count":2817,"watchers_count":2817,"language":"Kotlin","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":738,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":130,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":738,"open_issues":130,"watchers":2817,"default_branch":"master"}},"base":{"label":"corda:master","ref":"master","sha":"2f625c7dc7242c4138a5dcc4cbdcd88875c12bd5","user":{"login":"corda","id":22600631,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIyNjAwNjMx","avatar_url":"https://avatars1.githubusercontent.com/u/22600631?v=4","gravatar_id":"","url":"https://api.github.com/users/corda","html_url":"https://github.com/corda","followers_url":"https://api.github.com/users/corda/followers","following_url":"https://api.github.com/users/corda/following{/other_user}","gists_url":"https://api.github.com/users/corda/gists{/gist_id}","starred_url":"https://api.github.com/users/corda/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/corda/subscriptions","organizations_url":"https://api.github.com/users/corda/orgs","repos_url":"https://api.github.com/users/corda/repos","events_url":"https://api.github.com/users/corda/events{/privacy}","received_events_url":"https://api.github.com/users/corda/received_events","type":"Organization","site_admin":false},"repo":{"id":70137417,"node_id":"MDEwOlJlcG9zaXRvcnk3MDEzNzQxNw==","name":"corda","full_name":"corda/corda","private":false,"owner":{"login":"corda","id":22600631,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIyNjAwNjMx","avatar_url":"https://avatars1.githubusercontent.com/u/22600631?v=4","gravatar_id":"","url":"https://api.github.com/users/corda","html_url":"https://github.com/corda","followers_url":"https://api.github.com/users/corda/followers","following_url":"https://api.github.com/users/corda/following{/other_user}","gists_url":"https://api.github.com/users/corda/gists{/gist_id}","starred_url":"https://api.github.com/users/corda/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/corda/subscriptions","organizations_url":"https://api.github.com/users/corda/orgs","repos_url":"https://api.github.com/users/corda/repos","events_url":"https://api.github.com/users/corda/events{/privacy}","received_events_url":"https://api.github.com/users/corda/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/corda/corda","description":"Corda is an open source blockchain project, designed for business from the start. Only Corda allows you to build interoperable blockchain networks that transact in strict privacy. Corda's smart contract technology allows businesses to transact directly, with value.","fork":false,"url":"https://api.github.com/repos/corda/corda","forks_url":"https://api.github.com/repos/corda/corda/forks","keys_url":"https://api.github.com/repos/corda/corda/keys{/key_id}","collaborators_url":"https://api.github.com/repos/corda/corda/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/corda/corda/teams","hooks_url":"https://api.github.com/repos/corda/corda/hooks","issue_events_url":"https://api.github.com/repos/corda/corda/issues/events{/number}","events_url":"https://api.github.com/repos/corda/corda/events","assignees_url":"https://api.github.com/repos/corda/corda/assignees{/user}","branches_url":"https://api.github.com/repos/corda/corda/branches{/branch}","tags_url":"https://api.github.com/repos/corda/corda/tags","blobs_url":"https://api.github.com/repos/corda/corda/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/corda/corda/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/corda/corda/git/refs{/sha}","trees_url":"https://api.github.com/repos/corda/corda/git/trees{/sha}","statuses_url":"https://api.github.com/repos/corda/corda/statuses/{sha}","languages_url":"https://api.github.com/repos/corda/corda/languages","stargazers_url":"https://api.github.com/repos/corda/corda/stargazers","contributors_url":"https://api.github.com/repos/corda/corda/contributors","subscribers_url":"https://api.github.com/repos/corda/corda/subscribers","subscription_url":"https://api.github.com/repos/corda/corda/subscription","commits_url":"https://api.github.com/repos/corda/corda/commits{/sha}","git_commits_url":"https://api.github.com/repos/corda/corda/git/commits{/sha}","comments_url":"https://api.github.com/repos/corda/corda/comments{/number}","issue_comment_url":"https://api.github.com/repos/corda/corda/issues/comments{/number}","contents_url":"https://api.github.com/repos/corda/corda/contents/{+path}","compare_url":"https://api.github.com/repos/corda/corda/compare/{base}...{head}","merges_url":"https://api.github.com/repos/corda/corda/merges","archive_url":"https://api.github.com/repos/corda/corda/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/corda/corda/downloads","issues_url":"https://api.github.com/repos/corda/corda/issues{/number}","pulls_url":"https://api.github.com/repos/corda/corda/pulls{/number}","milestones_url":"https://api.github.com/repos/corda/corda/milestones{/number}","notifications_url":"https://api.github.com/repos/corda/corda/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/corda/corda/labels{/name}","releases_url":"https://api.github.com/repos/corda/corda/releases{/id}","deployments_url":"https://api.github.com/repos/corda/corda/deployments","created_at":"2016-10-06T08:46:29Z","updated_at":"2019-04-16T14:31:20Z","pushed_at":"2019-04-16T15:17:32Z","git_url":"git://github.com/corda/corda.git","ssh_url":"git@github.com:corda/corda.git","clone_url":"https://github.com/corda/corda.git","svn_url":"https://github.com/corda/corda","homepage":"https://www.corda.net","size":178688,"stargazers_count":2817,"watchers_count":2817,"language":"Kotlin","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":738,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":130,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":738,"open_issues":130,"watchers":2817,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/corda/corda/pulls/4991"},"html":{"href":"https://github.com/corda/corda/pull/4991"},"issue":{"href":"https://api.github.com/repos/corda/corda/issues/4991"},"comments":{"href":"https://api.github.com/repos/corda/corda/issues/4991/comments"},"review_comments":{"href":"https://api.github.com/repos/corda/corda/pulls/4991/comments"},"review_comment":{"href":"https://api.github.com/repos/corda/corda/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/corda/corda/pulls/4991/commits"},"statuses":{"href":"https://api.github.com/repos/corda/corda/statuses/ef0f76deedf75a825ff7c9c04beeb14c52d1ec80"}},"author_association":"CONTRIBUTOR"}}
other: {"actor":{"display_login":"exFalso"}}