Skip to content

Commit

Permalink
Connect to Holochain and call prepare_fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
ThetaSinner committed Apr 2, 2024
1 parent 861ee25 commit 44b4abc
Show file tree
Hide file tree
Showing 11 changed files with 3,556 additions and 496 deletions.
3,803 changes: 3,332 additions & 471 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ resolver = "2"
[workspace.dependencies]
hdi = "=0.4.0-beta-dev.32"
hdk = "=0.3.0-beta-dev.36"
holochain_types = "0.3.0-beta-dev.38"
serde = "1.0"
chrono = { version = "0.4.34", default-features = false, features = ["clock", "std"] }
hex = "0.4.3"
Expand All @@ -33,5 +34,6 @@ signing_keys = { path = "dnas/checked/zomes/coordinator/signing_keys" }
signing_keys_integrity = { path = "dnas/checked/zomes/integrity/signing_keys" }

fetch_types = { path = "types/fetch" }
checked_fetch_types = { path = "types/checked_fetch" }
fetch = { path = "dnas/checked/zomes/coordinator/fetch" }
fetch_integrity = { path = "dnas/checked/zomes/integrity/fetch" }
14 changes: 14 additions & 0 deletions checked_cli/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions checked_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ name = "checked"

[dependencies]
holochain_client = { version = "=0.5.0-dev.31", default-features = false }
holochain_conductor_api = "0.3.0-beta-dev.40"
holochain_types = "0.3.0-beta-dev.38"
checked_fetch_types = { path = "../types/checked_fetch" }
anyhow = "1.0.81"
clap = { version = "4.5.2", features = ["derive", "cargo"] }
minisign = "0.7.6"
Expand All @@ -16,6 +19,9 @@ tempfile = "3.10.1"
tokio = "1.37.0"
url = "2.5.0"
indicatif = "0.17.8"
ed25519-dalek = "2.1.1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"

[target.'cfg(any(windows, unix))'.dependencies]
dirs = "5.0.1"
Expand Down
14 changes: 14 additions & 0 deletions checked_cli/src/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,27 @@ use indicatif::{ProgressFinish, ProgressStyle};
use std::io::{BufWriter, Write};
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
use holochain_client::ZomeCallTarget;
use holochain_types::prelude::ExternIO;
use checked_fetch_types::{FetchCheckSignature, PrepareFetchRequest};
use crate::hc_client;

struct FetchState {
asset_size: AtomicUsize,
downloaded_size: AtomicUsize,
}

