about summary refs log tree commit diff
path: root/tvix/store/src/pathinfoservice
diff options
context:
space:
mode:
Diffstat (limited to 'tvix/store/src/pathinfoservice')
-rw-r--r--tvix/store/src/pathinfoservice/bigtable.rs30
-rw-r--r--tvix/store/src/pathinfoservice/combinators.rs23
-rw-r--r--tvix/store/src/pathinfoservice/fs/mod.rs32
-rw-r--r--tvix/store/src/pathinfoservice/grpc.rs40
-rw-r--r--tvix/store/src/pathinfoservice/lru.rs52
-rw-r--r--tvix/store/src/pathinfoservice/memory.rs22
-rw-r--r--tvix/store/src/pathinfoservice/mod.rs2
-rw-r--r--tvix/store/src/pathinfoservice/nix_http.rs34
-rw-r--r--tvix/store/src/pathinfoservice/redb.rs45
-rw-r--r--tvix/store/src/pathinfoservice/signing_wrapper.rs60
-rw-r--r--tvix/store/src/pathinfoservice/tests/mod.rs41
11 files changed, 146 insertions, 235 deletions
diff --git a/tvix/store/src/pathinfoservice/bigtable.rs b/tvix/store/src/pathinfoservice/bigtable.rs
index 15128986ff56..3d8db8e5044a 100644
--- a/tvix/store/src/pathinfoservice/bigtable.rs
+++ b/tvix/store/src/pathinfoservice/bigtable.rs
@@ -1,6 +1,5 @@
-use super::PathInfoService;
+use super::{PathInfo, PathInfoService};
 use crate::proto;
-use crate::proto::PathInfo;
 use async_stream::try_stream;
 use bigtable_rs::{bigtable, google::bigtable::v2 as bigtable_v2};
 use bytes::Bytes;
@@ -232,14 +231,13 @@ impl PathInfoService for BigtablePathInfoService {
         }
 
         // Try to parse the value into a PathInfo message
-        let path_info = proto::PathInfo::decode(Bytes::from(cell.value))
+        let path_info_proto = proto::PathInfo::decode(Bytes::from(cell.value))
             .map_err(|e| Error::StorageError(format!("unable to decode pathinfo proto: {}", e)))?;
 
-        let store_path = path_info
-            .validate()
-            .map_err(|e| Error::StorageError(format!("invalid PathInfo: {}", e)))?;
+        let path_info = PathInfo::try_from(path_info_proto)
+            .map_err(|e| Error::StorageError(format!("Invalid path info: {e}")))?;
 
