Commit Graph

105 Commits

Author SHA1 Message Date
Yorgos Saslis d36bf41f25 radicle/node/db: Directly represent SQLite pragmas
Instead of introducing our own names and aliases, directly model SQLite
pragmas with the defaults that they also take in SQLite, to avoid
confusion.

Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
2026-03-09 09:04:55 +00:00
Fintan Halpenny f018b434cb node: control debug serialization of FetcherState
Use proxy structs to control the serialized output of `FetcherState`.

These structs convert data inside the `FetcherState` into friendlier
output for the caller of `rad node debug`.
2026-03-05 15:29:42 +01:00
Fintan Halpenny 9ea1ea2433 Revert "node/debug: Use derived serializers"
This reverts commit 5099c25df7.

The `HostName` of the `RateLimiter` could serialize as an invalid JSON
key, resulting in the command panicking.
2026-03-05 15:29:42 +01:00
Fintan Halpenny c0ae5e32d9 node: Update to 0.17.0 2026-02-19 21:06:43 +01:00
Lorenz Leutgeb 30701cc6fb node/runtime: Make `Runtime::run` more readable
A small refactoring that reorders arms of a match and makes them shorter
by precise imports.
2026-02-18 09:36:49 +00:00
Lorenz Leutgeb 057edf55b8 node/reactor: Introduce `LAG_TIMEOUT`
Since the service performs further I/O (e.g. uses SQLite), it can keep
the reactor runtime thread busy for long periods. Emit a warning if that
is the case.

100 ms is quite relaxed, this is to only catch severe cases and avoid
spamming the log.
2026-02-18 09:36:49 +00:00
Lorenz Leutgeb ae06111e07 node/reactor: Rewrite `Runtime::run`
More commentary and slight refactoring in `Runtime::run` to improve
readability.
2026-02-18 09:36:49 +00:00
Adrian Duke d88ef3fa66 e2e: Introduce 3 tests for block command
1. It disconnects an active connection.
2. A blocked peer can not connect to us.
3. A blocked peer can not be fetched from.
2026-02-17 16:12:04 +01:00
Fintan Halpenny a4806f2718 node/test: check remote events for `Event::PeerDisconnected`
The node being connected to may be blocking the connecting node.
In this case the service will emit an event that says the connecting
node is disconnected.

Check the events for this event as well as `Event::PeerConnected`.
2026-02-17 16:12:04 +01:00
Adrian Duke 94e0a5128d node: Add block command to control socket
Introduces the ability to explicitly block a peer via the node control
socket. Previously, the node only exposed follow and unfollow commands.
While the underlying policy database schema supported a Block variant,
there was no mechanism to trigger this state via the client handle.

The new block command:

 1. Updates the node's follow policy to Block.
 2. Immediately disconnects the peer if a session is active.
2026-02-17 16:12:04 +01:00
Lorenz Leutgeb 5fa68ed814
node: Use `gix_packetline`
`gix-packetline` already is in the dependency closure of `radicle-node`.
Use the well-tested and -used reader instead of ours.
2026-02-16 21:42:59 +01:00
Adrian Duke c96aea0699 node/e2e: Fix test_non_fastforward_identity_doc
This test was flakey.
2026-02-16 21:39:11 +01:00
Adrian Duke b5e8776efd fetch: Improve `refs/rad/id` resolution
The previous resolution would favour one `Oid` over another, due to
the call to `Option::or`.

This was found to result in scenarios where an identity document being
advertised was older than the identity document that the fetching node
already has, and would not fetch namespaces by newly added delegates.

The resolution logic is updated to handle all cases for these `Oid`s,
and when they are both present, uses a commit graph check to see which
one is the latest.

An e2e test is added to check ensure behaviour works correctly.
2026-02-16 21:39:11 +01:00
Fintan Halpenny 84320919e2 node/tests/e2e: use assert_eq! in test
The use of `assert_eq!` will highlight the differences in the two
values when it fails.
2026-02-16 17:13:39 +00:00
Fintan Halpenny 980ed56186 protocol/service: rename `Responder::new` to `Responder::oneshot`
The `Responder` is a oneshot channel where it sends one result and the
other side receives that result.

