3314 Commits
Author SHA1 Message Date
Lorenz Leutgeb 3658ece34f node/test: Clean up
Adjusting visibility and conditional compilation for the modules in
`radicle_node::test` allows some dead code elimination.
2026-07-21 12:32:42 +02:00
Lorenz Leutgeb 8efbf06f78 node: Clean up uses
The re-exports from other crates got quite confusing.

Remove all `pub use` from `radicle-node`, and rewrite all `use`s
accordingly.

The result is a slight increase in the number of `use` lines, but with
improved clarity on which type is actually imported.
2026-07-21 12:31:48 +02:00
Lorenz Leutgeb 9eeecacf28 node: Remove re-export …::node::PROTOCOL_VERSION
It appears that `radicle_node::PROTOCOL_VERSION` (which is a re-export
of `radicle::node::PROTOCOL_VERSION` is the only symbol exported from
`radicle-node` that is used in `radicle-cli` (except tests).

Since `radicle-cli` depends on `radicle` anyway, it is questionable how
much sense that re-export and use from a binary crate (!) makes.

Remove the re-export.
2026-07-21 12:28:10 +02:00
Lorenz Leutgeb f45a96f0e9 cargo/dev: Optimize compute-heavy dependencies 2026-07-21 09:19:01 +02:00
Lorenz LeutgebandFintan Halpenny df6524e7e2 crypto: Rewrite
Reasons for the rewrite:
 - Migrate away from `ec25519` as our implementation of Ed25519,
   because that crate is unmaintained.
   Instead of `ec25519` we use `ed25519-dalek` which is the most popular
   implementation of Ed25519 in Rust.
 - Migrate away from `amplify`, because we make very little use of its
   features. Here, we only use `amplify::Bytes32`, which is just a thin
   wrapper of `[u8; 32]`.
   Instead of `amplify::Bytes32` we use `type PublicKeyBytes = [u8; 32]`.

The realization that `ExtendedSignature` is not really tied to SSH, but
is merely a representation of a key together with a signature, allows to
relax the bounds on traits in `radicle-cob`.

Separating out uses of "public key" (a rather simple model of a key,
just 32 bytes) and "verifying key" (an actual point on the Edwards
curve, ready to verify signatures, using more memory than just 32 bytes)
allows a more sensible definition of `crypto::Signer`, which propagates
to `radicle-cob`, `radicle`, `radicle-cli`, `radicle-node` and
simplifies trait bounds.

In `radicle-node`, make the fact clearer that actually the secret key
itself is required (for our implementation of Diffie-Hellman) not
"just" signing. This results in removal of the generic argument
(usually called `G`) of multiple types.

Concretizing the types in `radicle-node` this way, i.e. by (morally)
setting `G = radicle_crypto::SigningKey`, leads to removal of
`MemorySigner` and `MockSigner`, as they really just wrap a signing
key.

Re-exporting `ed25519::Signature` (via re-export in `ed25519-dalek`)
is hopefully more future-proof than using our own type for signatures.
However, the fact that `ed25519::Signature` is not serializable and
does not implement `std::hash::Hash` means that we have to resort to
ad-hoc solutions in `radicle`:
 1. The Identity COB requires serialization of signatures. These are
    generally questionable, since the security of the Identity COB
    should rely on the security of Signed References. In the medium
    term we might even want to consider ignoring signatures in actions.
 2. When reading Signed References, detection of Replay Attacks uses
    a `HashMap` keyed by signatures. This detection only exists for
    backwards compatibility.
These two use cases are exotic enough to warrant ad-hoc implementations.
By not wrapping `ed25519::Signature`, we encourage writing more widely
compatible code in the future.

Co-authored-by: Fintan Halpenny <fintan.halpenny@gmail.com>
2026-07-21 09:19:01 +02:00
Lorenz Leutgeb 0fd0104e87 radicle, cli/auth: Let binary crate generate seed
In preparation of a rewrite of `radicle-crypto`, move the
responsibility for generating a cryptographically secure
seed from `radicle` (transitively via dependency on
`radicle-crypto` and `ec25519`) to the dependent of `radicle`.

Applications should decide how to obtain randomness, not `radicle` via
some far-removed dependency.

Within the workspace, the only dependent using `Profile::seed` is
`radicle-cli`, in particular for the `rad auth` subcommand. To obtain
random bytes, `radicle-cli` depends on `getrandom` and uses
`getrandom::fill`.
2026-07-17 16:52:33 +01:00
Lorenz Leutgeb 8ceef3c405 crypto/test: Diffie-Hellman Fixture
Before rewriting `radicle-crypto`, add a fixture to have some assurance
on compatibility.
2026-07-17 16:52:32 +01:00
zebus 66ab7f35c9 treewide: Add configurable per-stream fetch timeout
This introduces a `fetch_timeout` setting to the node configuration,
replacing the hardcoded constant. It defines the duration a fetch stream
is allowed to stall before being aborted. The default remains 30s, but
users on anonymizing transports like Tor can now increase it to avoid
spurious timeouts during network congestion.
2026-07-17 17:05:11 +02:00
Lorenz Leutgeb 027de89cdb node/worker/garbage: Clean up
Clippy reports dead code on all variants except `Hours` on `Expiry`,
since the type is defined in a binary crate.

Allow dead code here, just in case we want to use the other variants in
the future. They are not a maintenance burden, as the implementation is
simple and does not see much change.

Slightly refactor:
 - `impl std::fmt::Display for Expiry` to look cleaner.
 - `fn collect` to borrow `RepoId`, since it only needs
   a borrow, and a a borrow of `Expiry` to spare a copy
   at the only call site.
 - The constant for the default value of `Expiry` to be
   associated with the type `Expiry`.

Remove a remark about the default value of `Expiry` from `FetchConfig`,
as the default value is not a concern there, but rather wherever
`Expiry` is initialized.
2026-07-17 11:30:26 +02:00
Robin Heggelund Hansen 188f3041d6 flake.nix: Remove darwin.apple_sdk.frameworks as it is deprecated
More information can be found at <https://nixos.org/manual/nixpkgs/stable/#sec-darwin-legacy-frameworks>.
2026-07-17 11:22:54 +02:00
Lorenz Leutgeb 41d776be41 treewide: Use channels from std, not Crossbeam
For most use-cases, i.e., except some in `radicle-node`, the channels
in `std::sync::mpsc` suffice, allowing us to get rid of the dependency
`crossbeam-channel`.

Crossbeam is sometimes mentioned to offer higher performance compared
to `std::sync::mpsc`.
For usage in `radicle-signals` (and the counterparts in `radicle-node`
and `radicle-term`) performance is irrelevant, as these channels are
used to send a singular message (to the effect of shutting down the
node or stopping the spinner). These could potentially use
`std::sync::oneshot` in the future, which is nightly-only as of
2026-06-17, see <https://github.com/rust-lang/rust/issues/143674>.
Usage in `radicle-protocol` and `radicle`, is a heavier duty.
The channels pass commands to the service (arriving e.g. via the
control socket in `radicle-node`), events from the service to other
listeners (e.g. the control socket), and fetch results from workers to
the service. For user interaction, performance is irrelevant, since
the messages are in the tens or hundreds per second. Even for delivering
events and programmatic usage of commands, thousands to hundreds of
thousands of messages per second are plenty.

Note that these channel implementations are benchmarked in the millions
of operations per second, see e.g.:
 - <https://github.com/crossbeam-rs/crossbeam/tree/b23b7e8eca2efdad9bdc1ceb1aee1207a852c03b/crossbeam-channel/benchmarks#results>
 - <https://github.com/fereidani/rust-channel-benchmarks/blob/b4c979a94482e5012734657c2ee153b528bc56d6/README.md>

Also, `crossbeam-channel` was merged into `std` in 2022-11, and we do
not believe that performance of the implementations has siginifcantly
drifted since then, see <https://github.com/rust-lang/rust/pull/93563>.

One minor downside is that the channels in the standard library do not
expose the number of pending messages.
2026-07-17 10:58:58 +02:00
Lorenz Leutgeb f31fbf40ce rust: 1.95 → 1.97.1
The update to `flake.lock` is a simple

    nix flake update rust-overlay

to be able to reach 1.97.1.

The changes to `**/*.rs` files are improvements suggested by Clippy.
2026-07-17 10:29:56 +02:00
Lorenz Leutgeb 6d846d754d radicle: Mark AddressType as non_exhaustive 2026-07-17 10:17:40 +02:00
Lorenz Leutgeb cff80006a2 protocol: Remove duplicate enum AddressType
We already have `radicle::node::address::AddressType` for the same
purpose.
2026-07-17 10:17:40 +02:00
Lorenz Leutgeb e4775edf41 radicle: Remove dependency amplify
The `amplify` crate is only used in a handful places to derive `impl`s
that are not very complex.

Provide the required `impl`s directly, and remove the dependency.
2026-07-17 10:06:48 +02:00
Lorenz Leutgeb 564c167de1 git2: 0.20 → 0.21
Update `git2` due to
 - https://rustsec.org/advisories/RUSTSEC-2026-0008.html
 - https://rustsec.org/advisories/RUSTSEC-2026-0183.html
 - https://rustsec.org/advisories/RUSTSEC-2026-0184.html
2026-07-16 14:37:32 +02:00
Fintan Halpenny f897cbbf2f protocol: Remove unused radicle-crypto dev-dependency 2026-07-15 13:05:46 +02:00
Fintan Halpenny fa14b68e27 cli-test: Remove unused radicle dependency 2026-07-15 13:05:46 +02:00
Fintan Halpenny ef7d8bb75d node: Remove unused qcheck-macros dependency 2026-07-15 13:05:46 +02:00
Fintan Halpenny 8ad86c68c4 term: Remove unused tempfile dependency 2026-07-15 13:05:46 +02:00
Fintan Halpenny 6e06c05157 schemars: Remove unused serde dependency 2026-07-15 13:05:46 +02:00
Fintan Halpenny 07b838adbf log: Remove unused dependencies
`radicle-term` and `regex` are not required.
2026-07-15 13:05:46 +02:00
Fintan Halpenny 2590fc50cf fetch: Remove gix-refpsec dependency 2026-07-15 13:05:46 +02:00
Fintan Halpenny bdbe1e407b cli: Remove unused dependencies
`tree-sitter` and `pretty-assertions` are no longer used.
2026-07-15 13:05:46 +02:00
Fintan Halpenny 837ee79d28 flake.nix: Add cargo-shear
The [cargo-shear] tool can be used to detect unused dependencies.

[cargo-shear]: https://crates.io/crates/cargo-shear
2026-07-15 13:05:46 +02:00
Lorenz Leutgeb 27e2a9428a node: Refactor Tests
The tests in `crates/radicle-node/src/tests/.rs` repeat code quite a
bit, for example, construction of IP addresses. Also, `Peer` is
unnecessarily complex (too many struct members, state indicated via a
boolean, type parameter that is always instantiated for the same type).

Clean up `Peer` by removing multiple struct members, remove the
unnecessary type parameter, and provide convenience functions that make
statement of the actual tests a little less repetitive and more concise.

For test networks, five standard nodes (Amy, Bob, Cid, Dan, Eve) are all
easy to construct. Tests are refactored to make use of these nodes in
lexicgraphic oreder. The order of initialization may differ.
Modification of configuration is done by passing `FnMut`.

In the future, using the builder pattern could be explored.
2026-07-13 14:23:13 +02:00
Lorenz Leutgeb a079825afc node/reactor: Simplify Transport
1. Properly drain readiness
 2. Remove `WriteAtomic`
 3. Introduce `BufWrite`
 4. Remove flushing
2026-07-09 21:57:14 +02:00
Lorenz Leutgeb 4e22e929d8 node/reactor/session: Protocol::{state → machine}
Even though for the existing implementations all state machines are
somehow named "state", it is less confusing to call the struct member
"machine".
2026-07-09 21:57:14 +02:00
Lorenz Leutgeb 7d5248a89b node/reactor: Remove unnecessary functions/types
The functions
 - `Session::run_handshake`
 - `Session::stream`
 - `Session::disconnect`
are unused.

The associated type `Session::Inner` is unused.

The function `Session::display` is of limited use with little benefit.

`Transport::link_direction` is only used in implementations of
`Debug` and `Display`, but is not helpful otherwise.
2026-07-09 21:57:14 +02:00
Lorenz Leutgeb b5e6d82769 radicle/identity: Tidy up
Small improvements:
 - Remove confusing comments in test
 - Rename `has_active_sibling_accept`
     to   `has_accepted_active_sibling`
 - Small refactoring to avoid `… } else { None }`
2026-07-09 16:26:25 +02:00
Rūdolfs Ošiņš cd57affc36 cargo: Bump zlib-rs to 0.6.5
Fetching a large repository looped with

    Failed to consume the pack sent by the remote

even though the pack appears valid (Git indexes it in full).

The cause appears to be a bug in zlib-rs 0.6.3 according to
<https://github.com/trifectatechfoundation/zlib-rs/pull/504>.
2026-07-09 16:19:33 +02:00
Fintan Halpenny fb80d58af7 CHANGELOG: Describe the changes to repository identity evaluation releases/1.10.0-rc.1 2026-07-09 11:35:07 +02:00
Fintan Halpenny 2f1aaa45b2 radicle/cob/identity: Add sibling-accept invariant to property tests 2026-07-08 15:31:45 +01:00
Fintan Halpenny a63f91885f radicle/cob/identity: Guard IdentityMut::update against sibling proposals
Ensure that the `IdentityMut::update` API returns an error when it encounters an
active, sibling revision by the same delegate.
2026-07-08 15:31:45 +01:00
Fintan Halpenny b6f07074d9 radicle/cob/identity: Guard IdentityMut::accept against sibling accepts
Ensure that the `IdentityMut::accept` API returns an error when it encounters an
active, sibling revision by the same delegate.
2026-07-08 15:31:45 +01:00
Fintan Halpenny 7daeb3135b radicle/cob/identity: Add SiblingAccepted error variant
Add an `ApplyError` for variant, `SiblingAccepted`, to use to enforce the new
invariant: delegates must not accept active, sibling revisions.
2026-07-08 15:31:45 +01:00
Fintan Halpenny 654218e6a5 radicle/cob/identity: Strip implicit author accept for sibling revisions in action
When a delegate creates a new `Revision`, their signature is implicitly added to the revision.
Prevent the delegate from an accept verdict on two active, sibling revisions by:
1. Checking that the `state` is `Active`, and then checking `has_active_sibling_accept`
2. If there is an existing verdict, then it the verdict from the new revision is stripped.

The creation of the `Revision` must still happen, as opposed to throwing an
error, in case of existing histories.
2026-07-03 15:22:13 +01:00
Fintan Halpenny 82dc3fe20c radicle/cob/identity: Enforce sibling-accept invariant for RevisionAccept in action
On `Action::RevisionAccept` use `has_active_sibling_accept` to check for a sibling accept.
If one is found, the `RevisionAccept` is logged and skipped.

This preserves the invariant of a single accept per delegate on active, sibling
revisions without throwing an error on existing histories.
2026-07-03 15:22:13 +01:00
Fintan Halpenny 8e677a9d90 radicle/cob/identity: Add has_active_sibling_accept helper
Add a helper method to check if a delegate has already voted on a sibling
revision.
2026-07-03 15:22:13 +01:00
Fintan Halpenny 62f64744ad radicle/cob/identity: Test accept-before-redact convergence
Companion to the redact-vs-accept test, with reversed timestamp ordering so
accept wins.
2026-07-03 15:22:13 +01:00
Fintan Halpenny f720526fc9 radicle/cob/identity: Test concurrent redact-vs-accept convergence
Verifies that when Alice redacts a revision and Bob concurrently
accepts it, both nodes converge after sync. The CRDT evaluation
order (timestamp-based) determines which operation takes effect.
2026-07-03 15:22:13 +01:00
Fintan Halpenny 21700e4962 radicle/cob/identity: Fix cascading_rejections test to test actual cascading
The test previously had B2 as a sibling of B1 (both children of A1),
because update() always uses self.current as parent. This meant
the test was testing multi-sibling rejection, not cascading.

Now B2 is a child of B1 via a manual transaction, so when B1 is
rejected (sibling E1 accepted), B2 is rejected via RejectedBy::Parent
(cascade), which is what the test name promises.
2026-07-03 15:22:13 +01:00
Lorenz Leutgeb ebf0f417f7 radicle/cob/identity: Deprecate Identity::id
In the heartwood workspace, there is just a single use of this function.
Also, the repository ID can be computed on demand, saving memory.
2026-07-03 15:22:13 +01:00
Fintan Halpenny 97f279fe41 radicle/cob/identity: Test redacting accepted revisions
Add a test to ensure that a previously accepted revision cannot be redacted.
2026-07-03 15:22:13 +01:00
Fintan Halpenny e0e231813f radicle/cob/identity: Test authorization_based_on_parent_not_current
To reflect that autorization is governed by the causal parent of a
revision, not the currently accepted revision.
2026-07-03 15:22:13 +01:00
Fintan Halpenny 108686e299 radicle/cob/identity: Test queued children with delegate change
Ensure that adopting queued children takes into account that the delegate set
can change.
2026-07-03 15:22:13 +01:00
Adrian Duke da2c523d41 radicle/cob/identity: Add Property Tests
Introduce a property testing harness for asserting a set of properties expected
of the repository identity.

The harness uses the `Network` fixture for providing four nodes that can
interact with the repository identity. The state of the harness is advanced by
providing an actor and operation on the identity document. Invariants are then
asserted about the identity.

The current invariants are:
- The `current` revision is the one and only `Accepted` revision.
- The chain of revisions is valid. That is, `Active` revisions only have a
  parent that is `Accepted`, and not `Rejected` or `Redacted`.
- `Active` revisions do not contain a majority approval.
- `Accepted` revisions contain a majority approval.
- `Rejected` revisions do not contain a majority approval.
- For each revision, at most one child is `Accepted`.
- For each revision, if any of its children is `Accepted`, all other
  children are `Rejected`.
- A revision which is `Rejected(RejectedBy::Parent)` has a parent that
  is `Rejected`.
- A revision which is `Redacted(RedactedBy::Parent)` has a parent that
  is `Redacted`.
- For each revision that is `Rejected` or `Redacted`, none of its
  children is `Active`.
- A sibling or ancestor revision's rejected state applies to its sibling or
  descendant.
- The repository identity documents converge when all nodes have applied all
  operations.
2026-07-03 15:22:13 +01:00
Fintan Halpenny aae8938c01 radicle/test/arbitrary: Make BoundedVec shared
A `BoundedVec` that implements `Arbitrary` is useful for all property testing.
Move it from the `sigrefs` module so that it can be reused by other components.
2026-07-03 15:22:13 +01:00
Adrian Duke 94a24a7283 radicle/cob/identity: Fix comments in test reject_concurrent
This incorrectly listed the number of votes and delegates.
2026-07-03 15:22:13 +01:00
Adrian Duke 2768fe79d2 radicle/cob/identity: Test evaluating children of accepted revision
Add a test case to show that when a revision becomes accepted that its children,
which reach a majority vote, are also marked as accepted.
2026-07-03 15:22:13 +01:00