3361 Commits
Author SHA1 Message Date
Richard Levitte e6fe520753 just/check-scripts: Limit to tracked files
It's pretty annoying to have untracked (or even ignored!) stray files
checked when committing.

Script checks treat placeholders as filenames.

Remove the find-specific arguments from the xargs command so
ShellCheck receives only tracked shell script paths.
2026-10-30 14:34:30 +01:00
Lorenz Leutgeb 244a8b6fca scripts, build: Name all scripts *.sh
… so that `just check-scripts` actually finds all of them.
2026-10-30 12:48:00 +01:00
Lorenz Leutgeb c7d71df4ab scripts, build, just: POSIX Compliance
Enforce POSIX shell syntax with ShellCheck using `--shell sh`.
2026-10-30 12:45:40 +01:00
Rūdolfs Ošiņš 9e80d2d4b9 protocol/service: Record relayers of duplicates
Only the first peer to send us a version of an announcement was
recorded as its relayer, as the `FIXME` in `handle_announcement`
notes. Inventory announcements are relayed with a delay, so peers that
sent the same version in the meantime were sent it back, which kept
`announcement_message_amplification` close to the worst case. Node and
refs announcements are relayed as soon as they arrive, so this does
not affect them.

The gossip store does not report which row a duplicate belongs to, so
key the relayers by announcer, type and repository instead, together
with the version they relayed. Having `Store::announced` report the
row of a duplicate would also work, but would change what its result
means for its other callers. As before, entries are never pruned, but
there is now at most one entry per announcer, type and repository,
rather than one per row ever created.

With this, 1000 runs of `announcement_message_amplification` measured
between 2.525 and 2.975, with a mean of 2.72, down from a mean of 3.98,
against a best case of 2.5 for five fully connected nodes. Require it
to stay below 3.25, which leaves room for the variation between runs.
2026-10-27 11:05:19 +01:00
Rūdolfs Ošiņš 108140ac1a protocol/service: Forget relayers of older versions
The relayers of an announcement are keyed by its row in the gossip
store, which is the same for every version of the announcement, and
are only ever added to. A peer that relayed any version of it to us
is never sent a later one.

Keep only the relayers of the latest version. This also keeps each
list of relayers from growing with every version.

The amplification that `announcement_message_amplification` measures
rises slightly, from a mean of 3.90 to 3.98 over 300 runs each, since
peers that relayed an earlier version of the announcement are now sent
the new one as well. In one traced simulation, for example, Dan now
relays Amy's new inventory to Bob, which had relayed Amy's earlier
inventory to Dan.
2026-10-27 10:17:17 +01:00
Rūdolfs Ošiņš 7f89ad775b node/test: Demonstrate relay starvation
A node does not relay a new version of an announcement to a peer that
relayed an earlier version of it. If the link between the announcer
and that peer drops, the peer stops receiving the announcer's updates
through other paths until it reconnects to the announcer.

Add a simulation in which Cid learns about Amy's inventory through
Bob, after which Amy drops Bob and connects to Cid. Bob does not learn
about Amy's new inventory. Assert this, and mark it to be fixed.
2026-10-27 10:17:17 +01:00
Rūdolfs Ošiņš b544cbe69d node/test: Wake simulated nodes on their own clock
The simulator scheduled a wakeup relative to the simulation's time,
even when the node's clock is ahead of it, for example after
`Peer::elapse`. Tests that stagger nodes this way, such as
`announcement_message_amplification`, ended up running every node's
periodic tasks at the same moment.

Schedule the wakeup relative to the node's clock, or to the
simulation's time if the node's clock is behind. Other outputs are
still scheduled relative to the simulation's time.

With the nodes staggered, `announcement_message_amplification` still
measures close to the worst case: a node does not record the sender
of a duplicate as a relayer, as the `FIXME` in `handle_announcement`
notes, and so relays the message back to it.
2026-10-27 10:17:17 +01:00
Rūdolfs Ošiņš 8d345e73e7 node/test: Wake every simulated node
The simulator skipped a wakeup whenever any node was already due to
wake at the same time, so of several nodes asking to wake at the same
moment, only the first was ever woken. With nodes started together,
most of them never ran their periodic tasks, relaying included.

Only skip a wakeup if the same node is already due to wake at that
time, which includes the later time slots that colliding inputs are
moved to.

`announcement_message_amplification` passed only because of this: in
every simulation, only Cid, whose node ID sorts first, ever woke, so
only Cid relayed the announcement. With every node waking at the same
moment, all of them relay it before receiving it from each other,
which brings the amplification close to the worst case. Assert that it
stays above 3, and mark the assertion to be lowered.
2026-10-27 10:08:41 +01:00
Rūdolfs Ošiņš ad1699b025 node/test: Make simulations reproducible
A simulation that fails under a random seed can only be investigated
if it can be run again with the same seed. Replaying from a seed is
also a first step towards the deterministic simulation testing
proposed [on Zulip][dst], and one that works with the simulator as it
is.

