From f9188f73f19ee7b279e9616ddbaaf3e9eb7fc12b Mon Sep 17 00:00:00 2001 From: Daira Emma Hopwood Date: Tue, 17 Oct 2023 19:27:53 +0100 Subject: [PATCH 01/15] Use `_name` for attributes that are intended to be private. Signed-off-by: Daira Emma Hopwood --- simtfl/node.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/simtfl/node.py b/simtfl/node.py index 9a9f577..d84ce4b 100644 --- a/simtfl/node.py +++ b/simtfl/node.py @@ -56,16 +56,16 @@ def __init__(self, ident, env, network): Constructs a SequentialNode with the given simpy Environment and network. """ super().__init__(ident, env, network) - self.mailbox = deque() - self.wakeup = env.event() + self._mailbox = deque() + self._wakeup = env.event() def receive(self, sender, message): """ (process) Add incoming messages to the mailbox. """ - self.mailbox.append((sender, message)) + self._mailbox.append((sender, message)) try: - self.wakeup.succeed() + self._wakeup.succeed() except RuntimeError: pass return skip() @@ -78,12 +78,12 @@ def run(self): implementation. """ while True: - while len(self.mailbox) > 0: - (sender, message) = self.mailbox.popleft() + while len(self._mailbox) > 0: + (sender, message) = self._mailbox.popleft() print(f"T{self.env.now:5d}: handling {sender:2d} -> {self.ident:2d}: {message}") yield from self.handle(sender, message) # This naive implementation is fine because we have no actual # concurrency. - self.wakeup = self.env.event() - yield self.wakeup + self._wakeup = self.env.event() + yield self._wakeup From 67de1f9841a3c91d5e007ea3e9ad787b5eac490c Mon Sep 17 00:00:00 2001 From: Daira Emma Hopwood Date: Wed, 18 Oct 2023 11:25:58 +0100 Subject: [PATCH 02/15] Move documentation into a `doc/` directory. Signed-off-by: Daira Emma Hopwood --- README.md | 21 +++------------------ doc/patterns.md | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 18 deletions(-) create mode 100644 doc/patterns.md diff --git a/README.md b/README.md index 4a88c95..8894063 100644 --- a/README.md +++ b/README.md @@ -23,26 +23,11 @@ Note the caveats: *experimental*, *simulator*, *research*, *potential*. poetry run demo -## Programming patterns +## Documentation -The code makes use of the [simpy](https://simpy.readthedocs.io/en/latest/) -discrete event simulation library. This means that functions representing -processes are implemented as generators, so that the library can simulate -timeouts and asynchronous communication (typically faster than real time). +Design documentation is under the `doc/` directory: -We use the convention of putting "(process)" in the doc comment of these -functions. They either must use the `yield` construct, *or* return the -result of calling another "(process)" function (not both). - -Objects that implement processes typically hold the `simpy.Environment` in -an instance variable `self.env`. - -To wait for another process `f()` before continuing, use `yield from f()`. -(If it is the last thing to do in a function with no other `yield` -statements, `return f()` can be used as an optimization.) - -A "(process)" function that does nothing should `return skip()`, using -`simtfl.util.skip`. +* [Programming patterns for use of simpy](doc/patterns.md). ## Contributing diff --git a/doc/patterns.md b/doc/patterns.md new file mode 100644 index 0000000..b8831eb --- /dev/null +++ b/doc/patterns.md @@ -0,0 +1,20 @@ +# Programming patterns + +The code makes use of the [simpy](https://simpy.readthedocs.io/en/latest/) +discrete event simulation library. This means that functions representing +processes are implemented as generators, so that the library can simulate +timeouts and asynchronous communication (typically faster than real time). + +We use the convention of putting "(process)" in the doc comment of these +functions. They either must use the `yield` construct, *or* return the +result of calling another "(process)" function (not both). + +Objects that implement processes typically hold the `simpy.Environment` in +an instance variable `self.env`. + +To wait for another process `f()` before continuing, use `yield from f()`. +(If it is the last thing to do in a function with no other `yield` +statements, `return f()` can be used as an optimization.) + +A "(process)" function that does nothing should `return skip()`, using +`simtfl.util.skip`. From ffc2ecbc64d780b9d81e7ad0230338e7447d5e16 Mon Sep 17 00:00:00 2001 From: Daira Emma Hopwood Date: Thu, 19 Oct 2023 19:02:51 +0100 Subject: [PATCH 03/15] Add `check.sh` for convenience of running flake8 and tests before submitting a PR. Signed-off-by: Daira Emma Hopwood --- README.md | 9 ++++++++- check.sh | 12 ++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100755 check.sh diff --git a/README.md b/README.md index 8894063..34d8fc8 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,14 @@ Design documentation is under the `doc/` directory: ## Contributing -Please check `poetry run flake8` before submitting a PR. +Please use `./check.sh` before submitting a PR. This currently runs `flake8` +and the unit tests locally. + +You can use `./check.sh -k ` to run `flake8` and then only tests +with names matching the given substring. This will not suppress output to +stdout or stderr (but `./check.sh -bk ` will). + +To see other options for running unit tests, use `poetry run python -m unittest -h`. ## License diff --git a/check.sh b/check.sh new file mode 100755 index 0000000..9db28b2 --- /dev/null +++ b/check.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +set -eu +cd -P -- "$(dirname -- "$(command -v -- "$0")")" + +echo Running flake8... +poetry run flake8 + +echo +echo Running unit tests... +args="${*:---buffer}" +poetry run python -m unittest discover -s simtfl -t . -p '[a-z]*.py' --verbose ${args} From c7e29c924f239de31caf5b85e8af617584f53570 Mon Sep 17 00:00:00 2001 From: Daira Emma Hopwood Date: Thu, 19 Oct 2023 18:58:09 +0100 Subject: [PATCH 04/15] Minor refactoring to make the use of `Network` cleaner. Signed-off-by: Daira Emma Hopwood --- simtfl/demo.py | 13 ++++--------- simtfl/message.py | 14 ++++++-------- simtfl/network.py | 20 +++++++++++++++++++- simtfl/node.py | 36 +++++++++++++++++++++++++----------- simtfl/util.py | 1 + 5 files changed, 55 insertions(+), 29 deletions(-) diff --git a/simtfl/demo.py b/simtfl/demo.py index 480d2db..de16453 100644 --- a/simtfl/demo.py +++ b/simtfl/demo.py @@ -58,14 +58,9 @@ def run(): """ Runs the demo. """ - env = Environment() - network = Network(env, delay=4) + network = Network(Environment(), delay=4) for i in range(10): - network.add_node(PongNode(i, env, network)) + network.add_node(PongNode()) - network.add_node(PingNode(10, env, network)) - - for i in range(network.num_nodes()): - env.process(network.start_node(i)) - - env.run() + network.add_node(PingNode()) + network.run_all() diff --git a/simtfl/message.py b/simtfl/message.py index a74b2a1..45ba707 100644 --- a/simtfl/message.py +++ b/simtfl/message.py @@ -1,12 +1,10 @@ +from dataclasses import dataclass +from typing import Any + + +@dataclass(frozen=True) class PayloadMessage: """ A message with an arbitrary payload. """ - def __init__(self, payload): - """ - Constructs a `PayloadMessage` with the given payload. - """ - self.payload = payload - - def __str__(self): - return f"{self.__class__.__name__}({self.payload})" + payload: Any diff --git a/simtfl/network.py b/simtfl/network.py index a29e7cc..8a1c920 100644 --- a/simtfl/network.py +++ b/simtfl/network.py @@ -30,16 +30,34 @@ def add_node(self, node): """ Adds a node with the next available ident. """ + ident = self.num_nodes() self.nodes.append(node) + node.initialize(ident, self.env, self) def start_node(self, ident): """ (process) Start the node with the given ident. """ node = self.node(ident) - print(f"T{self.env.now:5d}: starting {node}") + print(f"T{self.env.now:5d}: starting {ident:2d}: {node}") return node.run() + def start_processes(self): + """ + Start a process for each node. + """ + print() + for i in range(self.num_nodes()): + self.env.process(self.start_node(i)) + + def run_all(self, *args, **kwargs): + """ + Convenience method to start a process for each node, then start + the simulation. Takes the same arguments as `simpy.Environment.run`. + """ + self.start_processes() + self.env.run(*args, **kwargs) + def send(self, sender, target, message, delay=None): """ (process) Sends a message to the node with ident `target`, from the node diff --git a/simtfl/node.py b/simtfl/node.py index d84ce4b..2a90855 100644 --- a/simtfl/node.py +++ b/simtfl/node.py @@ -1,42 +1,45 @@ -from .util import skip from collections import deque +from .util import skip + class PassiveNode: """ A node that sends no messages and does nothing with received messages. This class is intended to be subclassed. """ - def __init__(self, ident, env, network): + def initialize(self, ident, env, network): """ - Constructs a PassiveNode with the given simpy Environment and network. + Initializes a PassiveNode with the given ident, simpy Environment, + and network. Nodes are initialized when they are added to a network. """ self.ident = ident self.env = env self.network = network def __str__(self): - return f"{self.ident:2d}: {self.__class__.__name__}" + return f"{self.__class__.__name__}" def send(self, target, message): """ (process) This method can be overridden to intercept messages being sent - by this node. It should typically call `self.network.send`. + by this node. The implementation in this class calls `self.network.send`. """ return self.network.send(self.ident, target, message) def receive(self, sender, message): """ (process) This method can be overridden to intercept messages being received - by this node. It should typically call `self.handle`. + by this node. The implementation in this class calls `self.handle`. """ return self.handle(sender, message) - def handle(self, message, sender): + def handle(self, sender, message): """ (process) Handles a message by doing nothing. Note that the handling of each message, and the `run` method, are in separate simpy processes. That - is, yielding here will not block other incoming messages. + is, yielding here will not block other incoming messages for a direct + subclass of `PassiveNode`. """ return skip() @@ -51,11 +54,11 @@ class SequentialNode(PassiveNode): """ A node that processes messages sequentially. """ - def __init__(self, ident, env, network): + def initialize(self, ident, env, network): """ - Constructs a SequentialNode with the given simpy Environment and network. + Initializes a SequentialNode with the given simpy Environment and network. """ - super().__init__(ident, env, network) + super().initialize(ident, env, network) self._mailbox = deque() self._wakeup = env.event() @@ -70,6 +73,16 @@ def receive(self, sender, message): pass return skip() + def handle(self, sender, message): + """ + (process) Handles a message by doing nothing. Messages are handled + sequentially; that is, handling of the next message will be blocked + on this process. + """ + # This is the same implementation as `PassiveNode`, but the documentation + # is different. + return skip() + def run(self): """ (process) Repeatedly handle incoming messages. @@ -87,3 +100,4 @@ def run(self): # concurrency. self._wakeup = self.env.event() yield self._wakeup + diff --git a/simtfl/util.py b/simtfl/util.py index 734d65b..417b6f4 100644 --- a/simtfl/util.py +++ b/simtfl/util.py @@ -2,6 +2,7 @@ Utilities. """ + def skip(): """ (process) Does nothing. From f971c8b299c0182cb8778d2aab6cd396132dc2d6 Mon Sep 17 00:00:00 2001 From: Daira Emma Hopwood Date: Thu, 19 Oct 2023 19:08:12 +0100 Subject: [PATCH 05/15] Add tests for message-passing framework. Signed-off-by: Daira Emma Hopwood --- .flake8 | 8 ++++++- simtfl/node.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/.flake8 b/.flake8 index c44c3bf..23056d4 100644 --- a/.flake8 +++ b/.flake8 @@ -1,4 +1,10 @@ [flake8] exclude = .git, __pycache__ -ignore = E302 + +# Justifications for each ignored error or warning: +# * E302: always requiring two blank lines rather than one between top-level items is too annoying and nitpicky. +# * E402: we want imports for tests to go between the main code and the tests. +# * W503, W504: these give false positives for the style of avoiding \ at the end of each line by using parens. +ignore = E302, E402, W503, W504 + max-line-length = 120 diff --git a/simtfl/node.py b/simtfl/node.py index 2a90855..8af3adb 100644 --- a/simtfl/node.py +++ b/simtfl/node.py @@ -101,3 +101,62 @@ def run(self): self._wakeup = self.env.event() yield self._wakeup + +__all__ = ['PassiveNode', 'SequentialNode'] + +from simpy import Environment +import unittest + +from .message import PayloadMessage +from .network import Network + + +class PassiveReceiverTestNode(PassiveNode): + def __init__(self): + super().__init__() + self.received = deque() + + def handle(self, sender, message): + self.received.append((sender, message, self.env.now)) + yield self.env.timeout(3) + + +class SequentialReceiverTestNode(SequentialNode): + def __init__(self): + super().__init__() + self.received = deque() + + def handle(self, sender, message): + self.received.append((sender, message, self.env.now)) + yield self.env.timeout(3) + + +class SenderTestNode(PassiveNode): + def run(self): + for i in range(3): + yield from self.send(0, PayloadMessage(i)) + yield self.env.timeout(1) + + +class TestFramework(unittest.TestCase): + def _test_node(self, receiver_node, expected): + network = Network(Environment()) + network.add_node(receiver_node) + network.add_node(SenderTestNode()) + network.run_all() + + self.assertEqual(list(network.node(0).received), expected) + + def test_passive_node(self): + self._test_node(PassiveReceiverTestNode(), [ + (1, PayloadMessage(0), 1), + (1, PayloadMessage(1), 2), + (1, PayloadMessage(2), 3), + ]) + + def test_sequential_node(self): + self._test_node(SequentialReceiverTestNode(), [ + (1, PayloadMessage(0), 1), + (1, PayloadMessage(1), 4), + (1, PayloadMessage(2), 7), + ]) From 44fc6685a5c66250c092fdfb025204fa87db5002 Mon Sep 17 00:00:00 2001 From: Daira Emma Hopwood Date: Thu, 19 Oct 2023 19:06:19 +0100 Subject: [PATCH 06/15] Run tests in CI. Signed-off-by: Daira Emma Hopwood --- .github/workflows/flake8.yml | 1 + .github/workflows/tests.yml | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/flake8.yml b/.github/workflows/flake8.yml index 0e693a2..6c3d4ce 100644 --- a/.github/workflows/flake8.yml +++ b/.github/workflows/flake8.yml @@ -10,6 +10,7 @@ jobs: - uses: actions/checkout@v2 - name: Install gnome-keyring + # https://github.com/python-poetry/poetry/issues/2692 run: sudo apt-get install gnome-keyring - name: Install poetry diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..3241105 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,24 @@ +name: tests + +on: pull_request + +jobs: + verify: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Install gnome-keyring + # https://github.com/python-poetry/poetry/issues/2692 + run: sudo apt-get install gnome-keyring + + - name: Install poetry + run: pip install --user poetry + + - name: Install dependencies + run: poetry install --no-root + + - name: Run tests + # -p '[a-z]*.py' avoids running tests from __init__.py files twice. + run: poetry run python -m unittest discover -s simtfl -t . -p '[a-z]*.py' --verbose --buffer From df251635bf25260cb2b4837353013c2452af6f32 Mon Sep 17 00:00:00 2001 From: Daira Emma Hopwood Date: Thu, 19 Oct 2023 19:54:53 +0100 Subject: [PATCH 07/15] Add references for error and warning codes in .flake8 Signed-off-by: Daira Emma Hopwood --- .flake8 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.flake8 b/.flake8 index 23056d4..82316f1 100644 --- a/.flake8 +++ b/.flake8 @@ -5,6 +5,10 @@ exclude = .git, __pycache__ # * E302: always requiring two blank lines rather than one between top-level items is too annoying and nitpicky. # * E402: we want imports for tests to go between the main code and the tests. # * W503, W504: these give false positives for the style of avoiding \ at the end of each line by using parens. +# +# References: +# - https://flake8.pycqa.org/en/latest/user/error-codes.html +# - https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes ignore = E302, E402, W503, W504 max-line-length = 120 From d8c282c73c17a1ac7b904a4041ec32da21e6bdf3 Mon Sep 17 00:00:00 2001 From: Daira Emma Hopwood Date: Thu, 19 Oct 2023 21:57:58 +0100 Subject: [PATCH 08/15] More documentation. Signed-off-by: Daira Emma Hopwood --- simtfl/__init__.py | 7 +++++++ simtfl/message.py | 5 +++++ simtfl/network.py | 4 ++++ simtfl/node.py | 26 ++++++++++++++++++++------ 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/simtfl/__init__.py b/simtfl/__init__.py index e69de29..d797bac 100644 --- a/simtfl/__init__.py +++ b/simtfl/__init__.py @@ -0,0 +1,7 @@ +""" +This is an experimental simulator for research into a potential [Trailing Finality +Layer](https://electriccoin.co/blog/the-trailing-finality-layer-a-stepping-stone-to-proof-of-stake-in-zcash/) +for Zcash. + +See the [README](../README.md) for more information. +""" diff --git a/simtfl/message.py b/simtfl/message.py index 45ba707..3755ef8 100644 --- a/simtfl/message.py +++ b/simtfl/message.py @@ -1,3 +1,7 @@ +""" +Base classes for messages. +""" + from dataclasses import dataclass from typing import Any @@ -8,3 +12,4 @@ class PayloadMessage: A message with an arbitrary payload. """ payload: Any + """The payload.""" diff --git a/simtfl/network.py b/simtfl/network.py index 8a1c920..b5e17d4 100644 --- a/simtfl/network.py +++ b/simtfl/network.py @@ -1,3 +1,7 @@ +""" +Framework for message passing in a network of nodes. +""" + from .util import skip diff --git a/simtfl/node.py b/simtfl/node.py index 8af3adb..ab90ca2 100644 --- a/simtfl/node.py +++ b/simtfl/node.py @@ -1,3 +1,7 @@ +""" +Base classes for node implementations. +""" + from collections import deque from .util import skip @@ -5,13 +9,21 @@ class PassiveNode: """ - A node that sends no messages and does nothing with received messages. - This class is intended to be subclassed. + A node that processes messages concurrently. By default it sends no + messages and does nothing with received messages. This class is + intended to be subclassed. + + Inherit from this class directly if all messages are to be processed + concurrently without blocking. If messages are to be processed + sequentially, it may be easier to inherit from `SequentialNode`. + + Note that the simulation is deterministic regardless of which option + is selected. """ def initialize(self, ident, env, network): """ - Initializes a PassiveNode with the given ident, simpy Environment, - and network. Nodes are initialized when they are added to a network. + Initializes a `PassiveNode` with the given ident, `simpy.Environment`, + and `Network`. Nodes are initialized when they are added to a `Network`. """ self.ident = ident self.env = env @@ -52,11 +64,13 @@ def run(self): class SequentialNode(PassiveNode): """ - A node that processes messages sequentially. + A node that processes messages sequentially. By default it sends no + messages and does nothing with received messages. This class is + intended to be subclassed. """ def initialize(self, ident, env, network): """ - Initializes a SequentialNode with the given simpy Environment and network. + Initializes a `SequentialNode` with the given `simpy.Environment` and `Network`. """ super().initialize(ident, env, network) self._mailbox = deque() From 70c9ee523d1b4938a92e8816ebc4264fd513b646 Mon Sep 17 00:00:00 2001 From: Daira Emma Hopwood Date: Thu, 19 Oct 2023 22:51:09 +0100 Subject: [PATCH 09/15] Add dependencies and script for generating API documentation. Signed-off-by: Daira Emma Hopwood --- README.md | 3 ++ gendoc.sh | 5 ++ poetry.lock | 131 +++++++++++++++++++++++++++++++++++++++++++++++-- pyproject.toml | 1 + 4 files changed, 136 insertions(+), 4 deletions(-) create mode 100755 gendoc.sh diff --git a/README.md b/README.md index 34d8fc8..c5aabeb 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,9 @@ Design documentation is under the `doc/` directory: * [Programming patterns for use of simpy](doc/patterns.md). +You can also generate API documentation by running `./gendoc.sh`. +The starting point for the generated documentation is . + ## Contributing Please use `./check.sh` before submitting a PR. This currently runs `flake8` diff --git a/gendoc.sh b/gendoc.sh new file mode 100755 index 0000000..b0def4d --- /dev/null +++ b/gendoc.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -eu + +poetry run pdoc simtfl -o apidoc --no-include-undocumented -d markdown diff --git a/poetry.lock b/poetry.lock index e96b157..c12b531 100644 --- a/poetry.lock +++ b/poetry.lock @@ -17,6 +17,94 @@ mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.11.0,<2.12.0" pyflakes = ">=3.1.0,<3.2.0" +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markupsafe" +version = "2.1.3" +description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + [[package]] name = "mccabe" version = "0.7.0" @@ -29,16 +117,36 @@ files = [ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] +[[package]] +name = "pdoc" +version = "14.1.0" +description = "API Documentation for Python Projects" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pdoc-14.1.0-py3-none-any.whl", hash = "sha256:e8869dffe21296b3bd5545b28e7f07cae0656082aca43f8915323187e541b126"}, + {file = "pdoc-14.1.0.tar.gz", hash = "sha256:3a0bd921a05c39a82b1505089eb6dc99d857b71b856aa60d1aca4d9086d0e18c"}, +] + +[package.dependencies] +Jinja2 = ">=2.11.0" +MarkupSafe = "*" +pygments = ">=2.12.0" + +[package.extras] +dev = ["black", "hypothesis", "mypy", "pygments (>=2.14.0)", "pytest", "pytest-cov", "pytest-timeout", "ruff", "tox", "types-pygments"] + [[package]] name = "pycodestyle" -version = "2.11.0" +version = "2.11.1" description = "Python style guide checker" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "pycodestyle-2.11.0-py2.py3-none-any.whl", hash = "sha256:5d1013ba8dc7895b548be5afb05740ca82454fd899971563d2ef625d090326f8"}, - {file = "pycodestyle-2.11.0.tar.gz", hash = "sha256:259bcc17857d8a8b3b4a2327324b79e5f020a13c16074670f9c8c8f872ea76d0"}, + {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, + {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, ] [[package]] @@ -53,6 +161,21 @@ files = [ {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, ] +[[package]] +name = "pygments" +version = "2.16.1" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + [[package]] name = "simpy" version = "4.0.2" @@ -68,4 +191,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "c164f34aba3ecbda9fc9942d6e2bfad3eb11491c433ee46bdf0262fd8147209f" +content-hash = "d9f1f22d9f90f2ad4d00ec2785eee468e847dc1c8c4015615febe6094edf3e62" diff --git a/pyproject.toml b/pyproject.toml index 1ca0cc9..c1a9717 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ simpy = "^4" [tool.poetry.dev-dependencies] flake8 = "^6" +pdoc = "^14" [tool.poetry.scripts] demo = "simtfl.demo:run" From de53ac847c813b10155dff1c7db20a26107e1bb0 Mon Sep 17 00:00:00 2001 From: Daira Emma Hopwood Date: Fri, 20 Oct 2023 21:09:18 +0100 Subject: [PATCH 10/15] Add `apidoc/` to `.gitignore` and clarify README on generating docs. Signed-off-by: Daira Emma Hopwood --- .gitignore | 3 +++ README.md | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 8af7378..24230c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ *.swp *.save +# API docs +apidoc/ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/README.md b/README.md index c5aabeb..6c4f99d 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,9 @@ Design documentation is under the `doc/` directory: * [Programming patterns for use of simpy](doc/patterns.md). -You can also generate API documentation by running `./gendoc.sh`. -The starting point for the generated documentation is . +You can also generate API documentation by running `./gendoc.sh`. This assumes +that you have run `poetry install` as shown above. The starting point for the +generated documentation is . ## Contributing From d5c637954d4c1b7bf9b26bf0cfb831acede0754d Mon Sep 17 00:00:00 2001 From: Daira Emma Hopwood Date: Fri, 20 Oct 2023 21:11:45 +0100 Subject: [PATCH 11/15] Update the simtfl module doc comment to point to tfl-book. Co-authored-by: Nathan Wilcox --- simtfl/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simtfl/__init__.py b/simtfl/__init__.py index d797bac..1f7a2c5 100644 --- a/simtfl/__init__.py +++ b/simtfl/__init__.py @@ -1,6 +1,6 @@ """ -This is an experimental simulator for research into a potential [Trailing Finality -Layer](https://electriccoin.co/blog/the-trailing-finality-layer-a-stepping-stone-to-proof-of-stake-in-zcash/) +This is an experimental simulator for research into a potential +[Trailing Finality Layer](https://electric-coin-company.github.io/tfl-book/) for Zcash. See the [README](../README.md) for more information. From 92c808219341515ea8d4b66171f7d4a40c0fad37 Mon Sep 17 00:00:00 2001 From: Daira Emma Hopwood Date: Fri, 20 Oct 2023 21:36:05 +0100 Subject: [PATCH 12/15] Refactor starting of nodes. Signed-off-by: Daira Emma Hopwood --- simtfl/network.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/simtfl/network.py b/simtfl/network.py index b5e17d4..51a5a75 100644 --- a/simtfl/network.py +++ b/simtfl/network.py @@ -38,28 +38,36 @@ def add_node(self, node): self.nodes.append(node) node.initialize(ident, self.env, self) + def _start(self, node): + """ + Starts a process for the given node (which is assumed to + have already been added to this `Network`). + """ + print(f"T{self.env.now:5d}: starting {node.ident:2d}: {node}") + self.env.process(node.run()) + def start_node(self, ident): """ - (process) Start the node with the given ident. + Starts a process for the node with the given ident. + A given node should only be started once. """ - node = self.node(ident) - print(f"T{self.env.now:5d}: starting {ident:2d}: {node}") - return node.run() + self._start(self.nodes[ident]) - def start_processes(self): + def start_all_nodes(self): """ - Start a process for each node. + Starts a process for each node. + A given node should only be started once. """ print() - for i in range(self.num_nodes()): - self.env.process(self.start_node(i)) + for node in self.nodes: + self._start(node) def run_all(self, *args, **kwargs): """ Convenience method to start a process for each node, then start the simulation. Takes the same arguments as `simpy.Environment.run`. """ - self.start_processes() + self.start_all_nodes() self.env.run(*args, **kwargs) def send(self, sender, target, message, delay=None): From 3d4a647bd05aa553651b32f74348b749cf8355e5 Mon Sep 17 00:00:00 2001 From: Daira Emma Hopwood Date: Fri, 20 Oct 2023 22:28:39 +0100 Subject: [PATCH 13/15] Add comments to explain the `Node` tests. Signed-off-by: Daira Emma Hopwood --- simtfl/node.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/simtfl/node.py b/simtfl/node.py index ab90ca2..e6cb4a7 100644 --- a/simtfl/node.py +++ b/simtfl/node.py @@ -128,25 +128,32 @@ def run(self): class PassiveReceiverTestNode(PassiveNode): def __init__(self): super().__init__() - self.received = deque() + self.handled = deque() def handle(self, sender, message): - self.received.append((sender, message, self.env.now)) + # Record when each message is handled. + self.handled.append((sender, message, self.env.now)) + # The handler takes 3 time units. yield self.env.timeout(3) class SequentialReceiverTestNode(SequentialNode): def __init__(self): super().__init__() - self.received = deque() + self.handled = deque() def handle(self, sender, message): - self.received.append((sender, message, self.env.now)) + # Record when each message is handled. + self.handled.append((sender, message, self.env.now)) + # The handler takes 3 time units. yield self.env.timeout(3) class SenderTestNode(PassiveNode): def run(self): + # We send messages at times 0, 1, 2. Since the network + # propagation delay is 1 (the default), they will be + # received at times 1, 2, 3. for i in range(3): yield from self.send(0, PayloadMessage(i)) yield self.env.timeout(1) @@ -159,9 +166,12 @@ def _test_node(self, receiver_node, expected): network.add_node(SenderTestNode()) network.run_all() - self.assertEqual(list(network.node(0).received), expected) + self.assertEqual(list(network.node(0).handled), expected) def test_passive_node(self): + # A PassiveNode subclass does not block on handling of + # previous messages, so it handles each message immediately + # when it is received. self._test_node(PassiveReceiverTestNode(), [ (1, PayloadMessage(0), 1), (1, PayloadMessage(1), 2), @@ -169,6 +179,10 @@ def test_passive_node(self): ]) def test_sequential_node(self): + # A SequentialNode subclass *does* block on handling of + # previous messages. It handles the messages as soon as + # possible after they are received subject to that blocking, + # so they will be handled at intervals of 3 time units. self._test_node(SequentialReceiverTestNode(), [ (1, PayloadMessage(0), 1), (1, PayloadMessage(1), 4), From 224faeebb73a1825668a69f3ce1b308ad20ce92c Mon Sep 17 00:00:00 2001 From: Daira Emma Hopwood Date: Fri, 20 Oct 2023 22:40:04 +0100 Subject: [PATCH 14/15] Fix an issue with overriding the network delay, and test it. Signed-off-by: Daira Emma Hopwood --- simtfl/node.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/simtfl/node.py b/simtfl/node.py index e6cb4a7..3e8a1a5 100644 --- a/simtfl/node.py +++ b/simtfl/node.py @@ -32,12 +32,12 @@ def initialize(self, ident, env, network): def __str__(self): return f"{self.__class__.__name__}" - def send(self, target, message): + def send(self, target, message, delay=None): """ (process) This method can be overridden to intercept messages being sent by this node. The implementation in this class calls `self.network.send`. """ - return self.network.send(self.ident, target, message) + return self.network.send(self.ident, target, message, delay=delay) def receive(self, sender, message): """ @@ -158,6 +158,9 @@ def run(self): yield from self.send(0, PayloadMessage(i)) yield self.env.timeout(1) + # Test overriding the propagation delay. This message + # is sent at time 3 and received at time 11. + yield from self.send(0, PayloadMessage(3), delay=8) class TestFramework(unittest.TestCase): def _test_node(self, receiver_node, expected): @@ -176,6 +179,7 @@ def test_passive_node(self): (1, PayloadMessage(0), 1), (1, PayloadMessage(1), 2), (1, PayloadMessage(2), 3), + (1, PayloadMessage(3), 11), ]) def test_sequential_node(self): @@ -187,4 +191,5 @@ def test_sequential_node(self): (1, PayloadMessage(0), 1), (1, PayloadMessage(1), 4), (1, PayloadMessage(2), 7), + (1, PayloadMessage(3), 11), ]) From 6e2686eae15b0ba17ac0aca079794a4ca5b54cfe Mon Sep 17 00:00:00 2001 From: Daira Emma Hopwood Date: Fri, 20 Oct 2023 23:02:30 +0100 Subject: [PATCH 15/15] Consistently use "message propagation delay". Signed-off-by: Daira Emma Hopwood --- simtfl/network.py | 8 ++++---- simtfl/node.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/simtfl/network.py b/simtfl/network.py index 51a5a75..45ebbdf 100644 --- a/simtfl/network.py +++ b/simtfl/network.py @@ -12,7 +12,7 @@ class Network: def __init__(self, env, nodes=None, delay=1): """ Constructs a Network with the given `simpy.Environment`, and optionally - a set of initial nodes and a message delay. + a set of initial nodes and a message propagation delay. """ self.env = env self.nodes = nodes or [] @@ -73,8 +73,8 @@ def run_all(self, *args, **kwargs): def send(self, sender, target, message, delay=None): """ (process) Sends a message to the node with ident `target`, from the node - with ident `sender`. The message delay is normally given by `self.delay`, - but can be overridden by the `delay` parameter. + with ident `sender`. The message propagation delay is normally given by + `self.delay`, but can be overridden by the `delay` parameter. """ if delay is None: delay = self.delay @@ -90,7 +90,7 @@ def send(self, sender, target, message, delay=None): def convey(self, delay, sender, target, message): """ (process) Conveys a message to the node with ident `target`, from the node - with ident `sender`, after waiting for the given transmission delay. + with ident `sender`, after waiting for the given message propagation delay. This normally should not be called directly because it *may* only complete after the message has been handled by the target node. The caller should not depend on when it completes. diff --git a/simtfl/node.py b/simtfl/node.py index 3e8a1a5..a714bfd 100644 --- a/simtfl/node.py +++ b/simtfl/node.py @@ -151,7 +151,7 @@ def handle(self, sender, message): class SenderTestNode(PassiveNode): def run(self): - # We send messages at times 0, 1, 2. Since the network + # We send messages at times 0, 1, 2. Since the message # propagation delay is 1 (the default), they will be # received at times 1, 2, 3. for i in range(3):