This surfaced that the `AnnounceRefs` command was returning one result
at a time. At the moment, this is only used for a single namespace,
the local user's.
2026-02-16 13:33:00 +01:00
Fintan Halpenny 0d628a45e2 protocol: refactor to use Responder
Refactors every command that uses a `Sender` to use a `Responder`
instead. There is an exception for `QueryState` since its usage
differs slightly to the other cases.

To make the usage more ergonomic, constructors for the variants that
require a `Responder` are added.
2026-02-16 13:33:00 +01:00
Fintan Halpenny 2c1972151d protocol: opaque command error
Refactor `CommandError` to be an opaque, boxed error.
Rename it to `Error`, so that it is imported via `command::Error`.
2026-02-16 13:25:58 +01:00
Lorenz Leutgeb 5099c25df7 node/debug: Use derived serializers
The construction of the debug object is unwieldy, and error prone
(for example, renamed struct members have to be manually renamed
in the serialization code, see "refs" vs. "refsAt").

Use derived serializers where possible to make this easier to maintain.
2026-02-16 09:43:38 +00:00
Lorenz Leutgeb d530f1265e node/test: Remove unused file `environment.rs` 2026-02-16 09:38:37 +00:00
Lorenz Leutgeb 730d696d74 node/test: Bind to loopback interface 2026-02-16 09:38:37 +00:00
Fintan Halpenny 2d0db3c6b2 CVE-2025-58160
Report: <https://www.cve.org/CVERecord?id=CVE-2025-58160>

This vulnerability was introduced via the `test-log` crate.

Updating to `0.2.19` in turn updates `tracing-subscriber` to `0.3.22`
which is in the acceptable upgrade range.
2026-02-13 17:26:50 +00:00
Lorenz Leutgeb 03bbe52417 signals: Add support for Windows 2026-02-13 15:38:02 +00:00
Lorenz Leutgeb 865abd35e2
protocol/fetcher: Intro `RefsToFetch`
Using empty `Vec`s to mean "all refs" can be confusing. Avoid the chance
of confusion by introduction of a new type that clearly describes which
refs should be fetched.
2026-02-13 15:27:21 +01:00
Lorenz Leutgeb 9081c11b98
protocol/fetcher: Remove `from` from `QueuedFetch` 2026-02-13 15:25:42 +01:00
Fintan Halpenny 993428df56 node: donwgrade log error to trace for disconnection
This is would be a very noisy error since disconnections should be
expected to happen frequently for nodes.

Downgrade the log level to trace.
2026-02-12 14:35:13 +00:00
Lorenz Leutgeb ebf7d876b4
node: Control via `uds_windows` not `winpipe`
Recent versions of Windows support Unix Domain Sockets natively, see
<https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/>.

By using that feature instead of Windows named pipes, the difference
for handling communication via the control socket comparing Unix-like
systems and Windows becomes smaller:

 1. No special paths like `\.\pipe\…` have to be handled.
 2. Not two I/O mechanisms are abstracted (named pipe and UDS) but
    just one.
 3. `winpipe` relies on background threads while `uds_windows` does
    not.

