Commit Graph

2425 Commits

Author SHA1 Message Date
Lorenz Leutgeb 819ae5fdf5 node/main: Refactor initialization of logging
It turns out that failing to read configuration resulted in no output,
because the logger would only be set after configuration had been read.

Fix this, by setting the logger early, with a log level that is then
corrected as soon as configuration is available.

While at it, also clean this up to allow logging errors about failure to
set up the logger.
2025-08-27 11:17:18 +02:00
Lorenz Leutgeb bc4a13902c systemd: Clean up default service configurations
Restructure, unify the style of the two files, refer to documentation
instead of repeating part of it.
2025-08-26 20:25:08 +02:00
srestegosaurio 25decf1617 systemd: Add example service hardening
Include some trivial sandboxing options in the provided service files
as an example and lead users to `systemd-analyze security`.

While being a trivial change and far from a secure service it is an
improvement and may push downstream packagers and/or users to add even
a bit of sandboxing.

Signed-off-by: srestegosaurio <lcdt@disroot.org>
2025-08-26 20:25:08 +02:00
Lorenz Leutgeb d7aa2d9da2 flake/hooks: Reconfigure Git Hooks
1. Introduce a new hook to run `cargo doc` in the "pre-commit"
    phase. We have to resort to wrapping the command in a shell
    script, since we cannot configure environment variables via
    `.pre-commit.yaml` but need `RUSTDOCFLAGS` so that `cargo doc`
    actually exists non-zero when there are warnings.
 2. Move `cargo check` from the "pre-push" to the "pre-commit"
    phase, as it is relatively quick, and redundant to run before
    `clippy`.
 3. Let `cargo check` and `cargo doc` run after `rustfmt`.
 4. Let a failure of `rustfmt` fail the whole check.
 5. Wire through `packageOverrides` to `rustfmt` so we use the
    same toolchain as the other tools.
 6. Additionally enable `cargo clippy` to consider all features.
2025-08-25 21:51:01 +02:00
Fintan Halpenny 7d2f0e387c node: e2e test for canonical ref update
Adds a test to ensure that canonical reference updates are received on the node
events stream.

Added a helper to `NodeHandle` for perfoming a commit to a reference and signing
the nodes references, to help with this use case.
2025-08-25 18:49:43 +01:00
Fintan Halpenny 0d96af5d07 node: update default branch for canonical refs
After a fetch is performed, `set_head` is called, which essentially updates the
default branch based on the canonical reference rule for that branch.

Later, `set_canonical_refs` is called, which originally could be the empty set
of rules. This would mean that the event for the default branch would never be
emitted.

The approach taken here is to always use the default branch rule if there are no
rules set in the identity document. Unfortunately, this ends up in computing the
default branch twice, due to `set_head`.

Another approach is to use the result of `set_head` to update the set of
`UpdatedCanonicalRefs`, to prevent the need for defaulting to the default branch
rule. However, this does not prevent the double counting as soon as the rules
are added to the identity document, since the default branch rule will then be
synthesised into the rule set.
2025-08-25 18:49:43 +01:00
Fintan Halpenny c38b9d9e1e node: simplify canonical reference calculation
The logging version was verbose, and not required. It also could end up double
processing canonical reference updates since multiple remotes may update the
same reference.

Instead, use `filter_map` to convert all the reference names, stripping
namespaces, and collect them into a `BTreeSet` to ensure uniqueness.
2025-08-25 18:49:43 +01:00
Fintan Halpenny a8255a2e07 Introduce a node event for canonical reference updates
Whenever the node fetches new updates, it checks if canonical references can be
updated. The node has learned how to return these results and emit them as node
events. This is a breaking change since it adds a new variant the `Event` type,
which is not forwards-compatible.
2025-08-25 18:49:43 +01:00
Fintan Halpenny 690f6b02c0 remote-helper: remove check for fast-forward
Since the commit is now always pushed to the Radicle storage, there is no need
to check for fast-forward. If heads diverge, it will be reported by the quorum
error.
2025-08-25 18:57:50 +02:00
Lorenz Leutgeb 119a124897 radicle: the great Canonical rewrite
This change was inspired by the a story as old as time:

    𝕃𝕖𝕥 𝕦𝕤 𝕞𝕚𝕩 𝕠𝕦𝕣 𝕓𝕦𝕤𝕚𝕟𝕖𝕤𝕤 𝕝𝕠𝕘𝕚𝕔 𝕨𝕚𝕥𝕙 𝕠𝕦𝕣 𝕀𝕆!