The simulator seeds its generator randomly and only logs the seed, so
there is no way to run a simulation with it again. Even with a fixed
seed, runs would not reliably replay, because partitions were decided
on the absolute time, and simulations start at `LocalTime::now()`.

Seed simulations from `RAD_SIM_SEED`, mention it next to the logged
seed, and decide partitions by the elapsed time. A fixed seed in every
test would also make runs reproducible, but would give up the
variation that random seeds provide.

Message timestamps still differ between runs of tests that start at
`LocalTime::now()`, and tests that draw from their own generator, such
as `announcement_message_amplification`, are not reproducible from the
seed alone yet.

[dst]: <https://radicle.zulipchat.com/#narrow/channel/369277-heartwood/topic/DST.3A.20Deterministic.20Simulation.20Testing/near/567515594>
2026-10-27 10:08:41 +01:00
Lorenz Leutgeb 9417a20fcd git-ref-format: Annotate transmute calls
Clippy reports `clippy::missing_transmute_annotations` in the
macro expansions for `refname!` and `pattern!`, including their
uses in `radicle-surf` tests.

Specify `String` as the source type in both calls to `transmute`.
2026-10-25 14:45:39 +01:00
Lorenz Leutgeb 3ca140cebb nix: Include radicle-surfs platinum 2026-10-25 14:45:39 +01:00
Lorenz Leutgeb 1f5e0deb45 cargo/deny: Make clear that we use cargo audit
Both `cargo deny` and `cargo audit` can check for advisories. We want
to use `cargo audit` for advisories and `cargo deny` for its other
checks.

Notify users of `cargo deny` by providing an obviously non-existing
database path to `cargo deny`, which is printed as part of the error
message.
2026-10-25 14:45:39 +01:00
Lorenz Leutgeb 213da1fdd6 ci/coverage: Add codecov.io via Tarpaulin 2026-10-25 14:45:39 +01:00
Yorgos Saslis 2a758c9487 cob/test: Fix colliding signing keys
Signing keys generated by `r#gen::<SigningKey>(n)` may collide,
leading to failure.

`SigningKey::mock(n)` derives the key from `n`, so keys
are distinct and stable across runs.
2026-10-24 10:20:47 +02:00
Lorenz Leutgeb 09d04d1787 cli/cob: Fix Race Condition in Test
Bob may fetch before Alice creates the issue, because the
clone may join an in-flight fetch.

Create the issue before connecting the nodes to avoid the race.
2026-10-23 20:45:48 +02:00
Lorenz Leutgeb 932c90fb00 ci: Set token permissions to read-only 2026-10-23 20:17:44 +02:00
Lorenz Leutgeb d1dfe091b4 ci: Bump GitHub Actions 2026-10-23 20:17:44 +02:00
Lorenz Leutgeb d3dd8e6b5c ci: Update images in the build matrix 2026-10-23 20:17:44 +02:00
Lorenz Leutgeb 5605dbd5ce ci: Relax RUST_{LOG,TEST_THREADS} on Windows
The jobs have stabilizied sufficiently to remove overrides that made
debugging easier on Windows.
2026-10-23 20:17:44 +02:00
Lorenz Leutgeb 526a94437b surf/test: Fixes for Windows
Various small fixes:
 1. Disable conversion of CRLFs for exact comparison of fixtures.
 2. Do not assume that the path separator is `/`.
 3. Skip a test that cannot work due to a backslash in a file name.
 4. Enable a workaround for long paths.
2026-10-23 20:17:44 +02:00
Lorenz Leutgeb 949bf9c72e radicle/node: Announcements only have one result
For commands that may return multiple responses, a loop makes sense,
but inventory announcements should expect exactly one response.
2026-10-23 20:15:11 +02:00
Lorenz Leutgeb 15e2d7d455 cli/test: Relax matching error message
On Windows, these tests sometimes produced a different error message.
The message itself, and the hint that follows, should be good enough to
match on.
2026-10-23 20:06:14 +02:00
Lorenz Leutgeb 7a21a472c2 cli: End lines in unified diffs with line feed
The workaround for Windows is not necessary anymore.
2026-10-23 20:04:49 +02:00
Lorenz Leutgeb 9b953908b9 node/control: End responses to fetch with newline
Without this, `rad` would hang on Windows, waiting for EOF.
2026-10-23 20:04:34 +02:00
Lorenz Leutgeb b7bc88037f cli: Avoid overflow in xdiff on Windows
We would like the diff output of changes to the identity document to not
collapse into separate hunks, but show all of the identity document in
the diff. The way to do this is to set the number of context lines
sufficiently high. It turns out that `u32::MAX` is too high for `xdiff`,
which is commonly used on Windows. `xdiff` stores the context lines in
a signed integer of 32 bits. Also, it appears to double line numbers
when it merges hunks. Thus, a safe value seems to be half of `i32::MAX`.
2026-10-23 20:03:12 +02:00
Lorenz Leutgeb fe7b8443c3 radicle/test: Disable Git core.autocrlf
This just makes handling test fixtures a lot easier.
2026-10-23 20:00:45 +02:00
Lorenz Leutgeb 0c00abbb91 ci: Disable Git core.autocrlf on Windows 2026-10-23 20:00:00 +02:00
Lorenz Leutgeb ae7aa710cd cli-test: Normalize Windows Paths
Maintaining tests for Windows and Unix separately, just because they use
different characters to separate paths is annoying.