pub async fn fetch(fetch_args: FetchArgs) -> anyhow::Result<()> {
let mut app_client = hc_client::get_authenticated_app_agent_client().await?;

let response = app_client.call_zome(ZomeCallTarget::RoleName("checked".to_string()), "fetch".into(), "prepare_fetch".into(), ExternIO::encode(PrepareFetchRequest {
fetch_url: fetch_args.url.clone(),
}).unwrap()).await.map_err(|e| anyhow::anyhow!("Error calling zome function: {:?}", e))?;

let response: Vec<FetchCheckSignature> = response.decode()?;

println!("Found {} signatures to check against", response.len());

let fetch_url = url::Url::parse(&fetch_args.url).context("Invalid URL")?;
println!("Fetching from {}", fetch_url);

Expand Down
150 changes: 150 additions & 0 deletions checked_cli/src/hc_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use std::fs::{File, Permissions};
use std::io::Write;
use holochain_client::{AdminWebsocket, AppAgentWebsocket, AppStatusFilter, AuthorizeSigningCredentialsPayload, ClientAgentSigner, SigningCredentials};
use holochain_conductor_api::CellInfo;
use holochain_types::prelude::{AgentPubKey, CapSecret, CellId};
use holochain_types::websocket::AllowedOrigins;
use serde::{Deserialize, Serialize};
use crate::common::get_store_dir;

const DEFAULT_INSTALLED_APP_ID: &'static str = "checked";

pub async fn get_authenticated_app_agent_client() -> anyhow::Result<AppAgentWebsocket> {
let mut admin_client = AdminWebsocket::connect("localhost:40589").await?;

let mut signer = ClientAgentSigner::new();
load_or_create_signing_credentials(&mut admin_client, &mut signer).await?;

let app_port = find_or_create_app_interface(&mut admin_client).await?;

AppAgentWebsocket::connect(format!("localhost:{app_port}"), DEFAULT_INSTALLED_APP_ID.to_string(), signer.into()).await
}

async fn find_or_create_app_interface(admin_client: &mut AdminWebsocket) -> anyhow::Result<u16> {
let app_interfaces = admin_client.list_app_interfaces().await.map_err(|e| anyhow::anyhow!("Error listing app interfaces: {:?}", e))?;

// The client doesn't tell us what origins are set for each app interface so we have to pick one.
let app_port = match app_interfaces.first() {
Some(app_port) => {
*app_port
}
None => {
admin_client.attach_app_interface(0, AllowedOrigins::Any).await.map_err(|e| anyhow::anyhow!("Error attaching app interface: {:?}", e))?
}
};
Ok(app_port)
}

async fn load_or_create_signing_credentials(mut admin_client: &mut AdminWebsocket, signer: &mut ClientAgentSigner) -> anyhow::Result<()> {
match try_load_credentials()? {
Some((cell_id, credentials)) => {
signer.add_credentials(cell_id, credentials);
}
None => {
let (cell_id, credentials) = create_new_credentials(&mut admin_client).await?;
dump_credentials(cell_id.clone(), &credentials)?;
signer.add_credentials(cell_id, credentials);
}
}
Ok(())
}

async fn create_new_credentials(client: &mut AdminWebsocket) -> anyhow::Result<(CellId, SigningCredentials)> {
let apps = client.list_apps(Some(AppStatusFilter::Running)).await.map_err(|e| anyhow::anyhow!("Error listing apps: {:?}", e))?;

let app = apps.iter().find(|app| {
// TODO allow this to be overridden on the CLI.
app.installed_app_id == DEFAULT_INSTALLED_APP_ID
}).ok_or_else(|| anyhow::anyhow!("App `checked` not found"))?;

let cells = app.cell_info.get("checked").ok_or_else(|| anyhow::anyhow!("Role `checked` not found"))?;

let cell = cells.iter().find_map(|cell| {
match cell {
CellInfo::Provisioned(cell) if cell.name == "checked" => {
Some(cell)
}
_ => None
}
}).ok_or_else(|| anyhow::anyhow!("Cell `checked` not found"))?;

let credentials = client.authorize_signing_credentials(AuthorizeSigningCredentialsPayload {
cell_id: cell.cell_id.clone(),
functions: None, // For all, not documented!
}).await.map_err(|e| anyhow::anyhow!("Error authorizing signing credentials: {:?}", e))?;

Ok((cell.cell_id.clone(), credentials))
}

#[derive(Serialize, Deserialize)]
struct SavedCredentials {
cell_id: CellId,
signing_agent_key: AgentPubKey,
keypair: Vec<u8>,
cap_secret: CapSecret,
}

fn dump_credentials(cell_id: CellId, signing_credentials: &SigningCredentials) -> anyhow::Result<()> {
let saved = SavedCredentials {
cell_id: cell_id.clone(),
signing_agent_key: signing_credentials.signing_agent_key.clone(),
keypair: signing_credentials.keypair.to_keypair_bytes().to_vec(),
cap_secret: signing_credentials.cap_secret.clone(),
};

let serialized = serde_json::to_string(&saved).map_err(|e| anyhow::anyhow!("Error serializing credentials: {:?}", e))?;

// generate_args.path
let credentials_path = get_credentials_path()?;

let mut f= File::options().create(true).write(true).truncate(true).open(&credentials_path).map_err(|e| anyhow::anyhow!("Error opening credentials file: {:?}", e))?;

if cfg!(unix) {
use std::os::unix::fs::PermissionsExt;
f.set_permissions(Permissions::from_mode(0o660)).map_err(|e| anyhow::anyhow!("Error setting permissions on credentials file: {:?}", e))?;
}

f.write_all(serialized.as_bytes()).map_err(|e| anyhow::anyhow!("Error writing credentials file: {:?}", e))?;

Ok(())
}

fn try_load_credentials() -> anyhow::Result<Option<(CellId, SigningCredentials)>> {
let credentials_path = get_credentials_path()?;

let f = match File::open(&credentials_path) {
Ok(f) => f,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
return Ok(None);
}
Err(e) => {
return Err(anyhow::anyhow!("Error reading credentials file: {:?}", e));
}
};

let saved: SavedCredentials = match serde_json::from_reader(f) {
Ok(saved) => saved,
Err(e) => {
eprintln!("Saved credentials file is corrupt: {:?}", e);
return Ok(None)
}
};

let keypair = match ed25519_dalek::SigningKey::from_keypair_bytes(saved.keypair.as_slice().try_into().unwrap()) {
Ok(keypair) => keypair,
Err(e) => {
eprintln!("Saved credentials file is corrupt: {:?}", e);
return Ok(None)
}
};

Ok(Some((saved.cell_id, SigningCredentials {
signing_agent_key: saved.signing_agent_key,
keypair,
cap_secret: saved.cap_secret,
})))
}

