radicle/sigrefs/write: Treat error to verify head

To prevent from inability to write sigrefs in case of a verification
error, swallow verification errors about the head commit, log, and
carry on writing.
This commit is contained in:
Lorenz Leutgeb 2026-03-22 16:39:48 +01:00 committed by Fintan Halpenny
parent 8bc3ffc0b0
commit 7f19044a72
7 changed files with 54 additions and 17 deletions

View File

@ -129,6 +129,12 @@ pub mod error {
Git(#[from] radicle::git::raw::Error),
#[error(transparent)]
Refs(#[from] storage::refs::Error),
Refs(Box<storage::refs::Error>),
}
impl From<storage::refs::Error> for Policy {
fn from(err: storage::refs::Error) -> Self {
Self::Refs(Box::new(err))
}
}
}

View File

@ -18,7 +18,7 @@ pub enum Fetch {
#[error(transparent)]
Repository(#[from] radicle::storage::RepositoryError),
#[error(transparent)]
RefsDb(#[from] radicle::node::refs::Error),
RefsDb(Box<radicle::node::refs::Error>),
#[error("validation of the storage repository failed: the delegates {delegates:?} failed to validate to meet a threshold of {threshold}")]
Validation {
threshold: usize,
@ -28,6 +28,12 @@ pub enum Fetch {
Cache(#[from] Cache),
}
impl From<radicle::node::refs::Error> for Fetch {
fn from(err: radicle::node::refs::Error) -> Self {
Self::RefsDb(Box::new(err))
}
}
#[derive(Debug, Error)]
pub enum Cache {
#[error(transparent)]

View File

@ -107,7 +107,7 @@ impl SetHead {
#[derive(Error, Debug)]
pub enum RepositoryError {
#[error(transparent)]
Storage(#[from] Error),
Storage(Box<Error>),
#[error(transparent)]
Store(#[from] cob::store::Error),
#[error(transparent)]
@ -119,7 +119,7 @@ pub enum RepositoryError {
#[error(transparent)]
Quorum(#[from] canonical::error::QuorumError),
#[error(transparent)]
Refs(#[from] refs::Error),
Refs(Box<refs::Error>),
#[error("missing canonical reference rule for default branch")]
MissingBranchRule,
#[error("could not get the default branch rule: {0}")]
@ -130,6 +130,18 @@ pub enum RepositoryError {
FindObjects(#[from] canonical::effects::FindObjectsError),
}
impl From<Error> for RepositoryError {
fn from(err: Error) -> Self {
Self::Storage(Box::new(err))
}
}
impl From<refs::Error> for RepositoryError {
fn from(err: refs::Error) -> Self {
Self::Refs(Box::new(err))
}
}
impl RepositoryError {
pub fn is_not_found(&self) -> bool {
match self {

View File

@ -202,7 +202,7 @@ impl WriteStorage for Storage {
// N.b. we remove the repository if the `local` peer has no
// `rad/sigrefs`. There's no risk of them corrupting data.
let has_sigrefs = SignedRefsAt::load(self.info.key, &repo)
.map_err(|err| RepositoryError::Storage(Error::Refs(refs::Error::Read(err))))?
.map_err(|err| RepositoryError::from(refs::Error::Read(err)))?
.is_some();
if has_sigrefs {
repo.clean(&self.info.key)
@ -259,7 +259,7 @@ impl Storage {
let (_, head) = repo.head()?;
let refs = refs::SignedRefsAt::load(self.info.key, &repo)
.map_err(|err| RepositoryError::Refs(refs::Error::Read(err)))?;
.map_err(|err| RepositoryError::from(refs::Error::Read(err)))?;
let synced_at = refs
.as_ref()
.map(|r| SyncedAt::new(r.at, &repo))

View File

@ -135,12 +135,13 @@ where
} = self;
let reference = SIGREFS_BRANCH.with_namespace(git::fmt::Component::from(&namespace));
let head = HeadReader::new(&reference, repository, rid, self.signer)
.read()
.map_err(error::Write::Head)?;
let head = HeadReader::new(&reference, repository, rid, self.signer).read();
let commit_writer = match head {
Some(head) if head.is_unchanged(&refs) => return Ok(Update::unchanged(head.verified)),
Some(head) => CommitWriter::with_parent(
Ok(Some(head)) if head.is_unchanged(&refs) => {
return Ok(Update::unchanged(head.verified))
}
Ok(Some(head)) => CommitWriter::with_parent(
refs,
*head.verified.commit().oid(),
author,
@ -148,7 +149,12 @@ where
repository,
signer,
),
None => CommitWriter::root(refs, author, message, repository, signer),
Ok(None) => CommitWriter::root(refs, author, message, repository, signer),
Err(error::Head::Verify { commit, source }) => {
log::warn!("Verification of head of signed references failed: {source}");
CommitWriter::with_parent(refs, commit, author, message, repository, signer)
}
Err(err) => return Err(error::Write::Head(err)),
};
let commit = commit_writer.write().map_err(error::Write::Commit)?;
repository
@ -383,7 +389,10 @@ where
.read()
.map_err(error::Head::Commit)?
.verify(self.rid, self.verifier)
.map_err(error::Head::Verify)?;
.map_err(|err| error::Head::Verify {
commit: oid,
source: err,
})?;
Ok(Some(Head { verified }))
}

View File

@ -1,3 +1,4 @@
use radicle_oid::Oid;
use thiserror::Error;
use crate::storage::refs::sigrefs::git::{object, reference};
@ -43,6 +44,9 @@ pub enum Head {
Reference(reference::error::FindReference),
#[error(transparent)]
Commit(super::read::error::Commit),
#[error(transparent)]
Verify(super::read::error::Verify),
#[error("failed to verify commit {commit}: {source}")]
Verify {
commit: Oid,
source: super::read::error::Verify,
},
}

View File

@ -89,7 +89,7 @@ impl ReadStorage for MockStorage {
self.repos
.get(&rid)
.ok_or_else(|| {
RepositoryError::Storage(Error::Io(io::Error::from(io::ErrorKind::NotFound)))
RepositoryError::from(Error::Io(io::Error::from(io::ErrorKind::NotFound)))
})
.cloned()
}
@ -115,7 +115,7 @@ impl WriteStorage for MockStorage {
fn repository_mut(&self, rid: RepoId) -> Result<Self::RepositoryMut, RepositoryError> {
self.repos
.get(&rid)
.ok_or(RepositoryError::Storage(Error::Io(io::Error::from(
.ok_or(RepositoryError::from(Error::Io(io::Error::from(
io::ErrorKind::NotFound,
))))
.cloned()