-        if store_path.digest() != &digest {
+        if path_info.store_path.digest() != &digest {
             return Err(Error::StorageError("PathInfo has unexpected digest".into()));
         }
 
@@ -248,14 +246,10 @@ impl PathInfoService for BigtablePathInfoService {
 
     #[instrument(level = "trace", skip_all, fields(path_info.root_node = ?path_info.node))]
     async fn put(&self, path_info: PathInfo) -> Result<PathInfo, Error> {
-        let store_path = path_info
-            .validate()
-            .map_err(|e| Error::InvalidRequest(format!("pathinfo failed validation: {}", e)))?;
-
         let mut client = self.client.clone();
-        let path_info_key = derive_pathinfo_key(store_path.digest());
+        let path_info_key = derive_pathinfo_key(path_info.store_path.digest());
 
-        let data = path_info.encode_to_vec();
+        let data = proto::PathInfo::from(path_info.clone()).encode_to_vec();
         if data.len() as u64 > CELL_SIZE_LIMIT {
             return Err(Error::StorageError(
                 "PathInfo exceeds cell limit on Bigtable".into(),
@@ -340,16 +334,12 @@ impl PathInfoService for BigtablePathInfoService {
                 }
 
                 // Try to parse the value into a PathInfo message.
-                let path_info = proto::PathInfo::decode(Bytes::from(cell.value))
+                let path_info_proto = proto::PathInfo::decode(Bytes::from(cell.value))
                     .map_err(|e| Error::StorageError(format!("unable to decode pathinfo proto: {}", e)))?;
 
-                // Validate the containing PathInfo, ensure its StorePath digest
-                // matches row key.
-                let store_path = path_info
-                    .validate()
-                    .map_err(|e| Error::StorageError(format!("invalid PathInfo: {}", e)))?;
+                let path_info = PathInfo::try_from(path_info_proto).map_err(|e| Error::StorageError(format!("Invalid path info: {e}")))?;
 
-                let exp_path_info_key = derive_pathinfo_key(store_path.digest());
+                let exp_path_info_key = derive_pathinfo_key(path_info.store_path.digest());
 
                 if exp_path_info_key.as_bytes() != row_key.as_slice() {
                     Err(Error::StorageError("PathInfo has unexpected digest".into()))?
diff --git a/tvix/store/src/pathinfoservice/combinators.rs b/tvix/store/src/pathinfoservice/combinators.rs
index bb5595f72b10..1f413cf310a2 100644
--- a/tvix/store/src/pathinfoservice/combinators.rs
+++ b/tvix/store/src/pathinfoservice/combinators.rs
@@ -1,6 +1,5 @@
 use std::sync::Arc;
 
-use crate::proto::PathInfo;
 use futures::stream::BoxStream;
 use nix_compat::nixbase32;
 use tonic::async_trait;
@@ -8,7 +7,7 @@ use tracing::{debug, instrument};
 use tvix_castore::composition::{CompositionContext, ServiceBuilder};
 use tvix_castore::Error;
 
-use super::PathInfoService;
+use super::{PathInfo, PathInfoService};
 
 /// Asks near first, if not found, asks far.
 /// If found in there, returns it, and *inserts* it into
@@ -105,11 +104,9 @@ mod test {
 
     use crate::{
         pathinfoservice::{LruPathInfoService, MemoryPathInfoService, PathInfoService},
-        tests::fixtures::PATH_INFO_WITH_NARINFO,
+        tests::fixtures::PATH_INFO,
     };
 
-    const PATH_INFO_DIGEST: [u8; 20] = [0; 20];
-
     /// Helper function setting up an instance of a "far" and "near"
     /// PathInfoService.
     async fn create_pathinfoservice() -> super::Cache<LruPathInfoService, MemoryPathInfoService> {
@@ -129,21 +126,25 @@ mod test {
         let svc = create_pathinfoservice().await;
 
         // query the PathInfo, things should not be there.
-        assert!(svc.get(PATH_INFO_DIGEST).await.unwrap().is_none());
+        assert!(svc
+            .get(*PATH_INFO.store_path.digest())
+            .await
+            .unwrap()
+            .is_none());
 
         // insert it into the far one.
-        svc.far.put(PATH_INFO_WITH_NARINFO.clone()).await.unwrap();
+        svc.far.put(PATH_INFO.clone()).await.unwrap();
 
         // now try getting it again, it should succeed.
         assert_eq!(
-            Some(PATH_INFO_WITH_NARINFO.clone()),
-            svc.get(PATH_INFO_DIGEST).await.unwrap()
+            Some(PATH_INFO.clone()),
+            svc.get(*PATH_INFO.store_path.digest()).await.unwrap()
         );
 
         // peek near, it should now be there.
         assert_eq!(
-            Some(PATH_INFO_WITH_NARINFO.clone()),
-            svc.near.get(PATH_INFO_DIGEST).await.unwrap()
+            Some(PATH_INFO.clone()),
+            svc.near.get(*PATH_INFO.store_path.digest()).await.unwrap()
         );
     }
 }
diff --git a/tvix/store/src/pathinfoservice/fs/mod.rs b/tvix/store/src/pathinfoservice/fs/mod.rs
index 1f7fa8a8afce..d996ec9f6f76 100644
--- a/tvix/store/src/pathinfoservice/fs/mod.rs
+++ b/tvix/store/src/pathinfoservice/fs/mod.rs
@@ -58,32 +58,20 @@ where
             .as_ref()
             .get(*store_path.digest())
             .await?
-            .map(|path_info| {
-                let node = path_info
-                    .node
-                    .as_ref()
-                    .expect("missing root node")
-                    .to_owned();
-
-                match node.into_name_and_node() {
-                    Ok((_name, node)) => Ok(node),
-                    Err(e) => Err(Error::StorageError(e.to_string())),
-                }
-            })
-            .transpose()?)
+            .map(|path_info| path_info.node))
     }
 
     fn list(&self) -> BoxStream<Result<(PathComponent, Node), Error>> {
         Box::pin(self.0.as_ref().list().map(|result| {
-            result.and_then(|path_info| {
-                let node = path_info
-                    .node
-                    .as_ref()
-                    .expect("missing root node")
-                    .to_owned();
-
-                node.into_name_and_node()
-                    .map_err(|e| Error::StorageError(e.to_string()))
+            result.map(|path_info| {
+                let basename = path_info.store_path.to_string();
+                (
+                    basename
+                        .as_str()
+                        .try_into()
+                        .expect("Tvix bug: StorePath must be PathComponent"),
+                    path_info.node,
+                )
             })
         }))
     }
diff --git a/tvix/store/src/pathinfoservice/grpc.rs b/tvix/store/src/pathinfoservice/grpc.rs
index 7510ccd911f0..d292b2a784f6 100644
--- a/tvix/store/src/pathinfoservice/grpc.rs
+++ b/tvix/store/src/pathinfoservice/grpc.rs
@@ -1,7 +1,7 @@
-use super::PathInfoService;
+use super::{PathInfo, PathInfoService};
 use crate::{
     nar::NarCalculationService,
-    proto::{self, ListPathInfoRequest, PathInfo},
+    proto::{self, ListPathInfoRequest},
 };
 use async_stream::try_stream;
 use futures::stream::BoxStream;
@@ -53,15 +53,10 @@ where
             .await;
 
         match path_info {
-            Ok(path_info) => {
-                let path_info = path_info.into_inner();
-
-                path_info
-                    .validate()
-                    .map_err(|e| Error::StorageError(format!("invalid pathinfo: {}", e)))?;
-
-                Ok(Some(path_info))
-            }
+            Ok(path_info) => Ok(Some(
+                PathInfo::try_from(path_info.into_inner())
+                    .map_err(|e| Error::StorageError(format!("Invalid path info: {e}")))?,
+            )),
             Err(e) if e.code() == Code::NotFound => Ok(None),
             Err(e) => Err(Error::StorageError(e.to_string())),
         }
@@ -72,12 +67,12 @@ where
         let path_info = self
             .grpc_client
             .clone()
-            .put(path_info)
+            .put(proto::PathInfo::from(path_info))
             .await
             .map_err(|e| Error::StorageError(e.to_string()))?
             .into_inner();
-
-        Ok(path_info)
+        Ok(PathInfo::try_from(path_info)
+            .map_err(|e| Error::StorageError(format!("Invalid path info: {e}")))?)
     }
 
     #[instrument(level = "trace", skip_all)]
@@ -91,21 +86,8 @@ where
 
             loop {
                 match stream.message().await {
-                    Ok(o) => match o {
-                        Some(pathinfo) => {
-                            // validate the pathinfo
-                            if let Err(e) = pathinfo.validate() {
-                                Err(Error::StorageError(format!(
-                                    "pathinfo {:?} failed validation: {}",
-                                    pathinfo, e
-                                )))?;
-                            }
-                            yield pathinfo
-                        }
-                        None => {
-                            return;
-                        },
-                    },
+                    Ok(Some(path_info)) => yield PathInfo::try_from(path_info).map_err(|e| Error::StorageError(format!("Invalid path info: {e}")))?,
+                    Ok(None) => return,
                     Err(e) => Err(Error::StorageError(e.to_string()))?,
                 }
             }
diff --git a/tvix/store/src/pathinfoservice/lru.rs b/tvix/store/src/pathinfoservice/lru.rs
index 695c04636089..2d8d52e3c9f6 100644
--- a/tvix/store/src/pathinfoservice/lru.rs
+++ b/tvix/store/src/pathinfoservice/lru.rs
@@ -8,11 +8,10 @@ use tokio::sync::RwLock;
 use tonic::async_trait;
 use tracing::instrument;
 
-use crate::proto::PathInfo;
 use tvix_castore::composition::{CompositionContext, ServiceBuilder};
 use tvix_castore::Error;
 
-use super::PathInfoService;
+use super::{PathInfo, PathInfoService};
 
 pub struct LruPathInfoService {
     lru: Arc<RwLock<LruCache<[u8; 20], PathInfo>>>,
@@ -35,15 +34,10 @@ impl PathInfoService for LruPathInfoService {
 
     #[instrument(level = "trace", skip_all, fields(path_info.root_node = ?path_info.node))]
     async fn put(&self, path_info: PathInfo) -> Result<PathInfo, Error> {
-        // call validate
-        let store_path = path_info
-            .validate()
-            .map_err(|e| Error::InvalidRequest(format!("invalid PathInfo: {}", e)))?;
-
         self.lru
             .write()
             .await
-            .put(*store_path.digest(), path_info.clone());
+            .put(*path_info.store_path.digest(), path_info.clone());
 
         Ok(path_info)
     }
@@ -91,40 +85,22 @@ impl ServiceBuilder for LruPathInfoServiceConfig {
 
 #[cfg(test)]
 mod test {
+    use nix_compat::store_path::StorePath;
     use std::num::NonZeroUsize;
 
     use crate::{
-        pathinfoservice::{LruPathInfoService, PathInfoService},
-        proto::PathInfo,
-        tests::fixtures::PATH_INFO_WITH_NARINFO,
+        pathinfoservice::{LruPathInfoService, PathInfo, PathInfoService},
+        tests::fixtures::PATH_INFO,
     };
     use lazy_static::lazy_static;
-    use tvix_castore::proto as castorepb;
 
     lazy_static! {
-        static ref PATHINFO_1: PathInfo = PATH_INFO_WITH_NARINFO.clone();
-        static ref PATHINFO_1_DIGEST: [u8; 20] = [0; 20];
         static ref PATHINFO_2: PathInfo = {
-            let mut p = PATHINFO_1.clone();
-            let root_node = p.node.as_mut().unwrap();
-            if let castorepb::Node { node: Some(node) } = root_node {
-                match node {
-                    castorepb::node::Node::Directory(n) => {
-                        n.name = "11111111111111111111111111111111-dummy2".into()
-                    }
-                    castorepb::node::Node::File(n) => {
-                        n.name = "11111111111111111111111111111111-dummy2".into()
-                    }
-                    castorepb::node::Node::Symlink(n) => {
-                        n.name = "11111111111111111111111111111111-dummy2".into()
-                    }
-                }
-            } else {
-                unreachable!()
-            }
+            let mut p = PATH_INFO.clone();
+            p.store_path = StorePath::from_name_and_digest_fixed("dummy", [1; 20]).unwrap();
             p
         };
-        static ref PATHINFO_2_DIGEST: [u8; 20] = *(PATHINFO_2.validate().unwrap()).digest();
+        static ref PATHINFO_2_DIGEST: [u8; 20] = *PATHINFO_2.store_path.digest();
     }
 
     #[tokio::test]
@@ -133,18 +109,20 @@ mod test {
 
         // pathinfo_1 should not be there
         assert!(svc
-            .get(*PATHINFO_1_DIGEST)
+            .get(*PATH_INFO.store_path.digest())
             .await
             .expect("no error")
             .is_none());
 
         // insert it
-        svc.put(PATHINFO_1.clone()).await.expect("no error");
+        svc.put(PATH_INFO.clone()).await.expect("no error");
 
         // now it should be there.
         assert_eq!(
-            Some(PATHINFO_1.clone()),
-            svc.get(*PATHINFO_1_DIGEST).await.expect("no error")
+            Some(PATH_INFO.clone()),
+            svc.get(*PATH_INFO.store_path.digest())
+                .await
+                .expect("no error")
         );
 
         // insert pathinfo_2. This will evict pathinfo 1
@@ -158,7 +136,7 @@ mod test {
 
         // … but pathinfo 1 not anymore.
         assert!(svc
-            .get(*PATHINFO_1_DIGEST)
+            .get(*PATH_INFO.store_path.digest())
             .await
             .expect("no error")
             .is_none());
diff --git a/tvix/store/src/pathinfoservice/memory.rs b/tvix/store/src/pathinfoservice/memory.rs
index 3fabd239c7b1..fd013fe9a573 100644
--- a/tvix/store/src/pathinfoservice/memory.rs
+++ b/tvix/store/src/pathinfoservice/memory.rs
@@ -1,5 +1,4 @@
-use super::PathInfoService;
-use crate::proto::PathInfo;
+use super::{PathInfo, PathInfoService};
 use async_stream::try_stream;
 use futures::stream::BoxStream;
 use nix_compat::nixbase32;
@@ -29,22 +28,11 @@ impl PathInfoService for MemoryPathInfoService {
 
     #[instrument(level = "trace", skip_all, fields(path_info.root_node = ?path_info.node))]
     async fn put(&self, path_info: PathInfo) -> Result<PathInfo, Error> {
-        // Call validate on the received PathInfo message.
-        match path_info.validate() {
-            Err(e) => Err(Error::InvalidRequest(format!(
-                "failed to validate PathInfo: {}",
-                e
-            ))),
+        // This overwrites existing PathInfo objects with the same store path digest.
+        let mut db = self.db.write().await;
+        db.insert(*path_info.store_path.digest(), path_info.clone());
 
-            // In case the PathInfo is valid, and we were able to extract a NixPath, store it in the database.
-            // This overwrites existing PathInfo objects.
-            Ok(nix_path) => {
-                let mut db = self.db.write().await;
-                db.insert(*nix_path.digest(), path_info.clone());
-
-                Ok(path_info)
-            }
-        }
+        Ok(path_info)
     }
 
     fn list(&self) -> BoxStream<'static, Result<PathInfo, Error>> {
diff --git a/tvix/store/src/pathinfoservice/mod.rs b/tvix/store/src/pathinfoservice/mod.rs
index 8d60ff79a33b..0a91d6267260 100644
--- a/tvix/store/src/pathinfoservice/mod.rs
+++ b/tvix/store/src/pathinfoservice/mod.rs
@@ -19,7 +19,7 @@ use tvix_castore::composition::{Registry, ServiceBuilder};
 use tvix_castore::Error;
 
 use crate::nar::NarCalculationService;
-use crate::proto::PathInfo;
+pub use crate::path_info::PathInfo;
 
 pub use self::combinators::{
     Cache as CachePathInfoService, CacheConfig as CachePathInfoServiceConfig,
diff --git a/tvix/store/src/pathinfoservice/nix_http.rs b/tvix/store/src/pathinfoservice/nix_http.rs
index 2ff094858bc9..ed386f0e9d14 100644
--- a/tvix/store/src/pathinfoservice/nix_http.rs
+++ b/tvix/store/src/pathinfoservice/nix_http.rs
@@ -1,10 +1,11 @@
-use super::PathInfoService;
-use crate::{nar::ingest_nar_and_hash, proto::PathInfo};
+use super::{PathInfo, PathInfoService};
+use crate::nar::ingest_nar_and_hash;
 use futures::{stream::BoxStream, TryStreamExt};
 use nix_compat::{
-    narinfo::{self, NarInfo},
+    narinfo::{self, NarInfo, Signature},
     nixbase32,
     nixhash::NixHash,
+    store_path::StorePath,
 };
 use reqwest::StatusCode;
 use std::sync::Arc;
@@ -12,9 +13,7 @@ use tokio::io::{self, AsyncRead};
 use tonic::async_trait;
 use tracing::{debug, instrument, warn};
 use tvix_castore::composition::{CompositionContext, ServiceBuilder};
-use tvix_castore::{
-    blobservice::BlobService, directoryservice::DirectoryService, proto as castorepb, Error,
-};
+use tvix_castore::{blobservice::BlobService, directoryservice::DirectoryService, Error};
 use url::Url;
 
 /// NixHTTPPathInfoService acts as a bridge in between the Nix HTTP Binary cache
@@ -137,12 +136,11 @@ where
             }
         }
 
-        // Convert to a (sparse) PathInfo. We still need to populate the node field,
-        // and for this we need to download the NAR file.
+        // To construct the full PathInfo, we also need to populate the node field,
+        // and for this we need to download the NAR file and ingest it into castore.
         // FUTUREWORK: Keep some database around mapping from narsha256 to
         // (unnamed) rootnode, so we can use that (and the name from the
         // StorePath) and avoid downloading the same NAR a second time.
-        let pathinfo: PathInfo = (&narinfo).into();
 
         // create a request for the NAR file itself.
         let nar_url = self.base_url.join(narinfo.url).map_err(|e| {
@@ -228,12 +226,18 @@ where
         }
 
         Ok(Some(PathInfo {
-            node: Some(castorepb::Node::from_name_and_node(
-                narinfo.store_path.to_string().into(),
-                root_node,
-            )),
-            references: pathinfo.references,
-            narinfo: pathinfo.narinfo,
+            store_path: narinfo.store_path.to_owned(),
+            node: root_node,
+            references: narinfo.references.iter().map(StorePath::to_owned).collect(),
+            nar_size: narinfo.nar_size,
+            nar_sha256: narinfo.nar_hash,
+            deriver: narinfo.deriver.as_ref().map(StorePath::to_owned),
+            signatures: narinfo
+                .signatures
+                .into_iter()
+                .map(|s| Signature::<String>::new(s.name().to_string(), s.bytes().to_owned()))
+                .collect(),
+            ca: narinfo.ca,
         }))
     }
 
diff --git a/tvix/store/src/pathinfoservice/redb.rs b/tvix/store/src/pathinfoservice/redb.rs
index bd0e0fc2b686..6e794e1981f0 100644
--- a/tvix/store/src/pathinfoservice/redb.rs
+++ b/tvix/store/src/pathinfoservice/redb.rs
@@ -1,5 +1,5 @@
-use super::PathInfoService;
-use crate::proto::PathInfo;
+use super::{PathInfo, PathInfoService};
+use crate::proto;
 use data_encoding::BASE64;
 use futures::{stream::BoxStream, StreamExt};
 use prost::Message;
@@ -78,10 +78,13 @@ impl PathInfoService for RedbPathInfoService {
                 let table = txn.open_table(PATHINFO_TABLE)?;
                 match table.get(digest)? {
                     Some(pathinfo_bytes) => Ok(Some(
-                        PathInfo::decode(pathinfo_bytes.value().as_slice()).map_err(|e| {
-                            warn!(err=%e, "failed to decode stored PathInfo");
-                            Error::StorageError("failed to decode stored PathInfo".to_string())
-                        })?,
+                        proto::PathInfo::decode(pathinfo_bytes.value().as_slice())
+                            .map_err(|e| {
+                                warn!(err=%e, "failed to decode stored PathInfo");
+                                Error::StorageError("failed to decode stored PathInfo".to_string())
+                            })?
+                            .try_into()
+                            .map_err(|e| Error::StorageError(format!("Invalid path info: {e}")))?,
                     )),
                     None => Ok(None),
                 }
@@ -92,25 +95,19 @@ impl PathInfoService for RedbPathInfoService {
 
     #[instrument(level = "trace", skip_all, fields(path_info.root_node = ?path_info.node))]
     async fn put(&self, path_info: PathInfo) -> Result<PathInfo, Error> {
-        // Call validate on the received PathInfo message.
-        let store_path = path_info
-            .validate()
-            .map_err(|e| {
-                warn!(err=%e, "failed to validate PathInfo");
-                Error::StorageError("failed to validate PathInfo".to_string())
-            })?
-            .to_owned();
-
-        let path_info_encoded = path_info.encode_to_vec();
         let db = self.db.clone();
 
         tokio::task::spawn_blocking({
+            let path_info = path_info.clone();
             move || -> Result<(), Error> {
                 let txn = db.begin_write()?;
                 {
                     let mut table = txn.open_table(PATHINFO_TABLE)?;
                     table
-                        .insert(store_path.digest(), path_info_encoded)
+                        .insert(
+                            *path_info.store_path.digest(),
+                            proto::PathInfo::from(path_info).encode_to_vec(),
+                        )
                         .map_err(|e| {
                             warn!(err=%e, "failed to insert PathInfo");
                             Error::StorageError("failed to insert PathInfo".to_string())
@@ -137,12 +134,18 @@ impl PathInfoService for RedbPathInfoService {
                 for elem in table.iter()? {
                     let elem = elem?;
                     tokio::runtime::Handle::current()
-                        .block_on(tx.send(Ok(
-                            PathInfo::decode(elem.1.value().as_slice()).map_err(|e| {
+                        .block_on(tx.send(Ok({
+                            let path_info_proto = proto::PathInfo::decode(
+                                elem.1.value().as_slice(),
+                            )
+                            .map_err(|e| {
                                 warn!(err=%e, "invalid PathInfo");
                                 Error::StorageError("invalid PathInfo".to_string())
-                            })?,
-                        )))
+                            })?;
+                            PathInfo::try_from(path_info_proto).map_err(|e| {
+                                Error::StorageError(format!("Invalid path info: {e}"))
+                            })?
+                        })))
                         .map_err(|e| Error::StorageError(e.to_string()))?;
                 }
 
diff --git a/tvix/store/src/pathinfoservice/signing_wrapper.rs b/tvix/store/src/pathinfoservice/signing_wrapper.rs
index 7f754ec49849..3230e000ab96 100644
--- a/tvix/store/src/pathinfoservice/signing_wrapper.rs
+++ b/tvix/store/src/pathinfoservice/signing_wrapper.rs
@@ -1,7 +1,6 @@
 //! This module provides a [PathInfoService] implementation that signs narinfos
 
-use super::PathInfoService;
-use crate::proto::PathInfo;
+use super::{PathInfo, PathInfoService};
 use futures::stream::BoxStream;
 use std::path::PathBuf;
 use std::sync::Arc;
@@ -11,9 +10,9 @@ use tvix_castore::composition::{CompositionContext, ServiceBuilder};
 
 use tvix_castore::Error;
 
-use nix_compat::narinfo::{parse_keypair, SigningKey};
+use nix_compat::narinfo::{parse_keypair, Signature, SigningKey};
 use nix_compat::nixbase32;
-use tracing::{instrument, warn};
+use tracing::instrument;
 
 #[cfg(test)]
 use super::MemoryPathInfoService;
@@ -52,22 +51,15 @@ where
     }
 
     async fn put(&self, path_info: PathInfo) -> Result<PathInfo, Error> {
-        let store_path = path_info.validate().map_err(|e| {
-            warn!(err=%e, "invalid PathInfo");
-            Error::StorageError(e.to_string())
-        })?;
-        let root_node = path_info.node.clone();
-        // If we have narinfo then sign it, else passthrough to the upper pathinfoservice
-        let path_info_to_put = match path_info.to_narinfo(store_path.as_ref()) {
-            Some(mut nar_info) => {
-                nar_info.add_signature(self.signing_key.as_ref());
-                let mut signed_path_info = PathInfo::from(&nar_info);
-                signed_path_info.node = root_node;
-                signed_path_info
-            }
-            None => path_info,
-        };
-        self.inner.put(path_info_to_put).await
+        let mut path_info = path_info.clone();
+        let mut nar_info = path_info.to_narinfo();
+        nar_info.add_signature(self.signing_key.as_ref());
+        path_info.signatures = nar_info
+            .signatures
+            .into_iter()
+            .map(|s| Signature::<String>::new(s.name().to_string(), s.bytes().to_owned()))
+            .collect();
+        self.inner.put(path_info).await
     }
 
     fn list(&self) -> BoxStream<'static, Result<PathInfo, Error>> {
@@ -134,51 +126,35 @@ pub const DUMMY_VERIFYING_KEY: &str = "do.not.use:cuXqnuzlWfGTKmfzBPx2kXShjRryZM
 
 #[cfg(test)]
 mod test {
-    use crate::{
-        pathinfoservice::PathInfoService,
-        proto::PathInfo,
-        tests::fixtures::{DUMMY_PATH, PATH_INFO_WITH_NARINFO},
-    };
+    use crate::{pathinfoservice::PathInfoService, tests::fixtures::PATH_INFO};
     use nix_compat::narinfo::VerifyingKey;
 
-    use lazy_static::lazy_static;
-    use nix_compat::store_path::StorePath;
-
-    lazy_static! {
-        static ref PATHINFO_1: PathInfo = PATH_INFO_WITH_NARINFO.clone();
-        static ref PATHINFO_1_DIGEST: [u8; 20] = [0; 20];
-    }
-
     #[tokio::test]
     async fn put_and_verify_signature() {
         let svc = super::test_signing_service();
 
         // pathinfo_1 should not be there ...
         assert!(svc
-            .get(*PATHINFO_1_DIGEST)
+            .get(*PATH_INFO.store_path.digest())
             .await
             .expect("no error")
             .is_none());
 
         // ... and not be signed
-        assert!(PATHINFO_1.narinfo.clone().unwrap().signatures.is_empty());
+        assert!(PATH_INFO.signatures.is_empty());
 
         // insert it
-        svc.put(PATHINFO_1.clone()).await.expect("no error");
+        svc.put(PATH_INFO.clone()).await.expect("no error");
 
         // now it should be there ...
         let signed = svc
-            .get(*PATHINFO_1_DIGEST)
+            .get(*PATH_INFO.store_path.digest())
             .await
             .expect("no error")
             .unwrap();
 
         // and signed
-        let narinfo = signed
-            .to_narinfo(
-                StorePath::from_bytes(DUMMY_PATH.as_bytes()).expect("DUMMY_PATH to be parsed"),
-            )
-            .expect("no error");
+        let narinfo = signed.to_narinfo();
         let fp = narinfo.fingerprint();
 
         // load our keypair from the fixtures
diff --git a/tvix/store/src/pathinfoservice/tests/mod.rs b/tvix/store/src/pathinfoservice/tests/mod.rs
index 028fa5af57fc..12c685c80fad 100644
--- a/tvix/store/src/pathinfoservice/tests/mod.rs
+++ b/tvix/store/src/pathinfoservice/tests/mod.rs
@@ -6,12 +6,10 @@ use futures::TryStreamExt;
 use rstest::*;
 use rstest_reuse::{self, *};
 
-use super::PathInfoService;
+use super::{PathInfo, PathInfoService};
 use crate::pathinfoservice::redb::RedbPathInfoService;
 use crate::pathinfoservice::MemoryPathInfoService;
-use crate::proto::PathInfo;
-use crate::tests::fixtures::DUMMY_PATH_DIGEST;
-use tvix_castore::proto as castorepb;
+use crate::tests::fixtures::{DUMMY_PATH_DIGEST, PATH_INFO};
 
 use crate::pathinfoservice::test_signing_service;
 
@@ -52,32 +50,35 @@ async fn not_found(svc: impl PathInfoService) {
 #[apply(path_info_services)]
 #[tokio::test]
 async fn put_get(svc: impl PathInfoService) {
-    let path_info = PathInfo {
-        node: Some(castorepb::Node {
-            node: Some(castorepb::node::Node::Symlink(castorepb::SymlinkNode {
-                name: "00000000000000000000000000000000-foo".into(),
-                target: "doesntmatter".into(),
-            })),
-        }),
-        ..Default::default()
-    };
-
     // insert
-    let resp = svc.put(path_info.clone()).await.expect("must succeed");
+    let resp = svc.put(PATH_INFO.clone()).await.expect("must succeed");
 
-    // expect the returned PathInfo to be equal (for now)
-    // in the future, some stores might add additional fields/signatures.
-    assert_eq!(path_info, resp);
+    // expect the returned PathInfo to be equal,
+    // remove the signatures as the SigningPathInfoService adds them
+    assert_eq!(*PATH_INFO, strip_signatures(resp));
 
     // get it back
     let resp = svc.get(DUMMY_PATH_DIGEST).await.expect("must succeed");
 
-    assert_eq!(Some(path_info.clone()), resp);
+    assert_eq!(Some(PATH_INFO.clone()), resp.map(strip_signatures));
 
     // Ensure the listing endpoint works, and returns the same path_info.
     // FUTUREWORK: split this, some impls might (rightfully) not support listing
     let pathinfos: Vec<PathInfo> = svc.list().try_collect().await.expect("must succeed");
 
     // We should get a single pathinfo back, the one we inserted.
-    assert_eq!(vec![path_info], pathinfos);
+    assert_eq!(
+        vec![PATH_INFO.clone()],
+        pathinfos
+            .into_iter()
+            .map(strip_signatures)
+            .collect::<Vec<_>>()
+    );
+}
+
+fn strip_signatures(path_info: PathInfo) -> PathInfo {
+    PathInfo {
+        signatures: vec![],
+        ..path_info
+    }
 }