It was motivated by the fact that the canonical quorum logic was spread across
two modules and also two different repositories (in the API sense). The
`radicle-remote-helper` contained special logic for computing the quorum, and
also relied on the logic with `radicle` itself. It would also mix using the
Radicle storage repository and the working copy repository – resulting in issues
where objects could exist in one and not the other.

The change begins by separating away the IO away from any of the business logic
in the `git::canonical` module. To follow along, there are two important submodules:

1. `voting` captures the different type of voting processes for commits and tags
  a. Tags are simple, where one `Oid` means one vote
  b. Commits are slightly more complicated. They begin with one `Oid` means one
     vote, but then it is expected that merge bases are calculated for pairs of
     commits. These merge bases are used to increase a vote for an `Oid` if it
     is the merge base of another commit.
2. `quorum` builds on top of `voting` and uses the voting processes as well as
    the `threshold` to find the quorum for the tag or commit reference.
  a. For tags, the first past the threshold wins, but if multiple pass then it
     is an error.
  b. For commits, the merge base process should be used to increase the votes,
     until the caller is ready to find the quorum. At this point, the commits
     that pass the `threshold` are then compared to find the commit that is the
     child-most commit of all other candidates. If they diverge, then an error
     is returned.

There is also a `convergence` module that captures the logic that is required
for the `radicle-remote-helper`. It essentially checks if a candidate object
matches the expected objects, tags or commits, and performs the necessary
convergence logic – checking that the candidate commit is converging with
at least of the other `Did`s.

These two quorum processes, and the convergence process, essentially act as
state machines and can be driven by the use of a Git repository for finding the
merge bases. This is where the `effects` module comes in. The `effects` capture
the necessary traits that are required to drive the state machines of the quorum
processes. It is expected that a Git repository implements these, and in fact, a
`git2::Repository` implementation is provided. The traits are useful, since it
means that a `git2::Repository` can be swapped out and the logic would stay the
same.

This all culminates into the new and improved `Canonical` and
`CanonicalWithConvergence`. Both of which have methods `find_quorum` for
performing the quorum process using a *single* provided repository.

This resulted in the semantic change of requiring that the
`radicle-remote-helper` pushes the candidate commit, irregardless of whether it
will be a fast-forward. For now, this is something that will be accepted while
the UX can be improved in the future by providing detailed warnings of the
divergence and ways to fix it. The benefit is that the tooling will never stop
someone from diverging if that is in fact what they want to do.
2025-08-25 18:57:50 +02:00
Lorenz Leutgeb 8a66e4d044 node/systemd: Check that received sockets are AF_UNIX
Our implementation for the control socket is based on AF_UNIX, and as
the note (which is removed) suggests, we should actually check that the
received socket really is of that domain.

This check was not implemented because it is not exposed via `std`, and
a bit cumbersome to do via `libc`.

I did not realize that `socket2` which neatly sits between `std` and
`libc` in terms of its abstractions and is cross-platform allows us to
do this, and we even already depend on it!

So, add the suggested check.

While at it, refactor the function to return early in cases. Now the
progression from a file descriptor to a socket to a listener is nicely
captured in the types and not obstructed too much by indentation.

Also log at the error level.
2025-08-25 17:17:34 +01:00
Lorenz Leutgeb 192cc993a8 radicle: Fix panic when reading from SQLite database fails
I was greeted by `rad patch redact` with

    called `Result::unwrap()` on an `Err` value:

      Error { code: None, message: Some("failed to convert") }

    stack backtrace:

    […]
       3: core::result::Result<T,E>::unwrap
             at …/rust-1.88.0/lib/rustlib/src/rust/library/core/src/result.rs:1137:23
       4: sqlite::cursor::Row::read
             at …/index.crates.io-1949cf8c6b5b557f/sqlite-0.32.0/src/cursor.rs:136:9
       5: radicle::cob::patch::cache::query::find_by_revision
             at ./crates/radicle/src/cob/patch/cache.rs:624:65
       6: <… as radicle::cob::patch::cache::Patches>::find_by_revision
             at ./crates/radicle/src/cob/patch/cache.rs:553:9
       7: radicle_cli::commands::rad_patch::redact::run
             at ./crates/radicle-cli/src/commands/patch/redact.rs:23:9
       8: radicle_cli::commands::rad_patch::run
             at ./crates/radicle-cli/src/commands/patch.rs:1026:13
    […]

