Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(iroh): implement basic author api #2132

Merged
merged 3 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why? importing contains sensitive data as well

Copy link
Member

@Frando Frando Apr 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I thought it was a copy-paste error, because I related the contains to a return value.
So fine to leave as-is. Or we rephrase the comments to state what and where sensitive data is involved.

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
Loading