@@ -716,11 +716,180 @@ Now you know how to use the Tangle for data storage while keeping privacy.
716
716
When you need more granular access control on how and when one could read
717
717
data from the Tangle, consider using `Masked Authenticated Messaging `_ (MAM).
718
718
719
+ 8. Send and Monitor Concurrently
720
+ --------------------------------
721
+
722
+ In this example, you will learn how to:
723
+
724
+ - **Use the asynchronous PyOTA API. **
725
+ - **Send transactions concurrently. **
726
+ - **Monitor confirmation of transactions concurrently. **
727
+ - **Execute arbitrary code concurrently while doing the former two. **
728
+
729
+ .. warning ::
730
+
731
+ If you are new to `coroutines `_ and asynchronous programming in Python, it
732
+ is strongly recommended that you check out this `article `_ and the official
733
+ `asyncio `_ documentation before proceeding.
734
+
735
+ Code
736
+ ~~~~
737
+ .. literalinclude :: ../examples/tutorials/08_async_send_monitor.py
738
+ :linenos:
739
+
740
+ Discussion
741
+ ~~~~~~~~~~
742
+ This example is divided into 4 logical parts:
743
+
744
+ 1. Imports and constant declarations
745
+ 2. `Coroutine `_ to send and monitor a list of transactions as a bundle.
746
+ 3. `Coroutine `_ to execute arbitrary code concurrently.
747
+ 4. A main `coroutine `_ to schedule the execution of our application.
748
+
749
+ Let's start with the most simple one: **Imports and Constants **.
750
+
751
+ .. literalinclude :: ../examples/tutorials/08_async_send_monitor.py
752
+ :lines: 1-17
753
+ :lineno-start: 1
754
+
755
+ Notice, that we import the :py:class: `AsyncIota ` api class, because we
756
+ would like to use the asynchronous and concurrent features of PyOTA.
757
+ :py:class: `List ` from the :py:class: `typing ` library is needed for correct
758
+ type annotations, and we also import the `asyncio `_ library. This will come
759
+ handy when we want to schedule and run the coroutines.
760
+
761
+ On line 6, we instantiate an asynchronous IOTA API. Functionally, it does the
762
+ same operations as :py:class: `Iota `, but the api calls are defined as
763
+ coroutines. For this tutorial, we connect to a devnet node, and explicitly tell
764
+ this as well to the api on line 8.
765
+
766
+ On line 12, we declare an IOTA address. We will send our zero value transactions
767
+ to this address. Feel free to change it to your own address.
768
+
769
+ Once we have sent the transactions, we start monitoring their confirmation by the
770
+ network. Confirmation time depends on current network activity, the referenced
771
+ tips, etc., therefore we set a ``timeout `` of 120 seconds on line 15. You might
772
+ have to modify this value later to see the confirmation of your transactions.
773
+
774
+ You can also fine-tune the example code by tinkering with ``polling_interval ``.
775
+ This is the interval between two subsequent confirmation checks.
776
+
777
+ Let's move on to the next block, namely the **send and monitor coroutine **.
778
+
779
+ .. literalinclude :: ../examples/tutorials/08_async_send_monitor.py
780
+ :lines: 20-62
781
+ :lineno-start: 20
782
+
783
+ Notice, that coroutines are defined in python by the ``async def `` keywords.
784
+ This makes them `awaitable `_.
785
+
786
+ From the type annotations, we see that :py:meth: `send_and_monitor ` accepts a
787
+ list of :py:class: `ProposedTransaction ` objects and return a ``bool ``.
788
+
789
+ On line 28, we send the transfers with the help of
790
+ :py:meth: `AsyncIota.send_transfer `. Since this is not a regular method, but a
791
+ coroutine, we have to ``await `` its result. :py:meth: `AsyncIota.send_transfer `
792
+ takes care of building the bundle, doing proof-of-work and sending the
793
+ transactions within the bundle to the network.
794
+
795
+ Once we sent the transfer, we collect individual transaction hashes from the
796
+ bundle, which we will use for confirmation checking.
797
+
798
+ On line 39, the so called confirmation checking starts. With the help of
799
+ :py:meth: `AsyncIota.get_inclusion_states `, we determine if our transactions
800
+ have been confirmed by the network. The ``None `` value for the ``tips ``
801
+ parameter in the argument list basically means that check against the latest
802
+ milestone.
803
+
804
+ On line 43, we iterate over our original ``sent_tx_hashes `` list of sent
805
+ transaction hashes and ``git_response['states'] ``, which is a list of ``bool ``
806
+ values, at the same time using the built in `zip `_ method. We also employ
807
+ `enumerate `_, because we need the index of the elements in each iteration.
808
+
809
+ If a transaction is confirmed, we delete the corresponding elements from the
810
+ lists. When all transactions are confirmed, ``sent_tx_hashes `` becomes empty,
811
+ and the loop condition becomes ``False ``.
812
+
813
+ If however, not all transactions have been confirmed, we should continue
814
+ checking for confirmation. Observe line 58, where we suspend the coroutine
815
+ with :py:meth: `asyncio.sleep ` for ``polling_interval `` seconds. Awaiting the
816
+ result of :py:meth: `asyncio.sleep ` will cause our coroutine to continue
817
+ execution in ``polling_interval `` time. While our coroutine is sleeping,
818
+ other coroutines can run concurrently, hence it is a non-blocking call.
819
+
820
+ To do something in the meantime, we can **execute another coroutine concurrently **:
821
+
822
+ .. literalinclude :: ../examples/tutorials/08_async_send_monitor.py
823
+ :lines: 65-71
824
+ :lineno-start: 65
825
+
826
+ This is really just a dummy coroutine that prints something to the terminal and
827
+ then goes to sleep periodically, but in a real application, you could do
828
+ meaningful tasks here.
829
+
830
+ Now let's look at how to **schedule the execution of our application with the
831
+ main coroutine **:
832
+
833
+ .. literalinclude :: ../examples/tutorials/08_async_send_monitor.py
834
+ :lines: 74-115
835
+ :lineno-start: 74
836
+
837
+ First, we declare a list of :py:meth: `ProposedTransaction ` objects, that will
838
+ be the input for our :py:meth: `send_and_monitor ` coroutine.
839
+
840
+ The important stuff begins on line 101. We use :py:meth: `asyncio.gather ` to
841
+ submit our coroutines for execution, wait for their results and then return
842
+ them in a list. `gather `_ takes our coroutines, transforms them into runnable
843
+ `tasks `_, and runs them concurrently.
844
+
845
+ Notice, that we listed :py:meth: `send_and_monitor ` twice in
846
+ :py:meth: `asyncio.gather ` with the same list of :py:meth: `ProposedTransaction `
847
+ objects. This is to showcase how you can send and monitor multiple transfers
848
+ concurrently. In this example, two different bundles will be created from the
849
+ same :py:meth: `ProposedTransaction ` objects. The two bundles post zero value
850
+ transactions to the same address, contain the same messages respectively,
851
+ but are not dependent on each other in any way. That is why we can send them
852
+ concurrently.
853
+
854
+ As discussed previously, ``result `` will be a list of results of the coroutines
855
+ submitted to :py:meth: `asyncio.gather `, preserving their order.
856
+ ``result[0] `` is the result from the first :py:meth: `send_and_monitor `, and
857
+ ``result[1] `` is the result from the second :py:meth: `send_and_monitor ` from the
858
+ argument list. If any of these are ``False ``, confirmation did not happen
859
+ before ``timeout ``.
860
+
861
+ When you see the message from line 109 in your terminal, try increasing
862
+ ``timeout ``, or check the status of the network, maybe there is a temporary
863
+ downtime on the devnet due to maintenance.
864
+
865
+ Lastly, observe lines 113-115. If the current file (python module) is run
866
+ from the terminal, we use :py:meth: `ayncio.run ` to execute the main coroutine
867
+ inside an `event loop `_.
868
+
869
+ To run this example, navigate to ``examples/tutorial `` inside the cloned
870
+ PyOTA repository, or download the source file of `Tutorial 8 from GitHub `_
871
+ and run the following in a terminal:
872
+
873
+ .. code-block :: sh
874
+
875
+ $ python 08_async_send_monitor.py
876
+
719
877
.. _PyOTA Bug Tracker : https://github.com/iotaledger/iota.py/issues
720
878
.. _bytestring : https://docs.python.org/3/library/stdtypes.html#bytes
721
879
.. _tryte alphabet : https://docs.iota.org/docs/getting-started/0.1/introduction/ternary#tryte-encoding
722
880
.. _Tangle Explorer : https://utils.iota.org
723
881
.. _Account Module : https://docs.iota.org/docs/client-libraries/0.1/account-module/introduction/overview
724
882
.. _spending twice from the same address : https://docs.iota.org/docs/getting-started/0.1/clients/addresses#spent-addresses
725
883
.. _Base64 : https://en.wikipedia.org/wiki/Base64
726
- .. _Masked Authenticated Messaging : https://docs.iota.org/docs/client-libraries/0.1/mam/introduction/overview?q=masked%20auth&highlights=author;authent
884
+ .. _Masked Authenticated Messaging : https://docs.iota.org/docs/client-libraries/0.1/mam/introduction/overview?q=masked%20auth&highlights=author;authent
885
+ .. _coroutine : https://docs.python.org/3/glossary.html#term-coroutine
886
+ .. _coroutines : https://docs.python.org/3/glossary.html#term-coroutine
887
+ .. _asyncio : https://docs.python.org/3/library/asyncio.html
888
+ .. _article : https://realpython.com/async-io-python/
889
+ .. _awaitable : https://docs.python.org/3/library/asyncio-task.html#awaitables
890
+ .. _zip : https://docs.python.org/3.3/library/functions.html#zip
891
+ .. _enumerate : https://docs.python.org/3.3/library/functions.html#enumerate
892
+ .. _gather : https://docs.python.org/3/library/asyncio-task.html#running-tasks-concurrently
893
+ .. _tasks : https://docs.python.org/3/library/asyncio-task.html#asyncio.Task
894
+ .. _event loop : https://docs.python.org/3/library/asyncio-eventloop.html
895
+ .. _Tutorial 8 from GitHub : https://github.com/iotaledger/iota.py/blob/master/examples/tutorials/08_async_send_monitor.py
0 commit comments