It turns out that `sqlite::cursor::Row::read` is the panicky version of
`sqlite::cursor::Row::try_read` (which returns a `Result`).

While it is somewhat rare that SQLite reads fail, it is not unheard of.
In `radicle-cli` it might not be critical, but also `radicle-protocol`
and `radicle-fetch` are affected, and they could potentially panic a
`radicle-node` process.

Use `try_read` instead, and propagate down the error handling.
2025-08-25 17:05:21 +01:00
Lorenz Leutgeb a568e7f484 protocol: Refactor decoding
Remove `fn deserialize` and instead provide a trait `fn decode_exact`
for testing.

Refactor errors, so that we can now cleanly distinguish between
`Error::UnexpectedEnd` which means that decoding might work given
more data, and `Error::Invalid`, which means that no amount of
additional data will decode.

While at it, cleanup the variants of `Error::Invalid`, and provide `ControlType`.
2025-08-25 11:57:44 +01:00
Lorenz Leutgeb cbd2a7070c protocol: Refactor encoding
What made the issue fixed in the parent commit harder to spot
for me was the multitude of concepts and terms involved in
encoding: What's *serializing* vs. *encoding*? Why is there
this odd `Frame::to_bytes`?

So I decided to clean up a bit. I removed `fn serialize` so that the
logic stays close to the data, and instead of it provide
`fn encode_vec` in `trait Encode` so that we don't add up with
one-offs like `Frame::to_bytes` again.

Also we make use of `encode_vec` in the tests.
2025-08-25 11:57:44 +01:00
Lorenz Leutgeb a8426dfdac protocol: Fix panic when serializing large frames
In 3c5668edd2 I mistakenly moved the check
the length of an encoded `wire::Message` from its `impl wire::Encode` to
the `fn serialize`, so that it would not apply only to messages (as
originally intended), but to *any* argument of `serialize`.

This is not a problem for gossip messages, but catastrophic for Git
streams, which tend to send a lot of data.

The fix is simple: Move the check back into `impl Encode for Message`.
2025-08-25 11:57:44 +01:00
Lorenz Leutgeb c5b99db101 doc: Oversight when integrating winpipe 2025-08-25 10:10:52 +02:00
Lorenz Leutgeb a4d83ec8d2 node/control: Please the borrow checker
This only surfaced after building again on Windows.
2025-08-24 20:04:00 +02:00
Lorenz Leutgeb fca96e697f node: Fix test on Windows
Small oversight in previous commit, as mutability of course also needs
to change in tests.

This only surfaced after building again on Windows.
2025-08-24 20:03:39 +02:00
Lorenz Leutgeb 19a262d3d6 node: Use winpipe for control socket on Windows
As `std::os::unix` is obviously not available on Windows, resort to the
`winpipe` crate, which implements a very similar API for named pipes.

Apart from simple changes to imports, we need to jump through a few
hoops because the `std::io::{Read, Write}` impls are on `&mut` for
`winpipe`, thus yielding different mutability requirements on Unix vs.
Windows.