This simple normalization function that in essence replaces backslashes
with forward slashes eats all cases in our tests.
2026-10-23 19:58:55 +02:00
Lorenz Leutgeb f6ea698f7b cli-test: Use shlex on Windows
Because tests are written "Unix-style", it actually does more harm
than good to use `winsplit` on Windows, because of differences in
behavior compared to `shlex`. For example, empty arguments are
dropped by `winsplit`.
2026-10-23 19:42:06 +02:00
Lorenz Leutgeb 7bf63d3b25 cli-test, node: Preserve SystemRoot on Windows
Preserve the environment variable `SystemRoot`, as Winsock might get
confused without it.
2026-10-23 19:37:59 +02:00
Lorenz Leutgeb af135ca723 crypto/ssh: Fix line endings for signatures
Use a line feed to end lines in signatures that might end
up in Git objects to have matching OIDs on Windows and Unix.
2026-10-23 19:36:33 +02:00
Lorenz Leutgeb 83137067f0 cli/test: Remove .exe
It turns out that `clap` already helpfully strips the extension on
Windows.
2026-10-23 19:35:12 +02:00
Lorenz Leutgeb 134e249b2d cli/test: Skip rad_cob_multiset on Windows
The test uses a script with a shebang that cannot
be executed on Windows.
2026-10-23 19:35:12 +02:00
Lorenz Leutgeb 41608ee678 cli/test/init: Set invalid default branch 2026-10-23 19:35:12 +02:00
Lorenz Leutgeb 91651b2322 cargo/rand: 0.8.5 → 0.8.6, 0.9.2 → 0.9.3
https://rustsec.org/advisories/RUSTSEC-2026-0097
2026-10-22 19:47:33 +02:00
Lorenz Leutgeb 5083212468 cargo/memmap2: 0.9.10 → 0.9.11
https://rustsec.org/advisories/RUSTSEC-2026-0186
2026-10-22 19:47:03 +02:00
Lorenz Leutgeb 4e89e16cb3 cargo/faster-hex: 0.10.0 → 0.10.1
https://rustsec.org/advisories/RUSTSEC-2026-0306
2026-10-22 19:46:43 +02:00
Lorenz Leutgeb 1e6d444d99 cargo/anyhow: 1.0.102 → 1.0.103
https://rustsec.org/advisories/RUSTSEC-2026-0190
2026-10-22 19:46:06 +02:00
Xeonacid 5e0f26a73f cli/tests: Give private-clone success paths more time
The unauthorized clone in `rad-init-private-clone` keeps a 1s timeout
so it still fails quickly. The success paths used 1s and 3s, which is
not enough on slower hosts: `rad sync --announce` reports "All seeds
timed out" after Bob has already fetched, and `rad seed`/`rad clone`
miss the fetch handshake.

Raise those success timeouts to 30s, matching `DEFAULT_TIMEOUT`. The
wait ends as soon as the target is met, so passing runs are not slower.

Signed-off-by: Xeonacid <h.dwwwwww@gmail.com>
2026-10-20 17:56:30 +02:00
Xeonacid 2054b7a894 node/test: Match large-fetch timeout to payload size
`test_large_fetch` populates the repository with `scale.max(3)` but
waits only `9 * scale` seconds for `RefsFetched`. At the default scale
of 1 that is about 9MB of incompressible data with a 9s budget.

On slower hosts the wait times out before the fetch finishes. Use
`scale.max(3)` for the timeout as well, matching the payload size.

Signed-off-by: Xeonacid <h.dwwwwww@gmail.com>
2026-10-20 17:53:41 +02:00
Xeonacid ec3a58050b cli/tests: Wait for seed discovery after clone
`git_push_canonical` clones the repository as Bob and then immediately
runs `git push` as Alice, expecting "Synced with 1 seed(s)".

