From bf7c0f5d252bd5f756459e537dbd60abff3f9547 Mon Sep 17 00:00:00 2001 From: Radoslaw Kaminski Date: Wed, 22 Apr 2026 16:11:47 +0100 Subject: [PATCH 1/8] nim-libp2p gossipsub --- .github/workflows/gossipsub-interop-pr.yml | 14 ++++++ gossipsub-interop/.gitignore | 1 + gossipsub-interop/Makefile | 55 ++++++++++++++++++++-- gossipsub-interop/experiment.py | 14 ++++++ gossipsub-interop/nim-libp2p/.gitkeep | 0 5 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 gossipsub-interop/nim-libp2p/.gitkeep diff --git a/.github/workflows/gossipsub-interop-pr.yml b/.github/workflows/gossipsub-interop-pr.yml index 6f0a49490..48643e9c5 100644 --- a/.github/workflows/gossipsub-interop-pr.yml +++ b/.github/workflows/gossipsub-interop-pr.yml @@ -27,6 +27,11 @@ jobs: - name: Set up Rust uses: dtolnay/rust-toolchain@stable + - name: Set up Nim + run: | + curl https://nim-lang.org/choosenim/init.sh -sSf | sh -s -- -y + echo "$HOME/.nimble/bin" >> $GITHUB_PATH + - name: Install uv run: | curl -LsSf https://astral.sh/uv/install.sh | sh @@ -60,3 +65,12 @@ jobs: - name: Run gossipsub interop tests working-directory: gossipsub-interop run: make test + continue-on-error: true + + - name: Upload Shadow output + if: always() + uses: actions/upload-artifact@v4 + with: + name: shadow-output + path: gossipsub-interop/shadow-outputs/ + retention-days: 5 diff --git a/gossipsub-interop/.gitignore b/gossipsub-interop/.gitignore index 26b0c9768..7e60de738 100644 --- a/gossipsub-interop/.gitignore +++ b/gossipsub-interop/.gitignore @@ -21,3 +21,4 @@ backup timelines experiment-results/** __pycache__ +nim-libp2p-src/ diff --git a/gossipsub-interop/Makefile b/gossipsub-interop/Makefile index 50da64722..022da37bb 100644 --- a/gossipsub-interop/Makefile +++ b/gossipsub-interop/Makefile @@ -1,29 +1,76 @@ # Default target all: binaries -binaries: +NIM_LIBP2P_COMMIT = 19d6127890c80bc65323c6f5e4ef03d868378d0d +nim-libp2p/gossipsub-bin: + @echo "Building nim-libp2p from commit: $(NIM_LIBP2P_COMMIT)" + # Clone nim-libp2p (if not already present) and build gossipsub binary + @if [ ! -d "nim-libp2p-src" ]; then \ + git clone https://github.com/vacp2p/nim-libp2p.git nim-libp2p-src && \ + cd nim-libp2p-src && git checkout $(NIM_LIBP2P_COMMIT); \ + fi + @mkdir -p nim-libp2p + cd nim-libp2p-src && nimble install_pinned + cd nim-libp2p-src && nim c -d:release \ + --skipProjCfg --skipParentCfg \ + --NimblePath:./nimbledeps/pkgs2 \ + -p:. --mm:refc \ + -d:chronicles_colors=None \ + -d:chronicles_log_level=TRACE \ + -d:chronicles_default_output_device=stderr \ + --threads:on \ + -o:$(CURDIR)/nim-libp2p/gossipsub-bin \ + ./interop/gossipsub/peer.nim + +binaries: nim-libp2p/gossipsub-bin cd go-libp2p && go build -linkshared -o gossipsub-bin cd rust-libp2p && cargo build # Clean all generated shadow simulation files clean: rm -rf shadow-outputs || true + rm -rf nim-libp2p-src || true + rm -f nim-libp2p/gossipsub-bin || true rm plots/* || true test: # Testing partial messages - @echo "Testing partial messages" + @echo "Testing partial messages (rust-and-go)" @uv run run.py --node_count 32 --composition "rust-and-go" --scenario "partial-messages" && uv run checks/partial_messages.py latest --count 1 - @echo "Testing partial messages chain" + @echo "Testing partial messages (nim-and-go)" + @uv run run.py --node_count 32 --composition "nim-and-go" --scenario "partial-messages" && uv run checks/partial_messages.py latest --count 1 + + @echo "Testing partial messages (nim-and-rust)" + @uv run run.py --node_count 32 --composition "nim-and-rust" --scenario "partial-messages" && uv run checks/partial_messages.py latest --count 1 + + @echo "Testing partial messages chain (rust-and-go)" @uv run run.py --node_count 8 --composition "rust-and-go" --scenario "partial-messages-chain" && uv run checks/partial_messages.py latest --count 16 - @echo "Testing fanout" + @echo "Testing partial messages chain (nim-and-go)" + @uv run run.py --node_count 8 --composition "nim-and-go" --scenario "partial-messages-chain" && uv run checks/partial_messages.py latest --count 16 + + @echo "Testing partial messages chain (nim-and-rust)" + @uv run run.py --node_count 8 --composition "nim-and-rust" --scenario "partial-messages-chain" && uv run checks/partial_messages.py latest --count 16 + + @echo "Testing fanout (rust-and-go)" uv run run.py --node_count 8 --composition "rust-and-go" --scenario "partial-messages-fanout" && uv run checks/partial_messages.py latest/ uv run run.py --node_count 8 --seed 1 --composition "rust-and-go" --scenario "partial-messages-fanout" && uv run checks/partial_messages.py latest/ uv run run.py --node_count 8 --seed 2 --composition "rust-and-go" --scenario "partial-messages-fanout" && uv run checks/partial_messages.py latest/ uv run run.py --node_count 8 --seed 3 --composition "rust-and-go" --scenario "partial-messages-fanout" && uv run checks/partial_messages.py latest/ + @echo "Testing fanout (nim-and-go)" + uv run run.py --node_count 8 --composition "nim-and-go" --scenario "partial-messages-fanout" && uv run checks/partial_messages.py latest/ + uv run run.py --node_count 8 --seed 1 --composition "nim-and-go" --scenario "partial-messages-fanout" && uv run checks/partial_messages.py latest/ + uv run run.py --node_count 8 --seed 2 --composition "nim-and-go" --scenario "partial-messages-fanout" && uv run checks/partial_messages.py latest/ + uv run run.py --node_count 8 --seed 3 --composition "nim-and-go" --scenario "partial-messages-fanout" && uv run checks/partial_messages.py latest/ + + @echo "Testing fanout (nim-and-rust)" + uv run run.py --node_count 8 --composition "nim-and-rust" --scenario "partial-messages-fanout" && uv run checks/partial_messages.py latest/ + uv run run.py --node_count 8 --seed 1 --composition "nim-and-rust" --scenario "partial-messages-fanout" && uv run checks/partial_messages.py latest/ + uv run run.py --node_count 8 --seed 2 --composition "nim-and-rust" --scenario "partial-messages-fanout" && uv run checks/partial_messages.py latest/ + uv run run.py --node_count 8 --seed 3 --composition "nim-and-rust" --scenario "partial-messages-fanout" && uv run checks/partial_messages.py latest/ + test-go: # Testing partial messages @echo "Testing partial messages" diff --git a/gossipsub-interop/experiment.py b/gossipsub-interop/experiment.py index 3449a2edd..eb31ad1c0 100644 --- a/gossipsub-interop/experiment.py +++ b/gossipsub-interop/experiment.py @@ -358,6 +358,20 @@ def composition(preset_name: str) -> List[Binary]: ), Binary("go-libp2p/gossipsub-bin", percent_of_nodes=50), ] + case "all-nim": + return [Binary("nim-libp2p/gossipsub-bin", percent_of_nodes=100)] + case "nim-and-go": + return [ + Binary("nim-libp2p/gossipsub-bin", percent_of_nodes=50), + Binary("go-libp2p/gossipsub-bin", percent_of_nodes=50), + ] + case "nim-and-rust": + return [ + Binary("nim-libp2p/gossipsub-bin", percent_of_nodes=50), + Binary( + "rust-libp2p/target/debug/rust-libp2p-gossip", percent_of_nodes=50 + ), + ] raise ValueError(f"Unknown preset name: {preset_name}") diff --git a/gossipsub-interop/nim-libp2p/.gitkeep b/gossipsub-interop/nim-libp2p/.gitkeep new file mode 100644 index 000000000..e69de29bb From 4bdace466782b25a2f492c386a7a7af0610905fe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:54:17 +0000 Subject: [PATCH 2/8] chore: update the link to the interop dashboard [skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7db00dbe4..4c43340aa 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Interoperability/end to end test-plans & performance benchmarking for libp2p -[![Interop Dashboard](https://github.com/libp2p/test-plans/workflows/libp2p%20transport%20interop%20test/badge.svg?branch=master)](https://github.com/libp2p/test-plans/actions/runs/24059437449/attempts/1#summary-70174001405) +[![Interop Dashboard](https://github.com/rlve/test-plans/workflows/libp2p%20transport%20interop%20test/badge.svg?branch=master)](https://github.com/rlve/test-plans/actions/runs/24787308432/attempts/1#summary-72538371243) [![Made by Protocol Labs](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://protocol.ai) From 9b6db612ea0ba5539c6c14f882a57ff13455fce0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:57:23 +0000 Subject: [PATCH 3/8] chore: update the link to the interop dashboard [skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c43340aa..8b433f85e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Interoperability/end to end test-plans & performance benchmarking for libp2p -[![Interop Dashboard](https://github.com/rlve/test-plans/workflows/libp2p%20transport%20interop%20test/badge.svg?branch=master)](https://github.com/rlve/test-plans/actions/runs/24787308432/attempts/1#summary-72538371243) +[![Interop Dashboard](https://github.com/rlve/test-plans/workflows/libp2p%20transport%20interop%20test/badge.svg?branch=master)](https://github.com/rlve/test-plans/actions/runs/24787221085/attempts/1#summary-72538932832) [![Made by Protocol Labs](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://protocol.ai) From bfa5826536235bb8a601ba612a52246fdfc9bfe2 Mon Sep 17 00:00:00 2001 From: Radoslaw Kaminski Date: Thu, 7 May 2026 09:44:47 +0100 Subject: [PATCH 4/8] refactor composition --- gossipsub-interop/README.md | 8 ++++---- gossipsub-interop/run.py | 13 +++++++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/gossipsub-interop/README.md b/gossipsub-interop/README.md index 310074788..295340f5c 100644 --- a/gossipsub-interop/README.md +++ b/gossipsub-interop/README.md @@ -50,7 +50,7 @@ nodes with default GossipSub parameters and sending large messages to a network of 700 nodes: ```bash -uv run run.py --node_count 700 --composition "rust-and-go" --scenario "subnet-blob-msg" +uv run run.py --node_count 700 --composition rust go --scenario "subnet-blob-msg" ``` The definitions of the experiment, composition, and scenarios are defined in `experiment.py`. @@ -59,7 +59,7 @@ After running an experiment all the results and configuration needed to reproduce the test are saved in an output folder which, by default, is named by the specific scenario, node count, and composition. For the above example, the output folder is -`subnet-blob-msg-700-rust-and-go.data`. This output folder contains the following files: +`subnet-blob-msg-700-rust-go.data`. This output folder contains the following files: - shadow.yaml: The Shadow config defining the binaries and network. - graph.gml: The graph of the network links for Shadow. @@ -74,14 +74,14 @@ To build the implementation reference `./test-specs/implementation.md`. After implementing it, make sure to add build commands in the Makefile's `binaries` recipe. -Finally, add it to the `composition` function in `experiment.py`. +Finally, add an entry to the `IMPLEMENTATIONS` dict in `experiment.py` mapping a short name (e.g. `nim`) to the binary path. It then becomes available to `--composition` automatically. ## Examples Minimal test of partial messages ```bash -uv run run.py --node_count 2 --composition "all-go" --scenario "partial-messages" && uv run checks/partial_messages.py latest/ +uv run run.py --node_count 2 --composition go --scenario "partial-messages" && uv run checks/partial_messages.py latest/ ``` That command runs the shadow simulation and then verifies the stdout logs have the expected message. diff --git a/gossipsub-interop/run.py b/gossipsub-interop/run.py index 98adddc7b..90dfe111b 100644 --- a/gossipsub-interop/run.py +++ b/gossipsub-interop/run.py @@ -28,7 +28,15 @@ def main(): parser.add_argument( "--scenario", type=str, required=False, default="subnet-blob-msg" ) - parser.add_argument("--composition", type=str, required=False, default="all-go") + parser.add_argument( + "--composition", + type=str, + nargs="+", + required=False, + default=["go"], + help="One or more implementation names (e.g. go rust nim jvm). " + "Nodes are split evenly across them.", + ) parser.add_argument("--output_dir", type=str, required=False) args = parser.parse_args() @@ -48,7 +56,8 @@ def main(): import datetime timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") - args.output_dir = f"{args.scenario}-{args.node_count}-{args.composition}-{ + composition_label = "-".join(args.composition) + args.output_dir = f"{args.scenario}-{args.node_count}-{composition_label}-{ args.seed }-{timestamp}-{git_describe}.data" From 3a8ffec17ca742bdc1cd0d0eab4de7a4a0ea9859 Mon Sep 17 00:00:00 2001 From: Radoslaw Kaminski Date: Thu, 7 May 2026 09:44:47 +0100 Subject: [PATCH 5/8] refactor composition --- gossipsub-interop/experiment.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/gossipsub-interop/experiment.py b/gossipsub-interop/experiment.py index 8a2963de2..7ecca96fb 100644 --- a/gossipsub-interop/experiment.py +++ b/gossipsub-interop/experiment.py @@ -352,20 +352,20 @@ def scenario( def composition(impls: List[str]) -> List[Binary]: if not impls: raise ValueError("composition requires at least one implementation") - unknown = [n for n in impls if n not in IMPLEMENTATIONS] - if unknown: - raise ValueError( - f"Unknown implementation(s) {unknown}. " - f"Known: {sorted(IMPLEMENTATIONS)}" - ) - base = 100 // len(impls) - remainder = 100 - base * len(impls) + for name in impls: + if name not in IMPLEMENTATIONS: + raise ValueError( + f"Unknown implementation '{name}'. " + f"Known: {sorted(IMPLEMENTATIONS)}" + ) + + # Split 100% as evenly as possible. First `leftover` impls get +1 (e.g. 3 impls -> 34/33/33). + base, leftover = divmod(100, len(impls)) + percents = [base + 1] * leftover + [base] * (len(impls) - leftover) + return [ - Binary( - IMPLEMENTATIONS[name], - percent_of_nodes=base + (1 if i < remainder else 0), - ) - for i, name in enumerate(impls) + Binary(IMPLEMENTATIONS[name], percent_of_nodes=pct) + for name, pct in zip(impls, percents) ] From 135bb22bdccf970195ba20eb841aa0670457b3d8 Mon Sep 17 00:00:00 2001 From: Radoslaw Kaminski Date: Thu, 7 May 2026 10:17:00 +0100 Subject: [PATCH 6/8] update commit --- gossipsub-interop/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gossipsub-interop/Makefile b/gossipsub-interop/Makefile index c96402b3d..4f0e6ca10 100644 --- a/gossipsub-interop/Makefile +++ b/gossipsub-interop/Makefile @@ -1,7 +1,7 @@ # Default target all: binaries -NIM_LIBP2P_COMMIT = 19d6127890c80bc65323c6f5e4ef03d868378d0d +NIM_LIBP2P_COMMIT = 220a07fd49531505d7191c96521a627280712e35 nim-libp2p/gossipsub-bin: @echo "Building nim-libp2p from commit: $(NIM_LIBP2P_COMMIT)" # Clone nim-libp2p (if not already present) and build gossipsub binary From edd03e01f552dc2d0a53c45e1d69e678c38c09d5 Mon Sep 17 00:00:00 2001 From: Radoslaw Kaminski Date: Thu, 7 May 2026 12:38:49 +0100 Subject: [PATCH 7/8] update commit --- gossipsub-interop/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gossipsub-interop/Makefile b/gossipsub-interop/Makefile index 4f0e6ca10..90a60bf7b 100644 --- a/gossipsub-interop/Makefile +++ b/gossipsub-interop/Makefile @@ -1,7 +1,7 @@ # Default target all: binaries -NIM_LIBP2P_COMMIT = 220a07fd49531505d7191c96521a627280712e35 +NIM_LIBP2P_COMMIT = 22ab0402cd86f88fba0103f7946311d93e2566cd nim-libp2p/gossipsub-bin: @echo "Building nim-libp2p from commit: $(NIM_LIBP2P_COMMIT)" # Clone nim-libp2p (if not already present) and build gossipsub binary From 67f393539c01ed3b60920de393a2a94bbca2e3f1 Mon Sep 17 00:00:00 2001 From: Radoslaw Kaminski Date: Thu, 7 May 2026 12:42:53 +0100 Subject: [PATCH 8/8] revert readme change --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b433f85e..7db00dbe4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Interoperability/end to end test-plans & performance benchmarking for libp2p -[![Interop Dashboard](https://github.com/rlve/test-plans/workflows/libp2p%20transport%20interop%20test/badge.svg?branch=master)](https://github.com/rlve/test-plans/actions/runs/24787221085/attempts/1#summary-72538932832) +[![Interop Dashboard](https://github.com/libp2p/test-plans/workflows/libp2p%20transport%20interop%20test/badge.svg?branch=master)](https://github.com/libp2p/test-plans/actions/runs/24059437449/attempts/1#summary-70174001405) [![Made by Protocol Labs](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://protocol.ai)