We resort to cloning, but as this fallible, swallow the added complexity
of a platform-dependent implementation to not risk panics on Unix.
2025-08-22 15:45:20 +01:00
Lorenz Leutgeb ce11f03fe6 radicle/profile: Control socket path for Windows
The `winpipe` crate which is our current best bet for support on Windows
requires that named pipes are at the magical location `\\.pipe\`.

Provide a default on Windows that matches this, and use our freshly
assigned IANA service name for it.

This is a breaking change, as we remove a `pub const`, it should be
reasonably easy to fix by dependents, and probably it was a mistake to
make the default `pub const` as dependents should call `Home::socket`
anyway.
2025-08-22 15:29:08 +01:00
Lorenz Leutgeb fd34b680b5 signals: Guard most of the crate for Unix
The signals crate does not build on non-Unix-like platforms, such as
Windows. It uses constants for signals that are not exported from
`libc` on Windows, and transitive dependencies such as `sem_safe` fail
to compile.

Resolve this, by guarding most of the crate, certainly all real
features, by `cfg(unix)`.

To require less uses of the `cfg` macro, move all affected parts into a
new module, and re-export it.

The crate now builds on Windows (although it does not do anything
interesting on that platform) and its Unix interface remains unchanged.

Changes are integrated into `radicle-node`, but only affect Windows.
2025-08-22 15:19:31 +01:00
Lorenz Leutgeb 5229fb8a59 fix: Normalize filesystem paths with `dunce`
Microsoft Windows has Universal Naming Convention (UNC) paths, see
<https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats#unc-paths>

The Rust standard library normalizes paths to this form when calling
`std::fs::canonicalize`, see
<https://doc.rust-lang.org/std/fs/fn.canonicalize.html>

However, some programs, do not parse these paths correctly, including
our ally `git`:

    $ git clone \\?\C:\Users\lorenz\tmp
    Cloning into 'tmp'...
    ssh: Could not resolve hostname \\\\?\\c: No such host is known.
    fatal: Could not read from remote repository.

    Please make sure you have the correct access rights
    and the repository exists.

The `dunce` crate solves the problem of having to deal with
normalization and avoiding weird UNC paths for us. Note that on
non-Windows platforms, it *behaves exactly like*
`std::fs::canonicalize`!
2025-08-22 15:11:43 +01:00
Lorenz Leutgeb 31039bbceb radicle-fetch: More fine-grained errors
The `fn io_other` disguises all sorts of errors as I/O errors, making
errors in the transport component of `radicle-fetch` harder to
understand.

Instead, expose a new error type that allows to discriminate.
2025-08-22 14:56:03 +02:00
Lorenz Leutgeb d2517c5004 radicle-fetch: Fallback to known version
There's a good chance that gix can just keep going in this case.
2025-08-22 14:55:19 +02:00
Fintan Halpenny a670b6e668 radicle: fix stream tests
The stream tests did not take into account that the tips of previous COB entries
need to be inserted so that a Git history can be formed.

This was surfaced by the added regression test.
2025-08-20 11:43:18 +01:00
Fintan Halpenny 0c6ff06c65 radicle: use repository fixture in cob stream tests
If the tests are run without a global Git config, then the tests will error with
no name or email set for the commits.

Instead, use the test fixture setup to ensure there is a name and email for
running the tests.
2025-08-20 10:45:57 +01:00
Sebastian Martinez 1d7478cd90 radicle: introduce `cob::common::Title`
This patch adds the `cob::common::Title` struct, this allows instead of
using `std::str::String` or generics and traits for it, to define more
precisely what a title should be.
It trims the provided string and makes sure it contains no carriage
return or new line characters.

Where a `std::str::String` makes more sense so it's easier to mutate it,
we keep the code as is.

Co-authored-by: Fintan Halpenny <fintan.halpenny@gmail.com>
2025-08-19 17:05:33 +02:00
Fintan Halpenny 2a0f6fd3c5 cli: extend `rad cob log` behaviour
Use the COB stream behaviour for implementing `rad cob log` and introducing the
from and until options.
2025-08-19 15:01:26 +01:00
Fintan Halpenny 044ff8adde radicle: introduce specialised Stream types
Add type aliases to specialise the `Stream` type for each of `Patch`, `Issue`,
and `Identity`.

An `init` constructor is also added for each to make it easier to construct each
stream.
2025-08-19 14:49:51 +01:00
Fintan Halpenny c0ac228c37 radicle: introduce COB stream
Introduce a new `stream` module to `radicle::cob`. The purpose of this module is
to provide an API for iterating over a COB's operations, given a range of
commits.

The `CobStream` trait provides the generic API for implementing and using, while
`Stream` provides a concrete implementation using the `git2` crate.

The approach is to use a `git2::Revwalk` for walking over each commit in the
range, then loading the `Op` for that commit – skipping any commits that are
unrelated to that COB type, in the case of associated COBs.

The `Stream` is tested using some properties of the API.

The next steps are to introduce specialised types for `Patch` and `Issue` to
ensure that it works for those.
2025-08-19 14:49:51 +01:00
Fintan Halpenny 9b59c0e2c6 radicle: add Op::load method
Adds an `Op::load` helper method for loading an `Entry`, given its `Oid` and the
`store` it is stored in.

This can be useful for downstream consumers to inspect operations on COBs given
a single `Oid`, without having to load the entire object and/or graph.

It can also be useful when looking at implementing COB stream primitives.
2025-08-19 14:27:35 +01:00
Fintan Halpenny dbfcf424d6 radicle: add Op::manifest_of
Add a helper method `Op::manifest_of`, to allow the caller to inspect the
`Manifest` of the entry. This can be used to avoid attempting to call `Op:load`
with a manifest that would not match the expected `Action`s of the `Op`.
2025-08-19 14:27:35 +01:00
Fintan Halpenny bfe8c5234f cob: add method for loading entry manifest
Adds a helper for only loading the manifest of a COB entry. This allows callers
to inspect the manifest without loading the whole entry.
2025-08-19 14:27:35 +01:00
Fintan Halpenny 86119473b7 test: add alphanumeric generator
Add an `alphanumeric` function for generating alphanumeric strings.
2025-08-19 14:27:35 +01:00
Matthias Beyer 8953ec4c87 cli/inbox: Clearify "clear" behaviour
This patch clearifies (in the help message of the command) how the
"clear" subcommand behaves when passing ids.

Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-08-19 12:46:07 +01:00
Lorenz Leutgeb 9a7c22536b radicle/config/node: Use newtypes 2025-08-19 13:16:19 +02:00
Lorenz Leutgeb c4ff0d8eae radicle/config/node: Granular Default Values
Provide default values not only per struct, but for each individual
struct member. This allows more granular configuration and fallback to
defaults.
2025-08-19 13:16:19 +02:00
Matthias Beyer 77f63c76df Replace unit-returning-closure with drop()
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-08-19 10:57:37 +01:00
Matthias Beyer cb7c748c66 Move destructuring to fn signature
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-08-19 10:57:37 +01:00
Matthias Beyer 9486e751f2 Simplify impl of Display for some types
Micro-optimization, I know. ;-D

Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-08-19 10:57:37 +01:00
Matthias Beyer 787cb3a872 Collapse nested if
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-08-19 10:57:37 +01:00
Matthias Beyer b0af48aa03 To not allocate in good case
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-08-19 10:57:37 +01:00
Lorenz Leutgeb 0200e84aea bootstrap: Add IPv6, IPv4, Onion addresses
This should allow bootstrapping when DNS is not available or a
connection via Tor is desired.
2025-08-18 17:39:20 +02:00
Matthias Beyer 6a1b13bb66 cli: Use human-panic
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-08-17 20:53:53 +02:00
Matthias Beyer f6f3be437b cli/issue: Optimize how the issues are collected
This patch optimizes how the issues are collected.
Before, the issues where pushed to a new instance of `Vec`, which was
not preallocated to the size of the iterator.
This could be slow when a large number of issues is returned by the
iterator.

With the `Iterator::collect` function, the `FromIterator` implementation
of `Vec` is used, which makes use of `Iterator::size_hint` for
optimizing how much space is preallocated when collecting into the
`Vec`.

Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-08-17 20:22:56 +02:00
Matthias Beyer b49ff9e5af radicle: Implement Iterator::size_hint() for optimizations
This patch adds an implementation of the Iterator::size_hint() trait
function so that the compiler can optimize allocations on the collecting
side of the iterator better.

From the trait function docs:

> size_hint() is primarily intended to be used for optimizations such
> as reserving space for the elements of the iterator, but must not be
> trusted to e.g., omit bounds checks in unsafe code. An incorrect
> implementation of size_hint() should not lead to memory safety
> violations.

Source: https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.size_hint

And:

> Vec may use any or none of the following strategies, depending on the
> supplied iterator:
>
>  *  preallocate based on Iterator::size_hint() and panic if the number
>     of items is outside the provided lower/upper bounds

Source: https://doc.rust-lang.org/stable/std/iter/trait.FromIterator.html

Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-08-17 20:22:56 +02:00
Matthias Beyer 1e66c57643 refactor: Replace return Err(anyhow!()) with anyhow::bail!()
This is equivalent to before, but reduces cognitive load when browsing
the source.

Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-08-17 20:17:11 +02:00
Fintan Halpenny 01bed73a60 cli: fix error formatting
Missed using `format!` to actually emit the error when the sync fails.
2025-08-15 11:40:25 +01:00
Lorenz Leutgeb ed5b2659c8 cli: Gracefully handle failure to link log file
Not linking `node.log` is not ideal, but also not a critical failure.
It's better to swallow that instead of crashing.
2025-08-15 10:13:51 +01:00
Lorenz Leutgeb de78cf7874 cli: Add verbose printing of authors
To allow users to see the full DID of authors of issues, introduce a
boolean to the `Author` struct and wire it through.

While doing that, also remove `--debug` options from `rad issue` and
`rad patch` as they have been obsolete since the introduction of `rad
cob [log|show]` for quite some time.

See:
 - https://radicle.zulipchat.com/#narrow/channel/369873-support/topic/help.20with.20seeing.20full.20nid/with/531390047
2025-08-14 16:15:19 +01:00