diff options
Diffstat (limited to 'adenosine-pds/src/repo.rs')
-rw-r--r-- | adenosine-pds/src/repo.rs | 246 |
1 files changed, 205 insertions, 41 deletions
diff --git a/adenosine-pds/src/repo.rs b/adenosine-pds/src/repo.rs index b74615b..419d23f 100644 --- a/adenosine-pds/src/repo.rs +++ b/adenosine-pds/src/repo.rs @@ -1,5 +1,6 @@ -use crate::mst::{collect_mst_keys, CommitNode, MetadataNode, RootNode}; -use anyhow::{anyhow, Result}; +use crate::load_car_to_blockstore; +use crate::mst::{collect_mst_keys, generate_mst, CommitNode, MetadataNode, RootNode}; +use anyhow::{anyhow, ensure, Context, Result}; use ipfs_sqlite_block_store::BlockStore; use libipld::cbor::DagCborCodec; use libipld::multihash::Code; @@ -57,18 +58,25 @@ impl RepoStore { } /// Returns CID that was inserted - pub fn put_ipld(&mut self, record: &Ipld) -> Result<String> { + pub fn put_ipld<S: libipld::codec::Encode<DagCborCodec>>( + &mut self, + record: &S, + ) -> Result<String> { let block = Block::<DefaultParams>::encode(DagCborCodec, Code::Sha2_256, record)?; let cid = block.cid().clone(); - self.db.put_block(block, None)?; + self.db + .put_block(block, None) + .context("writing IPLD DAG-CBOR record to blockstore")?; Ok(cid.to_string()) } /// Returns CID that was inserted - pub fn put_blob(&mut self, data: Vec<u8>) -> Result<String> { - let block = Block::<DefaultParams>::encode(libipld::raw::RawCodec, Code::Sha2_256, &data)?; + pub fn put_blob(&mut self, data: &[u8]) -> Result<String> { + let block = Block::<DefaultParams>::encode(libipld::raw::RawCodec, Code::Sha2_256, data)?; let cid = block.cid().clone(); - self.db.put_block(block, None)?; + self.db + .put_block(block, None) + .context("writing non-record blob to blockstore")?; Ok(cid.to_string()) } @@ -82,26 +90,40 @@ impl RepoStore { pub fn get_commit(&mut self, commit_cid: &str) -> Result<RepoCommit> { // read records by CID: commit, root, meta - let commit_node: CommitNode = DagCborCodec.decode( - &self - .db - .get_block(&Cid::from_str(commit_cid)?)? - .ok_or(anyhow!("expected commit block in store"))?, - )?; - let root_node: RootNode = DagCborCodec.decode( - &self - .db - .get_block(&commit_node.root)? - .ok_or(anyhow!("expected root block in store"))?, - )?; - let metadata_node: MetadataNode = DagCborCodec.decode( - &self - .db - .get_block(&root_node.meta)? - .ok_or(anyhow!("expected metadata block in store"))?, - )?; - assert_eq!(metadata_node.datastore, "mst"); - assert_eq!(metadata_node.version, 1); + let commit_node: CommitNode = DagCborCodec + .decode( + &self + .db + .get_block(&Cid::from_str(commit_cid)?)? + .ok_or(anyhow!("expected commit block in store"))?, + ) + .context("parsing commit IPLD node from blockstore")?; + let root_node: RootNode = DagCborCodec + .decode( + &self + .db + .get_block(&commit_node.root)? + .ok_or(anyhow!("expected root block in store"))?, + ) + .context("parsing root IPLD node from blockstore")?; + let metadata_node: MetadataNode = DagCborCodec + .decode( + &self + .db + .get_block(&root_node.meta)? + .ok_or(anyhow!("expected metadata block in store"))?, + ) + .context("parsing metadata IPLD node from blockstore")?; + ensure!( + metadata_node.datastore == "mst", + "unexpected repo metadata.datastore: {}", + metadata_node.datastore + ); + ensure!( + metadata_node.version == 1, + "unexpected repo metadata.version: {}", + metadata_node.version + ); Ok(RepoCommit { sig: commit_node.sig, did: metadata_node.did, @@ -110,8 +132,8 @@ impl RepoStore { }) } - pub fn get_record_by_key(&mut self, commit_cid: &str, key: &str) -> Result<Option<Ipld>> { - let map = self.as_map(commit_cid)?; + pub fn get_mst_record_by_key(&mut self, mst_cid: &str, key: &str) -> Result<Option<Ipld>> { + let map = self.mst_to_map(mst_cid)?; if let Some(cid) = map.get(key) { self.get_ipld(&cid.to_string()).map(|v| Some(v)) } else { @@ -119,35 +141,88 @@ impl RepoStore { } } - pub fn write_root(&mut self, did: &str, mst_cid: &str, prev: Option<&str>) -> Result<String> { - unimplemented!() + pub fn get_atp_record( + &mut self, + did: &str, + collection: &str, + tid: &str, + ) -> Result<Option<Ipld>> { + let commit = if let Some(c) = self.lookup_commit(did)? { + self.get_commit(&c)? + } else { + return Ok(None); + }; + let record_key = format!("/{}/{}", collection, tid); + self.get_mst_record_by_key(&commit.mst_cid, &record_key) + } + + pub fn write_metadata(&mut self, did: &str) -> Result<String> { + self.put_ipld(&MetadataNode { + datastore: "mst".to_string(), + did: did.to_string(), + version: 1, + }) + } + + pub fn write_root( + &mut self, + did: &str, + meta_cid: &str, + prev: Option<&str>, + mst_cid: &str, + ) -> Result<String> { + self.put_ipld(&RootNode { + auth_token: None, + // TODO: not unwrap here + prev: prev.map(|s| Cid::from_str(s).unwrap()), + // TODO: not 'metadata'? + meta: Cid::from_str(meta_cid)?, + data: Cid::from_str(mst_cid)?, + }) } pub fn write_commit(&mut self, did: &str, root_cid: &str, sig: &str) -> Result<String> { - // TODO: also update alias to point to new commit - unimplemented!() + let commit_cid = self.put_ipld(&CommitNode { + root: Cid::from_str(root_cid)?, + sig: sig.as_bytes().to_vec().into_boxed_slice(), + })?; + self.db + .alias(did.as_bytes().to_vec(), Some(&Cid::from_str(&commit_cid)?))?; + Ok(commit_cid.to_string()) } - pub fn write_map(&self, map: Result<BTreeMap<String, String>>) -> Result<String> { - unimplemented!() + pub fn mst_from_map(&mut self, map: &BTreeMap<String, String>) -> Result<String> { + // TODO: not unwrap in iter + let mut cid_map: BTreeMap<String, Cid> = BTreeMap::from_iter( + map.iter() + .map(|(k, v)| (k.to_string(), Cid::from_str(&v).unwrap())), + ); + let mst_cid = generate_mst(&mut self.db, &mut cid_map)?; + Ok(mst_cid.to_string()) } - fn as_cid_map(&mut self, commit_cid: &str) -> Result<BTreeMap<String, Cid>> { - let commit = self.get_commit(commit_cid)?; + fn mst_to_cid_map(&mut self, mst_cid: &str) -> Result<BTreeMap<String, Cid>> { let mut cid_map: BTreeMap<String, Cid> = Default::default(); - let mst_cid = Cid::from_str(&commit.mst_cid)?; - collect_mst_keys(&mut self.db, &mst_cid, &mut cid_map)?; + let mst_cid = Cid::from_str(mst_cid)?; + collect_mst_keys(&mut self.db, &mst_cid, &mut cid_map) + .context("reading repo MST from blockstore")?; Ok(cid_map) } /// Returns all the keys for a directory, as a sorted vec of strings - pub fn as_map(&mut self, commit_cid: &str) -> Result<BTreeMap<String, String>> { - let cid_map = self.as_cid_map(commit_cid)?; + pub fn mst_to_map(&mut self, mst_cid: &str) -> Result<BTreeMap<String, String>> { + let cid_map = self.mst_to_cid_map(mst_cid)?; let ret_map: BTreeMap<String, String> = BTreeMap::from_iter(cid_map.into_iter().map(|(k, v)| (k, v.to_string()))); Ok(ret_map) } + /// returns the root commit from CAR file + pub fn load_car(&mut self, car_path: &PathBuf) -> Result<String> { + let cid = load_car_to_blockstore(&mut self.db, car_path)?; + Ok(cid.to_string()) + } + /// Exports in CAR format to a Writer /// /// The "from" commit CID feature is not implemented. @@ -160,3 +235,92 @@ impl RepoStore { unimplemented!() } } + +#[test] +fn test_repo_mst() { + use libipld::ipld; + + let mut repo = RepoStore::open_ephemeral().unwrap(); + let did = "did:plc:dummy"; + + // basic blob and IPLD record put/get + let blob = b"beware the swamp thing"; + let blob_cid: String = repo.put_blob(blob).unwrap(); + + let record = ipld!({"some-thing": 123}); + let record_cid: String = repo.put_ipld(&record).unwrap(); + + repo.get_blob(&blob_cid).unwrap().unwrap(); + repo.get_ipld(&record_cid).unwrap(); + + // basic MST get/put + let mut map: BTreeMap<String, String> = Default::default(); + let empty_map_cid: String = repo.mst_from_map(&map).unwrap(); + assert_eq!(map, repo.mst_to_map(&empty_map_cid).unwrap()); + assert!(repo + .get_mst_record_by_key(&empty_map_cid, "/records/1") + .unwrap() + .is_none()); + + map.insert("/blobs/1".to_string(), blob_cid.clone()); + map.insert("/blobs/2".to_string(), blob_cid.clone()); + map.insert("/records/1".to_string(), record_cid.clone()); + map.insert("/records/2".to_string(), record_cid.clone()); + let simple_map_cid: String = repo.mst_from_map(&map).unwrap(); + assert_eq!(map, repo.mst_to_map(&simple_map_cid).unwrap()); + + // create root and commit IPLD nodes + let meta_cid = repo.write_metadata(did).unwrap(); + let simple_root_cid = repo + .write_root(did, &meta_cid, None, &simple_map_cid) + .unwrap(); + let simple_commit_cid = repo + .write_commit(did, &simple_root_cid, "dummy-sig") + .unwrap(); + assert_eq!( + Some(record.clone()), + repo.get_mst_record_by_key(&simple_map_cid, "/records/1") + .unwrap() + ); + assert_eq!( + Some(record.clone()), + repo.get_atp_record(did, "records", "1").unwrap() + ); + assert!(repo + .get_mst_record_by_key(&simple_map_cid, "/records/3") + .unwrap() + .is_none()); + assert!(repo.get_atp_record(did, "records", "3").unwrap().is_none()); + assert_eq!( + Some(simple_commit_cid.clone()), + repo.lookup_commit(did).unwrap() + ); + + map.insert("/records/3".to_string(), record_cid.clone()); + let simple3_map_cid: String = repo.mst_from_map(&map).unwrap(); + let simple3_root_cid = repo + .write_root(did, &meta_cid, Some(&simple_commit_cid), &simple3_map_cid) + .unwrap(); + let simple3_commit_cid = repo + .write_commit(did, &simple3_root_cid, "dummy-sig3") + .unwrap(); + assert_eq!(map, repo.mst_to_map(&simple3_map_cid).unwrap()); + assert_eq!( + Some(record.clone()), + repo.get_mst_record_by_key(&simple3_map_cid, "/records/3") + .unwrap() + ); + assert_eq!( + Some(record.clone()), + repo.get_atp_record(did, "records", "3").unwrap() + ); + let commit = repo.get_commit(&simple3_commit_cid).unwrap(); + assert_eq!(commit.sig.to_vec(), b"dummy-sig3".to_vec()); + assert_eq!(commit.did, did); + assert_eq!(commit.prev, Some(simple_commit_cid)); + assert_eq!(commit.mst_cid, simple3_map_cid); + assert_eq!( + Some(simple3_commit_cid.clone()), + repo.lookup_commit(did).unwrap() + ); +} |