Once the feature `windows_unix_domain_sockets` (which is tracked at
https://github.com/rust-lang/rust/issues/150487) stabilizes, it will
likely be possible to just drop the dependency `uds_windows` and use
the implementation in `std::os::windows::net` directly.
2026-02-12 10:18:48 +01:00
Lorenz Leutgeb 90cf37c471
node: On Windows, use job for upload-pack child
Reports of zombie (grand)child processes were received. By associating
the `git upload-pack` process with a job, zombies are prevented.
2026-02-11 16:37:56 +01:00
Fintan Halpenny 1cab036c33 protocol/service: Wire up `FetcherService`
Wire up the new `FetcherService`. This reduces the `Service` fetch
methods down to:
- `fetch`
- `fetched`
- `dequeue_fetches`
- `fetch_refs_at`

This simplifies the code by off-loading the logic to the `fetcher`
family of types, rather than handling the intricacies in the `Service`
itself.

The `Service` now just performs the necessary I/O based on the
returned events from the `fetcher`.

This also removes the fetching state from the node sessions. This
follows the single responsibility, and sessions now only care about
their connectivity.

Breaking changes:
- The `Connected` state of a peer no longer contains fetching
  information, which was being returned as part of the JSON payload
  result.
- The `rad debug` information for ongoing fetches contained the number
  listeners awaiting for results, this was removed.
2026-02-05 20:04:28 +01:00
Fintan Halpenny 6d1abd2833 node: downgrade `error!` logs
Evaluate the `error!` logging in `radicle-node`.

The majority of cases, the `error!` is downgraded to `warn!`. These
cases are generally useful to know if there's an issue, but the
operator cannon necessarily do anything about it.

In a few cases, `debug!` was chosen. These are generally when an error
means that a result is not part of an accumulation, or the system will
eventually correct itself during another event or restart.
2026-01-19 17:59:06 +01:00
Fintan Halpenny b020543c4b node: downgrade `warn!` logs
Evaluate the `warn!` logging in `radicle-node`.

If the event is a result of something being suspicious in the running
of the protocol, then it remains a `warn!`.

However, if the system ends up ignoring the event and can continue
without issues, the log level is reduced to `debug!`.
2026-01-19 17:59:06 +01:00
Fintan Halpenny eeadffa636 node: update comment
The comment mentions a variable `needs_flush`, which is not `write_intent`.
2026-01-19 17:56:56 +01:00
Adrian Duke 001aba0d29 clippy: Deny and fix `fallible_impl_from`
Instead of changing `impl From<SystemTime> for LocalTime` to `TryFrom`,
the implementation was removed, as there are not many callsites, and
they are well served by `LocalTime::now`.

radicle-localtime: drop TryFrom SystemTime

radicle-node: switch SystemTime usage with LocalTime
2026-01-16 22:52:18 +01:00
Fintan Halpenny d5fea6324c core: Introduce RepoId
Introduce the `radicle-core` crate for housing data types that are at
the core of the Radicle protocol. The initial data type being added is
`RepoId`. To make the crate as lightweight as possible many of
dependencies are optional and gated by feature flags.
The crate is marked as `no_std` even though it unconditionally depends
on `radicle-crypto`, which is *not* `no_std`. This is to invite
refactoring of `radicle-crypto` to `no_std` at a later time.
2026-01-09 14:03:27 +01:00
Fintan Halpenny d98033a1ff localtime: localise the localtime dependency
The `localtime` crate was defined by cloudhead, and is a minimal
repository with a single `lib.rs`.

Instead of using it as an external dependency, copy the code directly
into a new workspace crate `radicle-localtime`.

The default `serde` implementation goes through the `LocalTime`'s
seconds values rather than milliseconds, since this is the more common
format. This allows the removal of the `serde_ext` functions.
The one place milliseconds was used was for the
`radicle::cob::common::Timestamp` type, where the `Serialize` and
`Deserialize` implementations are manually written.

It also adds a `schemars` feature to remove `schemars_ext` functions
in `radicle` as well.
2026-01-08 14:58:41 +01:00
Fintan Halpenny 75b665ff3a node/wire: manage logs for error establishing connection
Instead of logging all IO errors as an error, match on the kind of the
error, logging an info message, otherwise an error.

These errors are informational, and cannot necessarily be resolved by
the node operator – since it is a matter of not being able to connect
to another end.
2026-01-05 17:57:34 +01:00
Fintan Halpenny a46f8eb1d5 node: use `outbound.get` over `outbound.get_mut`
The following block did not need mutable access to the `outbound`
value.

To avoid confusion around mutable access, the call was downgraded to
`get` so that the reader can safely know there is no mutation
happening.
2026-01-05 17:45:54 +01:00
Lorenz Leutgeb 4a5a51e6e5
node: Do not mix monotonic and system time
The `struct Epoch` introduced in e404f1038f
inadvertently made `radicle-node` suffer from
https://github.com/rust-lang/rust/issues/87906

That is, updating of the "current time" was skewed *way more*
than expected by a slowly ticking monotonic clock. Such slow
ticking can be caused by suspending the system.
2025-12-04 20:45:51 +01:00
Yaroslav Halchenko 32d77641ab chore: Fix spelling errors with codespell 2025-11-01 12:11:02 +01:00
Lorenz Leutgeb 8e331ce182
node/reactor/transport: Implement `Debug`
The derived implmenetation prints read and write buffers, which is too
much even for trace logging (where we use this trait).
2025-11-01 11:20:43 +01:00
Lorenz Leutgeb e404f1038f node: Use `std::time` for reactor and wire
This reduces the exposure to the `localtime` crate, using
`std` instead.
2025-10-29 09:05:52 +00:00
Adrian Duke 5ed1b8e579 node: Fix test 'test_concurrent_fetches'
The test 'test_concurrent_fetches' is broken. It asserts over empty
sets nullifying its utility. Correct the test by changing it so that
sets to be asserted against are cloned.
2025-10-22 17:11:23 +02:00
Lorenz Leutgeb b0beef4391
workspace/rust/clippy: Fix all warnings
Fix clippy warnings following the upgrade of the Rust toolchain to
version 1.90. Almost all are "mismatched_lifetime_syntaxes".
2025-10-17 12:56:43 +02:00
Lorenz Leutgeb 0b3424857e node/reactor: Correctly handle error events
`fn handle_events` would panic, if there were multiple events for one
token, and the first one that happened to be handled was an error.
Indeed it is concerning if a token is encountered that was never
registered before. However, tokens that were just deregistered must be
tracked.

Using `Vec` here seems a bit costly, in the future,
`smallvec::SmallVec` could be considered.

The "unregister" methods are renamed to "deregister" to better line up
with `mio` vocabulary.

Log statements that helped analysis of the panic that occurred here
are overhauled and improved, requiring a `Debug` bound on types that
obviously implement it.
2025-10-15 10:42:27 +01:00
Lorenz Leutgeb b7cfcfff75
workspace: A Little Less `git2`
Make `git2` an *optional* dependency of `radicle-cob`, and refactor
`radicle` to depend on crates that in turn do not depend on `git2`
*non-optionally*

The main offending dependency of `radicle-cob` is `radicle-git-ext`
from the `radicle-git` workspace in repository
(rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt) which *non-optionally* depends on
`git2`.

So, to achieve removal of this dependency:

 1. The crate is refactored to depend on the new crates
    `radicle-git-ref-format` `radicle-git-metadata`, and
    `radicle-oid` introduced in the previous commits, instead of
    `radicle-git-ext`.
 2. Some code from the `radicle-git-ext` crate in the `radicle-git`
    workspace in repository (rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt) is
    copied. See `crates/radicle-cob/src/backend/git/commit.rs`.

This cascades to `radicle` and its dependents.

  Firstly, the there is an
`impl Deref<Target=git2::Oid> for radicle_git_ext::Oid`. This made
it very convenient to just deref to obtain the wrapped `git2::Oid`,
so there are many expressions of the shape `*oid` in `radicle` and
its dependents. However `radicle-oid` does not provide
`impl Deref<Target=git2::Oid> for radicle_oid::Oid`, as notably,
`Target` is an associated type, and not a type parameter, so an
implementation of `Deref` would tie the new `Oid` too tightly to a
particular implementation.
Instead, work with `impl From<radicle_oid::Oid> for git2::Oid`
(which can be enabled using the feature flag `radicle-oid/git2`).
This explains the changes from `*oid` to `oid.into()` at every
transition to "`git2` land".

  Secondly, `radicle` and its dependents are refactored to also depend
on `radicle-git-ref-format` and (much less prominently)
`radicle-git-metadata` instead of `radicle-git-ext`
This is to avoid pulling in `git2` via these dependencies.

  Thirdly, as the re-exports in `crates/radicle/src/git.rs` change,
they are at the same time also cleaned up. Notably, the types from
`radicle-git-ref-format` are re-exported under `fmt` only, not "twice".

While overall this obviously is very much a breaking change, these
changes should mostly amount to changing from `Deref` to `Into`, i.e.,
`*oid` → `oid.into()` and rewriting imports for `radicle::git::fmt`.
This is indicated by the mostly mechanical nature of the changes to
`crates/radicle-{cli,node,remote-helper}`.
2025-10-13 16:14:23 +02:00
Lorenz Leutgeb 633059040c node/wire: Refactor precedence
No change in the logic here, just making this slightly easer to read and
understand.
2025-10-10 09:45:05 +01:00
Lorenz Leutgeb cb7bed5562 node: Remove dependency on amplify 2025-10-10 09:45:05 +01:00
Lorenz Leutgeb 3c3d81a085 node: Use Mio
The dependencies `netservices`, `io-reactor` and `popol` served us well,
however they do not support Windows and are not actively maintained.

This change removes the aforementioned dependencies (and `libc` along
with them). It reuses the integration with `cyphernet` from
`netservices` for Noise and SOCKS.

The new module `reactor` is a rewrite of `io-reactor` on top of Mio.

Note that no tests were changed.
2025-10-10 09:45:05 +01:00
Lorenz Leutgeb ac572e64e5 node: Support systemd credential for passphrase
While it is possible to pass the passphrase via the environment, e.g.
`EnvironmentFile=<path to file that contains "RAD_PASSPHRASE=…">`
this is less secure than passing it via a file, because the environment
is inherited down the process tree.

Thus, allow using a systemd credential. The ID of the credential must be

    xyz.radicle.node.passphrase

and is not user-configurable.

Passing the passphrase via file is now possible with
`LoadCredential=xyz.radicle.node.passphrase:<path to file that contains passphrase>`

This requires just a bit of plumbing in `radicle-node`.

Because this mechanism is more secure than using the environment
variable `RAD_PASSPHRASE`, it takes priority. That is, if both the
systemd credential is available, *and* the environment variable
`RAD_PASSPHRASE` is set, the former is preferred.

Heads-up:
 1. The contents of the file must be valid UTF-8 (see documentation of
    `std::fs::read_to_string`). Assuming that the passphrase is at some
    point chosen by the user and typed on a keyboard, this does not
    seem like a severe restriction.
 2. The contents of the file are not processed otherwise, i.e. line
    breaks (notably at the end of the file) are not stripped.

The related `issue/8bd040e9de05e7fc27e373ebc1649ff4ad930e7a` asked for a
very similar feature: Passing the passphrase via a file named by the
value of the of the environment variable `RAD_PASSPHRASE_FILE`.
It was also briefly discussed at
<https://radicle.zulipchat.com/#narrow/channel/369277-heartwood/topic/.60RAD_PASSPHRASE_FILE.60/with/529104447>.
2025-10-09 10:03:21 +01:00
Lorenz Leutgeb ae39f24b58 node: Support systemd credential for secret
While it is possible to use systemd credentials via

    LoadCredential=xyz.radicle.node.secret:…
    ExecStart=radicle-node … --secret "${CREDENTIALS_DIRECTORY}/xyz.radicle.node.secret"

Make usage more convenient and directly support passing the secret key
via a systemd credential. The ID of the credential must be

    xyz.radicle.node.secret

and is not user-configurable.

A systemd service unit file might contain:

    LoadCredential=xyz.radicle.node.secret:…
    ExecStart=radicle-node …

This requires just a bit of plumbing in `radicle-node`.

The preference order for the path of the secret key is:
 1. The command line argument `--secret`.
 2. The systemd credential.
 3. The configuration file.
 4. The default location to preserve backward compatibility.

The reason to prefer the systemd credential over the configuration file
is that it uses a mechanism that is influenced by the environment of the
process, which is deemed "closer at runtime" or "more dynamic" than a
configuration file. Ad-hoc overrides are still possible via the
commandline argument.
2025-10-09 10:03:21 +01:00
Lorenz Leutgeb 7effa7c64c
node: Report sync status for given namespaces
The "seeds" command does only take a repository ID, and
carries the implicit assumption that the namespace with the same public
key as the Node ID of the receiving `radicle-node` process should be
used as reference for determining whether other nodes are in sync,
but no other namespace.

In the spirit of separating user and node identity, relax this, so that
the command also carries the public keys relative to which the sync
status should be computed. This allows to inspect sync status for
arbitrary namespaces via command.

For now, this feature is not exposed to the CLI, but `rad sync status`
always passes the public key of the active profile.
2025-10-09 07:55:11 +02:00