fn get_credentials_path() -> anyhow::Result<std::path::PathBuf> {
Ok(get_store_dir(None)?.join("credentials.json"))
}
1 change: 1 addition & 0 deletions checked_cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod password;
pub mod sign;
pub mod verify;
mod fetch;
pub(crate) mod hc_client;

pub mod prelude {
pub use crate::cli::{Cli, Commands, GenerateArgs, SignArgs};
Expand Down
1 change: 1 addition & 0 deletions dnas/checked/zomes/coordinator/fetch/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ rand = { workspace = true }
signing_keys_types = { workspace = true }
fetch_integrity = { workspace = true }
fetch_types = { workspace = true }
checked_fetch_types = { workspace = true }

[dev-dependencies]
chrono = { workspace = true }
26 changes: 1 addition & 25 deletions dnas/checked/zomes/coordinator/fetch/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use fetch_integrity::prelude::*;
use checked_fetch_types::*;
use hdk::prelude::hash_type::AnyLinkable;
use hdk::prelude::*;
use rand::prelude::IteratorRandom;
Expand All @@ -7,31 +8,6 @@ use signing_keys_types::*;
use std::ops::{Add, Deref, Sub};
use std::time::Duration;

#[derive(Serialize, Deserialize, Debug)]
pub struct PrepareFetchRequest {
pub fetch_url: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct FetchCheckSignaturePinned {
pub author: AgentPubKey,
pub key_collection: String,
pub key_name: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub enum FetchCheckSignatureReason {
RandomRecent,
RandomHistorical,
Pinned(FetchCheckSignaturePinned),
}

#[derive(Serialize, Deserialize, Debug)]
pub struct FetchCheckSignature {
signature: Vec<u8>,
reason: FetchCheckSignatureReason,
}

#[hdk_extern]
fn prepare_fetch(request: PrepareFetchRequest) -> ExternResult<Vec<FetchCheckSignature>> {
let asset_base = make_asset_url_address(&request.fetch_url)?;
Expand Down
8 changes: 8 additions & 0 deletions types/checked_fetch/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "checked_fetch_types"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = "1"
holochain_types = { workspace = true }
27 changes: 27 additions & 0 deletions types/checked_fetch/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use serde::{Serialize, Deserialize};
use holochain_types::prelude::AgentPubKey;

#[derive(Serialize, Deserialize, Debug)]
pub struct PrepareFetchRequest {
pub fetch_url: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct FetchCheckSignaturePinned {
pub author: AgentPubKey,
pub key_collection: String,
pub key_name: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub enum FetchCheckSignatureReason {
RandomRecent,
RandomHistorical,
Pinned(FetchCheckSignaturePinned),
}

#[derive(Serialize, Deserialize, Debug)]
pub struct FetchCheckSignature {
pub signature: Vec<u8>,
pub reason: FetchCheckSignatureReason,
}

0 comments on commit 44b4abc

Please sign in to comment.