Skip to content

Commit

Permalink
feat(iroh): implement basic author api (#2132)
Browse files Browse the repository at this point in the history
- `author.export`
- `author.import`
- `author.delete`
  • Loading branch information
dignifiedquire authored Apr 2, 2024
1 parent 35a1cdd commit 5e1a71f
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 9 deletions.
31 changes: 30 additions & 1 deletion iroh-cli/src/commands/author.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use anyhow::{bail, Result};
use clap::Parser;
use derive_more::FromStr;
use futures::TryStreamExt;
use iroh::base::base32::fmt_short;

use iroh::sync::AuthorId;
use iroh::sync::{Author, AuthorId};
use iroh::{client::Iroh, rpc_protocol::ProviderService};
use quic_rpc::ServiceConnection;

Expand All @@ -19,6 +20,12 @@ pub enum AuthorCommands {
#[clap(long)]
switch: bool,
},
/// Delete an author.
Delete { author: AuthorId },
/// Export an author
Export { author: AuthorId },
/// Import an author
Import { author: String },
/// List authors.
#[clap(alias = "ls")]
List,
Expand Down Expand Up @@ -53,6 +60,28 @@ impl AuthorCommands {
println!("Active author is now {}", fmt_short(author_id.as_bytes()));
}
}
Self::Delete { author } => {
iroh.authors.delete(author).await?;
println!("Deleted author {}", fmt_short(author.as_bytes()));
}
Self::Export { author } => match iroh.authors.export(author).await? {
Some(author) => {
println!("{}", author);
}
None => {
println!("No author found {}", fmt_short(author));
}
},
Self::Import { author } => match Author::from_str(&author) {
Ok(author) => {
let id = author.id();
iroh.authors.import(author).await?;
println!("Imported {}", fmt_short(id));
}
Err(err) => {
eprintln!("Invalid author key: {}", err);
}
},
}
Ok(())
}
Expand Down
30 changes: 30 additions & 0 deletions iroh-sync/src/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ enum Action {
#[debug("reply")]
reply: oneshot::Sender<Result<AuthorId>>,
},
#[display("ExportAuthor")]
ExportAuthor {
author: AuthorId,
#[debug("reply")]
reply: oneshot::Sender<Result<Option<Author>>>,
},
#[display("DeleteAuthor")]
DeleteAuthor {
author: AuthorId,
#[debug("reply")]
reply: oneshot::Sender<Result<()>>,
},
#[display("NewReplica")]
ImportNamespace {
capability: Capability,
Expand Down Expand Up @@ -473,6 +485,18 @@ impl SyncHandle {
rx.await?
}

pub async fn export_author(&self, author: AuthorId) -> Result<Option<Author>> {
let (reply, rx) = oneshot::channel();
self.send(Action::ExportAuthor { author, reply }).await?;
rx.await?
}

pub async fn delete_author(&self, author: AuthorId) -> Result<()> {
let (reply, rx) = oneshot::channel();
self.send(Action::DeleteAuthor { author, reply }).await?;
rx.await?
}

pub async fn import_namespace(&self, capability: Capability) -> Result<NamespaceId> {
let (reply, rx) = oneshot::channel();
self.send(Action::ImportNamespace { capability, reply })
Expand Down Expand Up @@ -561,6 +585,12 @@ impl Actor {
let id = author.id();
send_reply(reply, self.store.import_author(author).map(|_| id))
}
Action::ExportAuthor { author, reply } => {
send_reply(reply, self.store.get_author(&author))
}
Action::DeleteAuthor { author, reply } => {
send_reply(reply, self.store.delete_author(author))
}
Action::ImportNamespace { capability, reply } => send_reply_with(reply, self, |this| {
let id = capability.id();
let outcome = this.store.import_namespace(capability.clone())?;
Expand Down
11 changes: 11 additions & 0 deletions iroh-sync/src/store/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,17 @@ impl Store {
Ok(())
}

/// Delte an author.
pub fn delete_author(&self, author: AuthorId) -> Result<()> {
let write_tx = self.db.begin_write()?;
{
let mut author_table = write_tx.open_table(AUTHORS_TABLE)?;
author_table.remove(author.as_bytes())?;
}
write_tx.commit()?;
Ok(())
}

/// List all author keys in this store.
pub fn list_authors(&self) -> Result<AuthorsIter<'_>> {
// TODO: avoid collect
Expand Down
82 changes: 80 additions & 2 deletions iroh/src/client/authors.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use anyhow::Result;
use futures::{Stream, TryStreamExt};
use iroh_sync::AuthorId;
use iroh_sync::{Author, AuthorId};
use quic_rpc::{RpcClient, ServiceConnection};

use crate::rpc_protocol::{AuthorCreateRequest, AuthorListRequest, ProviderService};
use crate::rpc_protocol::{
AuthorCreateRequest, AuthorDeleteRequest, AuthorExportRequest, AuthorImportRequest,
AuthorListRequest, ProviderService,
};

use super::flatten;

Expand All @@ -28,4 +31,79 @@ where
let stream = self.rpc.server_streaming(AuthorListRequest {}).await?;
Ok(flatten(stream).map_ok(|res| res.author_id))
}

/// Export the given author.
///
/// Warning: This contains sensitive data.
pub async fn export(&self, author: AuthorId) -> Result<Option<Author>> {
let res = self.rpc.rpc(AuthorExportRequest { author }).await??;
Ok(res.author)
}

/// Import the given author.
///
/// Warning: This contains sensitive data.
pub async fn import(&self, author: Author) -> Result<()> {
self.rpc.rpc(AuthorImportRequest { author }).await??;
Ok(())
}

/// Deletes the given author by id.
///
/// Warning: This permanently removes this author.
pub async fn delete(&self, author: AuthorId) -> Result<()> {
self.rpc.rpc(AuthorDeleteRequest { author }).await??;
Ok(())
}
}

#[cfg(test)]
mod tests {
use crate::node::Node;

use super::*;

#[tokio::test]
async fn test_authors() -> Result<()> {
let node = Node::memory().spawn().await?;

let author_id = node.authors.create().await?;

assert_eq!(
node.authors
.list()
.await?
.try_collect::<Vec<_>>()
.await?
.len(),
1
);

let author = node
.authors
.export(author_id)
.await?
.expect("should have author");
node.authors.delete(author_id).await?;
assert!(node
.authors
.list()
.await?
.try_collect::<Vec<_>>()
.await?
.is_empty());

node.authors.import(author).await?;
assert_eq!(
node.authors
.list()
.await?
.try_collect::<Vec<_>>()
.await?
.len(),
1
);

Ok(())
}
}
19 changes: 17 additions & 2 deletions iroh/src/node/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,23 @@ impl<D: BaoStore> Handler<D> {
})
.await
}
AuthorImport(_msg) => {
todo!()
AuthorImport(msg) => {
chan.rpc(msg, handler, |handler, req| async move {
handler.inner.sync.author_import(req).await
})
.await
}
AuthorExport(msg) => {
chan.rpc(msg, handler, |handler, req| async move {
handler.inner.sync.author_export(req).await
})
.await
}
AuthorDelete(msg) => {
chan.rpc(msg, handler, |handler, req| async move {
handler.inner.sync.author_delete(req).await
})
.await
}
DocOpen(msg) => {
chan.rpc(msg, handler, |handler, req| async move {
Expand Down
43 changes: 40 additions & 3 deletions iroh/src/rpc_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use iroh_net::{
use iroh_sync::{
actor::OpenState,
store::{DownloadPolicy, Query},
PeerIdBytes, {AuthorId, CapabilityKind, Entry, NamespaceId, SignedEntry},
Author, PeerIdBytes, {AuthorId, CapabilityKind, Entry, NamespaceId, SignedEntry},
};
use quic_rpc::{
message::{BidiStreaming, BidiStreamingMsg, Msg, RpcMsg, ServerStreaming, ServerStreamingMsg},
Expand Down Expand Up @@ -484,11 +484,44 @@ pub struct AuthorCreateResponse {
pub author_id: AuthorId,
}

/// Delete an author
#[derive(Serialize, Deserialize, Debug)]
pub struct AuthorDeleteRequest {
/// The id of the author to delete
pub author: AuthorId,
}

impl RpcMsg<ProviderService> for AuthorDeleteRequest {
type Response = RpcResult<AuthorDeleteResponse>;
}

/// Response for [`AuthorDeleteRequest`]
#[derive(Serialize, Deserialize, Debug)]
pub struct AuthorDeleteResponse;

/// Exports an author
#[derive(Serialize, Deserialize, Debug)]
pub struct AuthorExportRequest {
/// The id of the author to delete
pub author: AuthorId,
}

impl RpcMsg<ProviderService> for AuthorExportRequest {
type Response = RpcResult<AuthorExportResponse>;
}

/// Response for [`AuthorExportRequest`]
#[derive(Serialize, Deserialize, Debug)]
pub struct AuthorExportResponse {
/// The author
pub author: Option<Author>,
}

/// Import author from secret key
#[derive(Serialize, Deserialize, Debug)]
pub struct AuthorImportRequest {
/// The secret key for the author
pub key: KeyBytes,
/// The author to import
pub author: Author,
}

impl RpcMsg<ProviderService> for AuthorImportRequest {
Expand Down Expand Up @@ -1123,6 +1156,8 @@ pub enum ProviderRequest {
AuthorList(AuthorListRequest),
AuthorCreate(AuthorCreateRequest),
AuthorImport(AuthorImportRequest),
AuthorExport(AuthorExportRequest),
AuthorDelete(AuthorDeleteRequest),
}

/// The response enum, listing all possible responses.
Expand Down Expand Up @@ -1177,6 +1212,8 @@ pub enum ProviderResponse {
AuthorList(RpcResult<AuthorListResponse>),
AuthorCreate(RpcResult<AuthorCreateResponse>),
AuthorImport(RpcResult<AuthorImportResponse>),
AuthorExport(RpcResult<AuthorExportResponse>),
AuthorDelete(RpcResult<AuthorDeleteResponse>),
}

impl Service for ProviderService {
Expand Down
21 changes: 20 additions & 1 deletion iroh/src/sync_engine/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use iroh_bytes::{store::Store as BaoStore, BlobFormat};
use iroh_sync::{Author, NamespaceSecret};
use tokio_stream::StreamExt;

use crate::rpc_protocol::{DocGetSyncPeersRequest, DocGetSyncPeersResponse};
use crate::rpc_protocol::{
AuthorDeleteRequest, AuthorDeleteResponse, AuthorExportRequest, AuthorExportResponse,
AuthorImportRequest, AuthorImportResponse, DocGetSyncPeersRequest, DocGetSyncPeersResponse,
};
use crate::{
rpc_protocol::{
AuthorCreateRequest, AuthorCreateResponse, AuthorListRequest, AuthorListResponse,
Expand Down Expand Up @@ -60,6 +63,22 @@ impl SyncEngine {
})
}

pub async fn author_import(&self, req: AuthorImportRequest) -> RpcResult<AuthorImportResponse> {
let author_id = self.sync.import_author(req.author).await?;
Ok(AuthorImportResponse { author_id })
}

pub async fn author_export(&self, req: AuthorExportRequest) -> RpcResult<AuthorExportResponse> {
let author = self.sync.export_author(req.author).await?;

Ok(AuthorExportResponse { author })
}

pub async fn author_delete(&self, req: AuthorDeleteRequest) -> RpcResult<AuthorDeleteResponse> {
self.sync.delete_author(req.author).await?;
Ok(AuthorDeleteResponse)
}

pub async fn doc_create(&self, _req: DocCreateRequest) -> RpcResult<DocCreateResponse> {
let namespace = NamespaceSecret::new(&mut rand::rngs::OsRng {});
let id = namespace.id();
Expand Down

0 comments on commit 5e1a71f

Please sign in to comment.