`clone` announces Bob's inventory, but Alice only treats Bob as a seed
after that announcement is applied to her routing table. On a slower
host the reactor can still be busy when Alice looks up seeds, so she
reports "No seeds found".

Wait until Alice's routing table contains Bob for the repository before
pushing. Apply the same wait to the lightweight-tags variant, which
uses the same setup.

Signed-off-by: Xeonacid <h.dwwwwww@gmail.com>
2026-10-20 17:48:26 +02:00
Xeonacid 422b7c033f cli/tests: Treat exit code 127 as "not found"
`fn program_reports_version` skips optional tools such as `jq` and
`jj` when they are missing. It currently only treats an error returned
by `std::process::Command::status` of kind
`std::io::ErrorKind::NotFound` as absence.

Rust's `std::command::Command` uses glibc's `posix_spawn` on Unix.
When `execve` fails after `spawn` has already returned, POSIX requires
the child to exit with code 127 rather than reporting `errno` to the
parent. glibc usually copies that failure back through a
`CLONE_VM|CLONE_VFORK` shared-stack channel, but not every
implementation preserves it. qemu-user, OpenBSD, and HPPA surface a
successful `spawn` and an exit code of 127 instead.

On those platforms the helper panics, so tests that are meant to skip
without `jq` or `jj` fail. Treat exit status 127 the same as
`NotFound`.

See `man:posix_spawn(3)` and POSIX.1.

Signed-off-by: Xeonacid <h.dwwwwww@gmail.com>
2026-10-20 17:40:39 +02:00
Lorenz Leutgeb 515b5884d0 master → main
The default branch of the repository changed from `master` to `main`,
so adapt scripts and documentation.
2026-10-20 16:31:17 +02:00
Lorenz Leutgeb 28f740cb8a radicle/storage/git/cob: List using a glob
Instead of iterating over *all references* and filtering for those that
look like a COB of the expected type, use a glob so that Git can do more
lifting for us.
2026-10-18 21:12:10 +02:00
Lorenz Leutgeb 0f0d64203a radicle/git/refs/storage: Tune glob helpers
Rename `cob` to `cob_with_namespace` and `cobs` to `cob` to make
room for a new `cobs` that can be used to list all COBs (by type) in a
repo.
2026-10-18 21:12:10 +02:00
Lorenz Leutgeb a9525f2173 git-ref-format: Allow composing patterns 2026-10-18 21:12:10 +02:00
Yorgos SaslisandLorenz Leutgeb f4bc34450e radicle/storage/cob: Skip non-commit refs
Valid COB refs always point to commits. However, any peer can write
an invalid COB ref that points to a blob. When the node fetches
that peer's refs and it tries to load them, it would fail the whole
`objects` or `types` call. The targeted COB could not be opened and every
listing of that type from storage failed, until the ref was removed.

Any peer could thus create invalid COB refs for existing COBs, causing
any other node that fetched those refs (e.g. a permissive node) to no
longer be able to list / display the COB in question. A malicious peer
could thus prevent permissive nodes from displaying any issues or patches,
e.g. on popular projects.

Skip the ref with a warning instead. A valid COB ref always points to a
commit, so honest data evaluates the same as before. Other conversion
errors still fail the call as before.

Tests cover opening and listing an issue next to such a ref, and
opening an issue whose only ref is such a ref.

Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.dev>
2026-10-18 21:12:10 +02:00
Daniel Norman 81d79077d0 protocol: Do not panic on trivially empty duration
Any connected peer can send a `Subscribe` with `since` after `until`.
The service passes the range to `Store::filtered`, which asserts
`from <= to`. The panic stops the service thread.

Remove the assertion. The query already returns no rows when
`from > to`, so the peer gets no history.
2026-10-15 00:24:28 +02:00
Daniel NormanandLorenz Leutgeb a4aae610f4 protocol: Allow storing announcements with time 0
There is no apparent reason why announcements with a timestamp of zero
cannot be stored. Avoid panicking.

Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.dev>
2026-10-15 00:16:29 +02:00
Lorenz Leutgeb 9ce88eda3f build/zig: 0.16.0 → 0.17.0
This reverts commit a87e0bd1c8, which
changed from downloading Zig to installing it via `apk`.

The reason to do this back then was because the download failed after
the version bump in 8d50b75826.
However, the reason for the download to fail was that the file name
pattern for the tarballs changed, see
<https://ziggit.dev/t/zig-0-14-1-released/10233/2> and that no
indirection via <https://ziglang.org/download/index.json> was done.

Now, as 0.17.0 is released, and the package in the Alpine repositories
is taking time to be updated, we revert the change, and rewrite the
download step to be more resilient.
2026-10-13 23:56:45 +02:00